mirror of
https://github.com/zadam/trilium.git
synced 2025-10-28 00:36:33 +01:00
Compare commits
13 Commits
v0.24.0-be
...
v0.24.1-be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1ed022771 | ||
|
|
ad6cb6ba34 | ||
|
|
2e76de5f34 | ||
|
|
4f23f2515a | ||
|
|
8e16cc2326 | ||
|
|
49bca04ebb | ||
|
|
05e9669eaf | ||
|
|
62a250a7fc | ||
|
|
8299524682 | ||
|
|
7691a59977 | ||
|
|
3db2f6784d | ||
|
|
48684d0509 | ||
|
|
1ee8d9fd93 |
@@ -1,4 +1,4 @@
|
||||
FROM node:10.12.0
|
||||
FROM node:10.13.0
|
||||
|
||||
RUN apt-get update && apt-get install -y nasm
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ fi
|
||||
|
||||
VERSION=$1
|
||||
PKG_DIR=dist/trilium-linux-x64-server
|
||||
NODE_VERSION=10.12.0
|
||||
NODE_VERSION=10.13.0
|
||||
|
||||
rm -r $PKG_DIR
|
||||
mkdir $PKG_DIR
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
-- first fix deleted status of existing images
|
||||
UPDATE note_images SET isDeleted = 1 WHERE noteId IN (SELECT noteId FROM notes WHERE isDeleted = 1);
|
||||
|
||||
-- we don't need set data to null because table is going to be dropped anyway and we want image size into attribute
|
||||
UPDATE images SET isDeleted = 1 WHERE imageId NOT IN (SELECT imageId FROM note_images WHERE isDeleted = 0);
|
||||
|
||||
-- allow null for note content (for deleted notes)
|
||||
CREATE TABLE IF NOT EXISTS "notes_mig" (
|
||||
`noteId` TEXT NOT NULL,
|
||||
|
||||
1
db/migrations/0117__fix_attributes_of_deleted_notes.sql
Normal file
1
db/migrations/0117__fix_attributes_of_deleted_notes.sql
Normal file
@@ -0,0 +1 @@
|
||||
UPDATE attributes SET isDeleted = 1 WHERE noteId IN (SELECT noteId FROM notes WHERE isDeleted = 1);
|
||||
1
db/migrations/0118__fix_broken_relations.sql
Normal file
1
db/migrations/0118__fix_broken_relations.sql
Normal file
@@ -0,0 +1 @@
|
||||
UPDATE attributes SET isDeleted = 1 WHERE type = 'relation' AND value NOT IN (SELECT noteId FROM notes WHERE notes.isDeleted = 0);
|
||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "trilium",
|
||||
"version": "0.23.1",
|
||||
"version": "0.24.0-beta",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "trilium",
|
||||
"description": "Trilium Notes",
|
||||
"version": "0.24.0-beta",
|
||||
"version": "0.24.1-beta",
|
||||
"license": "AGPL-3.0-only",
|
||||
"main": "electron.js",
|
||||
"bin": {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
const $dialog = $("#prompt-dialog");
|
||||
const $question = $("#prompt-dialog-question");
|
||||
const $answer = $("#prompt-dialog-answer");
|
||||
const $dialogBody = $dialog.find(".modal-body");
|
||||
|
||||
let $question;
|
||||
let $answer;
|
||||
|
||||
const $form = $("#prompt-dialog-form");
|
||||
|
||||
let resolve;
|
||||
@@ -11,8 +14,21 @@ function ask({ message, defaultValue, shown }) {
|
||||
|
||||
shownCb = shown;
|
||||
|
||||
$question.text(message);
|
||||
$answer.val(defaultValue || "");
|
||||
$question = $("<label>")
|
||||
.prop("for", "prompt-dialog-answer")
|
||||
.text(message);
|
||||
|
||||
$answer = $("<input>")
|
||||
.prop("type", "text")
|
||||
.prop("id", "prompt-dialog-answer")
|
||||
.addClass("form-control")
|
||||
.val(defaultValue || "");
|
||||
|
||||
$dialogBody.empty().append(
|
||||
$("<div>")
|
||||
.addClass("form-group")
|
||||
.append($question)
|
||||
.append($answer));
|
||||
|
||||
$dialog.modal();
|
||||
|
||||
|
||||
@@ -50,7 +50,12 @@ async function execute(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const sqlQuery = codeEditor.getValue();
|
||||
// execute the selected text or the whole content if there's no selection
|
||||
let sqlQuery = codeEditor.getSelection();
|
||||
|
||||
if (!sqlQuery) {
|
||||
sqlQuery = codeEditor.getValue();
|
||||
}
|
||||
|
||||
const result = await server.post("sql/execute", {
|
||||
query: sqlQuery
|
||||
|
||||
@@ -117,6 +117,15 @@ async function show() {
|
||||
|
||||
}
|
||||
|
||||
function clearMap() {
|
||||
// delete all endpoints and connections
|
||||
// this is done at this point (after async operations) to reduce flicker to the minimum
|
||||
jsPlumbInstance.deleteEveryEndpoint();
|
||||
|
||||
// without this we still end up with note boxes remaining in the canvas
|
||||
$relationMapContainer.empty();
|
||||
}
|
||||
|
||||
async function loadNotesAndRelations() {
|
||||
const noteIds = mapData.notes.map(note => note.noteId);
|
||||
const data = await server.post("notes/relation-map", {noteIds});
|
||||
@@ -142,11 +151,9 @@ async function loadNotesAndRelations() {
|
||||
|
||||
mapData.notes = mapData.notes.filter(note => note.noteId in data.noteTitles);
|
||||
|
||||
// delete all endpoints and connections
|
||||
// this is done at this point (after async operations) to reduce flicker to the minimum
|
||||
jsPlumbInstance.deleteEveryEndpoint();
|
||||
|
||||
jsPlumbInstance.batch(async function () {
|
||||
clearMap();
|
||||
|
||||
for (const note of mapData.notes) {
|
||||
const title = data.noteTitles[note.noteId];
|
||||
|
||||
@@ -208,10 +215,17 @@ function initPanZoom() {
|
||||
|
||||
mapData.notes.push({ noteId: clipboard.noteId, x, y });
|
||||
|
||||
saveData();
|
||||
|
||||
clipboard = null;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
filterKey: function(e, dx, dy, dz) {
|
||||
// if ALT is pressed then panzoom should bubble the event up
|
||||
// this is to preserve ALT-LEFT, ALT-RIGHT navigation working
|
||||
return e.altKey;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -244,11 +258,7 @@ function saveCurrentTransform() {
|
||||
|
||||
function cleanup() {
|
||||
if (jsPlumbInstance) {
|
||||
// delete all endpoints and connections
|
||||
jsPlumbInstance.deleteEveryEndpoint();
|
||||
|
||||
// without this we still end up with note boxes remaining in the canvas
|
||||
$relationMapContainer.empty();
|
||||
clearMap();
|
||||
}
|
||||
|
||||
if (pzInstance) {
|
||||
@@ -312,8 +322,6 @@ function connectionContextMenuHandler(connection, event) {
|
||||
async function connectionCreatedHandler(info, originalEvent) {
|
||||
const connection = info.connection;
|
||||
|
||||
const isRelation = relations.some(rel => rel.attributeId === connection.id);
|
||||
|
||||
connection.bind("contextmenu", (obj, event) => {
|
||||
if (connection.getType().includes("link")) {
|
||||
// don't create context menu if it's a link since there's nothing to do with link from relation map
|
||||
@@ -362,9 +370,7 @@ async function connectionCreatedHandler(info, originalEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
const attribute = await server.put(`notes/${sourceNoteId}/relations/${name}/to/${targetNoteId}`);
|
||||
|
||||
relations.push({ attributeId: attribute.attributeId , targetNoteId, sourceNoteId, name });
|
||||
await server.put(`notes/${sourceNoteId}/relations/${name}/to/${targetNoteId}`);
|
||||
|
||||
await refresh();
|
||||
}
|
||||
|
||||
@@ -41,6 +41,10 @@ function setupTooltip() {
|
||||
if ($(this).is(":hover")) {
|
||||
$(this).tooltip({
|
||||
delay: {"show": 300, "hide": 100},
|
||||
container: 'body',
|
||||
placement: 'auto',
|
||||
trigger: 'manual',
|
||||
boundariesElement: 'window',
|
||||
title: html,
|
||||
html: true
|
||||
});
|
||||
@@ -50,7 +54,7 @@ function setupTooltip() {
|
||||
});
|
||||
|
||||
$(document).on("mouseleave", "a", function() {
|
||||
$(this).tooltip('hide');
|
||||
$(this).tooltip('dispose');
|
||||
});
|
||||
|
||||
// close any tooltip after click, this fixes the problem that sometimes tooltips remained on the screen
|
||||
|
||||
@@ -5,9 +5,7 @@
|
||||
|
||||
#relation-map-wrapper {
|
||||
position: relative;
|
||||
overflow: hidden !important;
|
||||
height: 4000px; /* we need to set fixed dimentions. This number is probably enough to cover any screen */
|
||||
width: 4000px;
|
||||
height: 100%;
|
||||
outline: none; /* remove dotted outline on click */
|
||||
}
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ body {
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
flex-basis: content;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.note-detail-component {
|
||||
|
||||
@@ -95,7 +95,7 @@ async function updateNoteAttributes(req) {
|
||||
attributeEntity.isInheritable = attribute.isInheritable;
|
||||
attributeEntity.isDeleted = attribute.isDeleted;
|
||||
|
||||
if (attributeEntity.type === 'relation' && !attributeEntity.value.trim()) {
|
||||
if (attributeEntity.type === 'relation' && !attribute.value.trim()) {
|
||||
// relation should never have empty target
|
||||
attributeEntity.isDeleted = true;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
const build = require('./build');
|
||||
const packageJson = require('../../package');
|
||||
|
||||
const APP_DB_VERSION = 116;
|
||||
const APP_DB_VERSION = 118;
|
||||
const SYNC_VERSION = 2;
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -1 +1 @@
|
||||
module.exports = { buildDate:"2018-11-16T23:30:52+01:00", buildRevision: "90eb1b53fbe915c4658617772aea4347a107a722" };
|
||||
module.exports = { buildDate:"2018-11-19T00:06:44+01:00", buildRevision: "ad6cb6ba347f0396cbf79b76ab62ee3e4a4e8566" };
|
||||
|
||||
@@ -275,8 +275,9 @@ async function runAllChecks() {
|
||||
LEFT JOIN notes AS sourceNote ON sourceNote.noteId = links.noteId AND sourceNote.isDeleted = 0
|
||||
LEFT JOIN notes AS targetNote ON targetNote.noteId = links.noteId AND targetNote.isDeleted = 0
|
||||
WHERE
|
||||
sourceNote.noteId IS NULL
|
||||
OR targetNote.noteId IS NULL`,
|
||||
links.isDeleted = 0
|
||||
AND (sourceNote.noteId IS NULL
|
||||
OR targetNote.noteId IS NULL)`,
|
||||
"Link to source/target note link is broken", errorList);
|
||||
|
||||
await runSyncRowChecks("notes", "noteId", errorList);
|
||||
|
||||
@@ -79,7 +79,11 @@ async function processMirrorRelations(entityName, entity, handler) {
|
||||
eventService.subscribe(eventService.ENTITY_CHANGED, async ({ entityName, entity }) => {
|
||||
await processMirrorRelations(entityName, entity, async (definition, note, targetNote) => {
|
||||
// we need to make sure that also target's mirror attribute exists and if note, then create it
|
||||
if (!await targetNote.hasRelation(definition.mirrorRelation)) {
|
||||
// mirror attribute has to target our note as well
|
||||
const hasMirrorAttribute = (await targetNote.getRelations(definition.mirrorRelation))
|
||||
.some(attr => attr.value === note.noteId);
|
||||
|
||||
if (!hasMirrorAttribute) {
|
||||
await new Attribute({
|
||||
noteId: targetNote.noteId,
|
||||
type: 'relation',
|
||||
@@ -97,13 +101,18 @@ eventService.subscribe(eventService.ENTITY_DELETED, async ({ entityName, entity
|
||||
await processMirrorRelations(entityName, entity, async (definition, note, targetNote) => {
|
||||
// if one mirror attribute is deleted then the other should be deleted as well
|
||||
const relations = await targetNote.getRelations(definition.mirrorRelation);
|
||||
let deletedSomething = false;
|
||||
|
||||
for (const relation of relations) {
|
||||
relation.isDeleted = true;
|
||||
await relation.save();
|
||||
if (relation.value === note.noteId) {
|
||||
relation.isDeleted = true;
|
||||
await relation.save();
|
||||
|
||||
deletedSomething = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (relations.length > 0) {
|
||||
if (deletedSomething) {
|
||||
targetNote.invalidateAttributeCache();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -31,6 +31,11 @@ async function importTar(fileBuffer, parentNote) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// we allow references to root and they don't need translation
|
||||
if (origNoteId === 'root') {
|
||||
return origNoteId;
|
||||
}
|
||||
|
||||
if (!ctx.noteIdMap[origNoteId]) {
|
||||
ctx.noteIdMap[origNoteId] = utils.newEntityId();
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ const sqlInit = require('./sql_init');
|
||||
const optionService = require('./options');
|
||||
const fs = require('fs-extra');
|
||||
const log = require('./log');
|
||||
const utils = require('./utils');
|
||||
const resourceDir = require('./resource_dir');
|
||||
|
||||
async function migrate() {
|
||||
@@ -72,7 +73,7 @@ async function migrate() {
|
||||
log.error("error during migration to version " + mig.dbVersion + ": " + e.stack);
|
||||
log.error("migration failed, crashing hard"); // this is not very user friendly :-/
|
||||
|
||||
process.exit(1);
|
||||
utils.crash();
|
||||
}
|
||||
finally {
|
||||
// make sure foreign keys are enabled even if migration script disables them
|
||||
|
||||
@@ -68,6 +68,10 @@ async function createNewNote(parentNoteId, noteData) {
|
||||
noteData.type = noteData.type || parentNote.type;
|
||||
noteData.mime = noteData.mime || parentNote.mime;
|
||||
|
||||
if (noteData.type === 'text' || noteData.type === 'code') {
|
||||
noteData.content = noteData.content || "";
|
||||
}
|
||||
|
||||
const note = await new Note({
|
||||
noteId: noteData.noteId, // optionally can force specific noteId
|
||||
title: noteData.title,
|
||||
@@ -374,19 +378,12 @@ async function deleteNote(branch) {
|
||||
async function cleanupDeletedNotes() {
|
||||
const cutoffDate = new Date(new Date().getTime() - 48 * 3600 * 1000);
|
||||
|
||||
const notesForCleanup = await repository.getEntities("SELECT * FROM notes WHERE isDeleted = 1 AND content != '' AND dateModified <= ?", [dateUtils.dateStr(cutoffDate)]);
|
||||
// it's better to not use repository for this because it will complain about saving protected notes
|
||||
// out of protected session
|
||||
|
||||
for (const note of notesForCleanup) {
|
||||
note.content = null;
|
||||
await note.save();
|
||||
}
|
||||
await sql.execute("UPDATE notes SET content = NULL WHERE isDeleted = 1 AND content IS NOT NULL AND dateModified <= ?", [dateUtils.dateStr(cutoffDate)]);
|
||||
|
||||
const notesRevisionsForCleanup = await repository.getEntities("SELECT note_revisions.* FROM notes JOIN note_revisions USING(noteId) WHERE notes.isDeleted = 1 AND note_revisions.content != '' AND notes.dateModified <= ?", [dateUtils.dateStr(cutoffDate)]);
|
||||
|
||||
for (const noteRevision of notesRevisionsForCleanup) {
|
||||
noteRevision.content = null;
|
||||
await noteRevision.save();
|
||||
}
|
||||
await sql.execute("UPDATE note_revisions SET content = NULL WHERE note_revisions.content IS NOT NULL AND noteId IN (SELECT noteId FROM notes WHERE isDeleted = 1 AND notes.dateModified <= ?)", [dateUtils.dateStr(cutoffDate)]);
|
||||
}
|
||||
|
||||
// first cleanup kickoff 5 minutes after startup
|
||||
|
||||
@@ -118,6 +118,15 @@ function escapeRegExp(str) {
|
||||
return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
|
||||
}
|
||||
|
||||
function crash() {
|
||||
if (isElectron()) {
|
||||
require('electron').app.exit(1);
|
||||
}
|
||||
else {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
randomSecureToken,
|
||||
randomString,
|
||||
@@ -137,5 +146,6 @@ module.exports = {
|
||||
stripTags,
|
||||
intersection,
|
||||
union,
|
||||
escapeRegExp
|
||||
escapeRegExp,
|
||||
crash
|
||||
};
|
||||
@@ -10,10 +10,6 @@
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="prompt-dialog-answer" id="prompt-dialog-question"></label>
|
||||
<input type="text" class="form-control" id="prompt-dialog-answer" placeholder="">
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-primary btn-sm" id="prompt-dialog-ok-button">OK <kbd>enter</kbd></button>
|
||||
|
||||
Reference in New Issue
Block a user