Compare commits

...

13 Commits

Author SHA1 Message Date
azivner
b1ed022771 release 0.24.1-beta 2018-11-19 00:06:44 +01:00
azivner
ad6cb6ba34 mirror relation has to target source note 2018-11-19 00:06:04 +01:00
azivner
2e76de5f34 minor relation map fixes 2018-11-18 23:57:39 +01:00
azivner
4f23f2515a fix relation saving 2018-11-18 23:36:21 +01:00
azivner
8e16cc2326 fix prompt mutability (autocomplete on answer input field) 2018-11-18 23:31:31 +01:00
azivner
49bca04ebb fix doubling of notes after some actions 2018-11-18 23:01:48 +01:00
azivner
05e9669eaf fixed relation map sizing issue 2018-11-18 22:49:53 +01:00
azivner
62a250a7fc fixes for tooltip handling 2018-11-18 20:57:52 +01:00
azivner
8299524682 fixed cleanup of deleted notes for protected notes 2018-11-18 11:34:40 +01:00
azivner
7691a59977 imported notes can reference root 2018-11-18 11:19:50 +01:00
azivner
3db2f6784d fix DB migration and consistency issues 2018-11-18 10:20:06 +01:00
azivner
48684d0509 fixed app termination after unsuccessful migration for electron 2018-11-18 09:05:50 +01:00
azivner
1ee8d9fd93 sql console executes selected text if there's a selection instead of the whole content 2018-11-18 08:29:56 +01:00
23 changed files with 110 additions and 53 deletions

View File

@@ -1,4 +1,4 @@
FROM node:10.12.0
FROM node:10.13.0
RUN apt-get update && apt-get install -y nasm

View File

@@ -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

View File

@@ -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,

View File

@@ -0,0 +1 @@
UPDATE attributes SET isDeleted = 1 WHERE noteId IN (SELECT noteId FROM notes WHERE isDeleted = 1);

View 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
View File

@@ -1,6 +1,6 @@
{
"name": "trilium",
"version": "0.23.1",
"version": "0.24.0-beta",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -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": {

View File

@@ -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();

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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

View File

@@ -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 */
}

View File

@@ -76,6 +76,7 @@ body {
position: relative;
overflow: auto;
flex-basis: content;
height: 100%;
}
.note-detail-component {

View File

@@ -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;
}

View File

@@ -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 = {

View File

@@ -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" };

View File

@@ -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);

View File

@@ -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();
}
});

View File

@@ -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();
}

View File

@@ -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

View File

@@ -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

View File

@@ -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
};

View File

@@ -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>