Compare commits

...

12 Commits

Author SHA1 Message Date
zadam
9a13edd490 release 0.39.6 2020-01-18 20:52:14 +01:00
zadam
0ec11d29ba fix creating root calendar note when missing, #752 2020-01-18 08:59:46 +01:00
zadam
a6cd25071e more robust handling of sync error, fixes #830 2020-01-18 08:48:36 +01:00
zadam
759e47bfcf using included note should create a relation, closes #820 2020-01-10 21:41:00 +01:00
zadam
67bdffb27b expose text editor instance and method to add text to editor, closes #819 2020-01-10 20:10:17 +01:00
zadam
3386dace3b provide context menu in text editor also with disabled spellcheck 2020-01-10 19:56:27 +01:00
zadam
3cf3fc13b9 release 0.39.5 2020-01-08 21:01:24 +01:00
zadam
2b69abf8ab fix filter parser for >=, <=, *=* 2020-01-08 20:23:41 +01:00
zadam
3e49a7dbfa all consistency checkers have now fixers 2020-01-08 19:28:22 +01:00
zadam
f782d2bef9 don't empty script area on save 2020-01-07 22:29:15 +01:00
zadam
ccaa9eae3a fix context submenus, closes #810 2020-01-07 20:53:41 +01:00
zadam
24c5388e0c protection against text note initialization race conditions 2020-01-07 19:48:26 +01:00
23 changed files with 438 additions and 111 deletions

1
.idea/.gitignore generated vendored
View File

@@ -3,3 +3,4 @@
# Datasource local storage ignored files # Datasource local storage ignored files
/dataSources.local.xml /dataSources.local.xml
/dataSources/

View File

@@ -1240,6 +1240,143 @@
<h4 class="name" id="addTextToActiveTabEditor"><span class="type-signature"></span>addTextToActiveTabEditor<span class="signature">(text)</span><span class="type-signature"></span></h4>
<div class="description">
Adds given text to the editor cursor
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>text</code></td>
<td class="type">
<span class="param-type">string</span>
</td>
<td class="description last"></td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line305">line 305</a>
</li></ul></dd>
</dl>
@@ -1366,7 +1503,7 @@
<dt class="tag-source">Source:</dt> <dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li> <dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line390">line 390</a> <a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line406">line 406</a>
</li></ul></dd> </li></ul></dd>
@@ -1785,7 +1922,7 @@
<dt class="tag-source">Source:</dt> <dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li> <dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line303">line 303</a> <a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line311">line 311</a>
</li></ul></dd> </li></ul></dd>
@@ -1891,7 +2028,7 @@
<dt class="tag-source">Source:</dt> <dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li> <dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line309">line 309</a> <a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line325">line 325</a>
</li></ul></dd> </li></ul></dd>
@@ -1949,6 +2086,119 @@
<h4 class="name" id="getActiveTabTextEditor"><span class="type-signature"></span>getActiveTabTextEditor<span class="signature">()</span><span class="type-signature"> &rarr; {Editor|null}</span></h4>
<div class="description">
See https://ckeditor.com/docs/ckeditor5/latest/api/module_core_editor_editor-Editor.html for a documentation on the returned instance.
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line319">line 319</a>
</li></ul></dd>
</dl>
<h5>Returns:</h5>
<div class="param-desc">
CKEditor instance or null (e.g. if active note is not a text note)
</div>
<dl>
<dt>
Type
</dt>
<dd>
<span class="param-type">Editor</span>
|
<span class="param-type">null</span>
</dd>
</dl>
<h4 class="name" id="getDateNote"><span class="type-signature"></span>getDateNote<span class="signature">(date)</span><span class="type-signature"> &rarr; {Promise.&lt;<a href="NoteShort.html">NoteShort</a>>}</span></h4> <h4 class="name" id="getDateNote"><span class="type-signature"></span>getDateNote<span class="signature">(date)</span><span class="type-signature"> &rarr; {Promise.&lt;<a href="NoteShort.html">NoteShort</a>>}</span></h4>
@@ -2050,7 +2300,7 @@
<dt class="tag-source">Source:</dt> <dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li> <dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line356">line 356</a> <a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line372">line 372</a>
</li></ul></dd> </li></ul></dd>
@@ -2312,7 +2562,7 @@ if some action needs to happen on only one specific instance.
<dt class="tag-source">Source:</dt> <dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li> <dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line365">line 365</a> <a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line381">line 381</a>
</li></ul></dd> </li></ul></dd>
@@ -2775,7 +3025,7 @@ otherwise (by e.g. createNoteLink())
<dt class="tag-source">Source:</dt> <dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li> <dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line347">line 347</a> <a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line363">line 363</a>
</li></ul></dd> </li></ul></dd>
@@ -2930,7 +3180,7 @@ otherwise (by e.g. createNoteLink())
<dt class="tag-source">Source:</dt> <dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li> <dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line374">line 374</a> <a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line390">line 390</a>
</li></ul></dd> </li></ul></dd>
@@ -3039,7 +3289,7 @@ note.
<dt class="tag-source">Source:</dt> <dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li> <dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line320">line 320</a> <a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line336">line 336</a>
</li></ul></dd> </li></ul></dd>
@@ -3194,7 +3444,7 @@ note.
<dt class="tag-source">Source:</dt> <dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li> <dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line328">line 328</a> <a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line344">line 344</a>
</li></ul></dd> </li></ul></dd>
@@ -3433,7 +3683,7 @@ note.
<dt class="tag-source">Source:</dt> <dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li> <dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line339">line 339</a> <a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line355">line 355</a>
</li></ul></dd> </li></ul></dd>
@@ -4606,7 +4856,7 @@ Internally this serializes the anonymous function into string and sends it to ba
<dt class="tag-source">Source:</dt> <dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li> <dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line383">line 383</a> <a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line399">line 399</a>
</li></ul></dd> </li></ul></dd>
@@ -4757,7 +5007,7 @@ Internally this serializes the anonymous function into string and sends it to ba
<dt class="tag-source">Source:</dt> <dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li> <dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line334">line 334</a> <a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line350">line 350</a>
</li></ul></dd> </li></ul></dd>
@@ -5123,7 +5373,7 @@ Typical use case is when new note has been created, we should wait until it is s
<dt class="tag-source">Source:</dt> <dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li> <dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line401">line 401</a> <a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line417">line 417</a>
</li></ul></dd> </li></ul></dd>

View File

@@ -324,12 +324,28 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, tabConte
*/ */
this.createNoteLink = linkService.createNoteLink; this.createNoteLink = linkService.createNoteLink;
/**
* Adds given text to the editor cursor
*
* @param {string} text
* @method
*/
this.addTextToActiveTabEditor = linkService.addTextToEditor;
/** /**
* @method * @method
* @returns {NoteFull} active note (loaded into right pane) * @returns {NoteFull} active note (loaded into right pane)
*/ */
this.getActiveTabNote = noteDetailService.getActiveTabNote; this.getActiveTabNote = noteDetailService.getActiveTabNote;
/**
* See https://ckeditor.com/docs/ckeditor5/latest/api/module_core_editor_editor-Editor.html for a documentation on the returned instance.
*
* @method
* @returns {Editor|null} CKEditor instance or null (e.g. if active note is not a text note)
*/
this.getActiveTabTextEditor = noteDetailService.getActiveEditor;
/** /**
* @method * @method
* @returns {Promise&lt;string|null>} returns note path of active note or null if there isn't active note * @returns {Promise&lt;string|null>} returns note path of active note or null if there isn't active note

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{ {
"name": "trilium", "name": "trilium",
"version": "0.39.3", "version": "0.39.5",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@@ -2,7 +2,7 @@
"name": "trilium", "name": "trilium",
"productName": "Trilium Notes", "productName": "Trilium Notes",
"description": "Trilium Notes", "description": "Trilium Notes",
"version": "0.39.4", "version": "0.39.6",
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"main": "electron.js", "main": "electron.js",
"bin": { "bin": {

View File

@@ -805,7 +805,7 @@ class Note extends Entity {
WHERE noteId = ? AND WHERE noteId = ? AND
isDeleted = 0 AND isDeleted = 0 AND
type = 'relation' AND type = 'relation' AND
name IN ('internalLink', 'imageLink', 'relationMapLink')`, [this.noteId]); name IN ('internalLink', 'imageLink', 'relationMapLink', 'includeNoteLink')`, [this.noteId]);
} }
/** /**

View File

@@ -6,7 +6,7 @@ const TPL = `
<div> <div>
<h4>Spell check</h4> <h4>Spell check</h4>
<p>These options apply only for desktop builds, browsers will use their own native spell check.</p> <p>These options apply only for desktop builds, browsers will use their own native spell check. App restart is required after change.</p>
<div class="custom-control custom-checkbox"> <div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="spell-check-enabled"> <input type="checkbox" class="custom-control-input" id="spell-check-enabled">

View File

@@ -76,8 +76,7 @@ async function initContextMenu(event, contextMenu) {
// in such case we'll position it above click coordinates so it will fit into client // in such case we'll position it above click coordinates so it will fit into client
const clickPosition = event.pageY; const clickPosition = event.pageY;
const clientHeight = document.documentElement.clientHeight; const clientHeight = document.documentElement.clientHeight;
const contextMenuHeight = $contextMenuContainer.height(); const contextMenuHeight = $contextMenuContainer.outerHeight() + 30;
let top; let top;
if (clickPosition + contextMenuHeight > clientHeight) { if (clickPosition + contextMenuHeight > clientHeight) {

View File

@@ -296,12 +296,28 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, tabConte
*/ */
this.createNoteLink = linkService.createNoteLink; this.createNoteLink = linkService.createNoteLink;
/**
* Adds given text to the editor cursor
*
* @param {string} text - this must be clear text, HTML is not supported.
* @method
*/
this.addTextToActiveTabEditor = linkService.addTextToEditor;
/** /**
* @method * @method
* @returns {NoteFull} active note (loaded into right pane) * @returns {NoteFull} active note (loaded into right pane)
*/ */
this.getActiveTabNote = noteDetailService.getActiveTabNote; this.getActiveTabNote = noteDetailService.getActiveTabNote;
/**
* See https://ckeditor.com/docs/ckeditor5/latest/api/module_core_editor_editor-Editor.html for a documentation on the returned instance.
*
* @method
* @returns {Editor|null} CKEditor instance or null (e.g. if active note is not a text note)
*/
this.getActiveTabTextEditor = noteDetailService.getActiveEditor;
/** /**
* @method * @method
* @returns {Promise<string|null>} returns note path of active note or null if there isn't active note * @returns {Promise<string|null>} returns note path of active note or null if there isn't active note

View File

@@ -201,17 +201,13 @@ async function loadNoteDetail(origNotePath, options = {}) {
const newTab = !!options.newTab; const newTab = !!options.newTab;
const activate = !!options.activate; const activate = !!options.activate;
const notePath = await treeService.resolveNotePath(origNotePath); let notePath = await treeService.resolveNotePath(origNotePath);
if (!notePath) { if (!notePath) {
console.error(`Cannot resolve note path ${origNotePath}`); console.error(`Cannot resolve note path ${origNotePath}`);
// fallback to display something // fallback to display something
if (tabContexts.length === 0) { notePath = 'root';
await openEmptyTab();
}
return;
} }
const noteId = treeUtils.getNoteIdFromNotePath(notePath); const noteId = treeUtils.getNoteIdFromNotePath(notePath);

View File

@@ -47,6 +47,7 @@ class NoteDetailText {
this.ctx = ctx; this.ctx = ctx;
this.$component = ctx.$tabContent.find('.note-detail-text'); this.$component = ctx.$tabContent.find('.note-detail-text');
this.$editorEl = this.$component.find('.note-detail-text-editor'); this.$editorEl = this.$component.find('.note-detail-text-editor');
this.textEditorPromise = null;
this.textEditor = null; this.textEditor = null;
this.$component.on("dblclick", "img", e => { this.$component.on("dblclick", "img", e => {
@@ -67,7 +68,23 @@ class NoteDetailText {
} }
async render() { async render() {
if (!this.textEditor) { if (!this.textEditorPromise) {
this.textEditorPromise = this.initEditor();
}
await this.textEditorPromise;
// lazy loading above can take time and tab might have been already switched to another note
if (this.ctx.note && this.ctx.note.type === 'text') {
this.textEditor.isReadOnly = await this.isReadOnly();
this.$component.show();
this.textEditor.setData(this.ctx.note.content);
}
}
async initEditor() {
await libraryLoader.requireLibrary(libraryLoader.CKEDITOR); await libraryLoader.requireLibrary(libraryLoader.CKEDITOR);
const codeBlockLanguages = const codeBlockLanguages =
@@ -85,10 +102,7 @@ class NoteDetailText {
// display of $component in both branches. // display of $component in both branches.
this.$component.show(); this.$component.show();
// textEditor might have been initialized during previous await so checking again const textEditorInstance = await BalloonEditor.create(this.$editorEl[0], {
// looks like double initialization can freeze CKEditor pretty badly
if (!this.textEditor) {
this.textEditor = await BalloonEditor.create(this.$editorEl[0], {
placeholder: "Type the content of your note here ...", placeholder: "Type the content of your note here ...",
mention: mentionSetup, mention: mentionSetup,
codeBlock: { codeBlock: {
@@ -98,22 +112,13 @@ class NoteDetailText {
if (glob.isDev && ENABLE_INSPECTOR) { if (glob.isDev && ENABLE_INSPECTOR) {
await import('../../libraries/ckeditor/inspector.js'); await import('../../libraries/ckeditor/inspector.js');
CKEditorInspector.attach(this.textEditor); CKEditorInspector.attach(textEditorInstance);
} }
this.textEditor = textEditorInstance;
this.onNoteChange(() => this.ctx.noteChanged()); this.onNoteChange(() => this.ctx.noteChanged());
} }
}
// lazy loading above can take time and tab might have been already switched to another note
if (this.ctx.note && this.ctx.note.type === 'text') {
this.textEditor.isReadOnly = await this.isReadOnly();
this.$component.show();
this.textEditor.setData(this.ctx.note.content);
}
}
getContent() { getContent() {
const content = this.textEditor.getData(); const content = this.textEditor.getData();

View File

@@ -42,6 +42,8 @@ async function setupProtectedSession(password) {
return; return;
} }
$("#container").addClass('protected-session-active');
protectedSessionHolder.setProtectedSessionId(response.protectedSessionId); protectedSessionHolder.setProtectedSessionId(response.protectedSessionId);
protectedSessionHolder.touchProtectedSession(); protectedSessionHolder.touchProtectedSession();

View File

@@ -3,15 +3,16 @@ import optionsService from "./options.js";
export async function initSpellCheck() { export async function initSpellCheck() {
const options = await optionsService.waitForOptions(); const options = await optionsService.waitForOptions();
if (!options.is('spellCheckEnabled')) {
return;
}
const {SpellCheckHandler, ContextMenuListener, ContextMenuBuilder} = require('electron-spellchecker'); const {SpellCheckHandler, ContextMenuListener, ContextMenuBuilder} = require('electron-spellchecker');
const {remote, shell} = require('electron'); const {remote, shell} = require('electron');
const spellCheckHandler = new SpellCheckHandler(); const spellCheckHandler = new SpellCheckHandler();
// not fully disabling the spellcheck since we want to preserve the context menu
// this will just get rid of the "red squiggles"
if (options.is('spellCheckEnabled')) {
spellCheckHandler.attachToInput(); spellCheckHandler.attachToInput();
}
spellCheckHandler.switchLanguage(options.get('spellCheckLanguageCode')); spellCheckHandler.switchLanguage(options.get('spellCheckLanguageCode'));

View File

@@ -350,8 +350,6 @@ class TabContext {
this.$savedIndicator.fadeIn(); this.$savedIndicator.fadeIn();
this.$scriptArea.empty();
// run async // run async
bundleService.executeRelationBundles(this.note, 'runOnNoteChange', this); bundleService.executeRelationBundles(this.note, 'runOnNoteChange', this);

View File

@@ -110,7 +110,7 @@ body {
#context-menu-container { #context-menu-container {
max-height: 100vh; max-height: 100vh;
overflow: auto; /* make it scrollable when exceeding total height of the window */ /* !!! Cannot set overflow: auto, submenus will break !!! */
} }
#context-menu-container, #context-menu-container .dropdown-menu { #context-menu-container, #context-menu-container .dropdown-menu {

View File

@@ -1 +1 @@
module.exports = { buildDate:"2020-01-04T22:01:20+01:00", buildRevision: "3b8b4da149fbc1b17d09253693823f5135a55f2e" }; module.exports = { buildDate:"2020-01-18T20:52:14+01:00", buildRevision: "0ec11d29ba2d524ff80b49ab8dc40f3f8cf3835b" };

View File

@@ -18,18 +18,6 @@ class ConsistencyChecks {
this.fixedIssues = false; this.fixedIssues = false;
} }
async findIssues(query, errorCb) {
const results = await sql.getRows(query);
for (const res of results) {
logError(errorCb(res));
this.unrecoveredConsistencyErrors = true;
}
return results;
}
async findAndFixIssues(query, fixerCb) { async findAndFixIssues(query, fixerCb) {
const results = await sql.getRows(query); const results = await sql.getRows(query);
@@ -175,13 +163,6 @@ class ConsistencyChecks {
logError(`Relation ${attributeId} references missing note ${noteId}`) logError(`Relation ${attributeId} references missing note ${noteId}`)
} }
}); });
await this.findIssues(`
SELECT noteRevisionId, note_revisions.noteId
FROM note_revisions
LEFT JOIN notes USING (noteId)
WHERE notes.noteId IS NULL`,
({noteRevisionId, noteId}) => `Note revision ${noteRevisionId} references missing note ${noteId}`);
} }
async findExistencyIssues() { async findExistencyIssues() {
@@ -335,13 +316,22 @@ class ConsistencyChecks {
} }
}); });
await this.findIssues(` await this.findAndFixIssues(`
SELECT noteId SELECT noteId
FROM notes FROM notes
JOIN note_contents USING (noteId) JOIN note_contents USING (noteId)
WHERE isErased = 1 WHERE isErased = 1
AND content IS NOT NULL`, AND content IS NOT NULL`,
({noteId}) => `Note ${noteId} content is not null even though the note is erased`); async ({noteId}) => {
if (this.autoFix) {
await sql.execute(`UPDATE note_contents SET content = NULL WHERE noteId = ?`, [noteId]);
logFix(`Note ${noteId} content has been set to null since the note is erased`);
}
else {
logError(`Note ${noteId} content is not null even though the note is erased`);
}
});
await this.findAndFixIssues(` await this.findAndFixIssues(`
SELECT noteId, noteRevisionId SELECT noteId, noteRevisionId
@@ -398,20 +388,40 @@ class ConsistencyChecks {
} }
}); });
await this.findIssues(` await this.findAndFixIssues(`
SELECT noteRevisionId SELECT noteRevisionId
FROM note_revisions FROM note_revisions
JOIN note_revision_contents USING (noteRevisionId) JOIN note_revision_contents USING (noteRevisionId)
WHERE isErased = 1 WHERE isErased = 1
AND content IS NOT NULL`, AND content IS NOT NULL`,
({noteRevisionId}) => `Note revision ${noteRevisionId} content is not null even though the note revision is erased`); async ({noteRevisionId}) => {
if (this.autoFix) {
await sql.execute(`UPDATE note_revision_contents SET content = NULL WHERE noteRevisionId = ?`, [noteRevisionId]);
await this.findIssues(` logFix(`Note revision ${noteRevisionId} content was set to null since the note revision is erased`);
}
else {
logError(`Note revision ${noteRevisionId} content is not null even though the note revision is erased`);
}
});
await this.findAndFixIssues(`
SELECT noteId SELECT noteId
FROM notes FROM notes
WHERE isErased = 1 WHERE isErased = 1
AND isDeleted = 0`, AND isDeleted = 0`,
({noteId}) => `Note ${noteId} is not deleted even though it is erased`); async ({noteId}) => {
if (this.autoFix) {
const note = await repository.getNote(noteId);
note.isDeleted = true;
await note.save();
logFix(`Note ${noteId} was set to deleted since it is erased`);
}
else {
logError(`Note ${noteId} is not deleted even though it is erased`);
}
});
await this.findAndFixIssues(` await this.findAndFixIssues(`
SELECT parentNoteId SELECT parentNoteId

View File

@@ -40,7 +40,9 @@ async function getRootCalendarNote() {
parentNoteId: 'root', parentNoteId: 'root',
title: 'Calendar', title: 'Calendar',
target: 'into', target: 'into',
isProtected: false isProtected: false,
type: 'text',
content: ''
})).note; })).note;
await attributeService.createLabel(rootNote.noteId, CALENDAR_ROOT_LABEL); await attributeService.createLabel(rootNote.noteId, CALENDAR_ROOT_LABEL);

View File

@@ -147,7 +147,7 @@ async function importTar(taskContext, fileBuffer, importRootNote) {
continue; continue;
} }
if (attr.type === 'relation' && ['internalLink', 'imageLink', 'relationMapLink'].includes(attr.name)) { if (attr.type === 'relation' && ['internalLink', 'imageLink', 'relationMapLink', 'includeNoteLink'].includes(attr.name)) {
// these relations are created automatically and as such don't need to be duplicated in the import // these relations are created automatically and as such don't need to be duplicated in the import
continue; continue;
} }

View File

@@ -242,6 +242,20 @@ function findInternalLinks(content, foundLinks) {
return content.replace(/href="[^"]*#root/g, 'href="#root'); return content.replace(/href="[^"]*#root/g, 'href="#root');
} }
function findIncludeNoteLinks(content, foundLinks) {
const re = /<section class="include-note" data-note-id="([a-zA-Z0-9]+)">/g;
let match;
while (match = re.exec(content)) {
foundLinks.push({
name: 'includeNoteLink',
value: match[1]
});
}
return content;
}
function findRelationMapLinks(content, foundLinks) { function findRelationMapLinks(content, foundLinks) {
const obj = JSON.parse(content); const obj = JSON.parse(content);
@@ -267,6 +281,7 @@ async function saveLinks(note, content) {
if (note.type === 'text') { if (note.type === 'text') {
content = findImageLinks(content, foundLinks); content = findImageLinks(content, foundLinks);
content = findInternalLinks(content, foundLinks); content = findInternalLinks(content, foundLinks);
content = findIncludeNoteLinks(content, foundLinks);
} }
else if (note.type === 'relation-map') { else if (note.type === 'relation-map') {
findRelationMapLinks(content, foundLinks); findRelationMapLinks(content, foundLinks);
@@ -490,6 +505,8 @@ async function eraseDeletedNotes() {
SET isErased = 1, SET isErased = 1,
title = NULL title = NULL
WHERE isErased = 0 AND noteId IN (???)`, noteIdsToErase); WHERE isErased = 0 AND noteId IN (???)`, noteIdsToErase);
log.info(`Erased notes: ${JSON.stringify(noteIdsToErase)}`);
} }
async function duplicateNote(noteId, parentNoteId) { async function duplicateNote(noteId, parentNoteId) {

View File

@@ -1,6 +1,6 @@
const dayjs = require("dayjs"); const dayjs = require("dayjs");
const filterRegex = /(\b(AND|OR)\s+)?@(!?)([\p{L}\p{Number}_]+|"[^"]+")\s*((=|!=|<|<=|>|>=|!?\*=|!?=\*|!?\*=\*)\s*(\S+|"[^"]+"))?/igu; const filterRegex = /(\b(AND|OR)\s+)?@(!?)([\p{L}\p{Number}_]+|"[^"]+")\s*((=|!=|<|<=|>|>=|!?\*=|!?=\*|!?\*=\*)\s*([^\s=*]+|"[^"]+"))?/igu;
const smartValueRegex = /^(NOW|TODAY|WEEK|MONTH|YEAR) *([+\-] *\d+)?$/i; const smartValueRegex = /^(NOW|TODAY|WEEK|MONTH|YEAR) *([+\-] *\d+)?$/i;
function calculateSmartValue(v) { function calculateSmartValue(v) {

View File

@@ -71,22 +71,36 @@ function sendMessageToAllClients(message) {
} }
} }
async function fillInAdditionalProperties(sync) {
// fill in some extra data needed by the frontend
if (sync.entityName === 'attributes') {
sync.noteId = await sql.getValue(`SELECT noteId
FROM attributes
WHERE attributeId = ?`, [sync.entityId]);
} else if (sync.entityName === 'note_revisions') {
sync.noteId = await sql.getValue(`SELECT noteId
FROM note_revisions
WHERE noteRevisionId = ?`, [sync.entityId]);
} else if (sync.entityName === 'branches') {
const {noteId, parentNoteId} = await sql.getRow(`SELECT noteId, parentNoteId
FROM branches
WHERE branchId = ?`, [sync.entityId]);
sync.noteId = noteId;
sync.parentNoteId = parentNoteId;
}
}
async function sendPing(client) { async function sendPing(client) {
const syncData = require('./sync_table').getEntitySyncsNewerThan(lastAcceptedSyncIds[client.id]); const syncData = require('./sync_table').getEntitySyncsNewerThan(lastAcceptedSyncIds[client.id]);
for (const sync of syncData) { for (const sync of syncData) {
// fill in some extra data needed by the frontend try {
if (sync.entityName === 'attributes') { await fillInAdditionalProperties(sync);
sync.noteId = await sql.getValue(`SELECT noteId FROM attributes WHERE attributeId = ?`, [sync.entityId]);
} }
else if (sync.entityName === 'note_revisions') { catch (e) {
sync.noteId = await sql.getValue(`SELECT noteId FROM note_revisions WHERE noteRevisionId = ?`, [sync.entityId]); log.error("Could not fill additional properties for sync " + JSON.stringify(sync)
} + " because of error: " + e.message + ": " + e.stack);
else if (sync.entityName === 'branches') {
const {noteId, parentNoteId} = await sql.getRow(`SELECT noteId, parentNoteId FROM branches WHERE branchId = ?`, [sync.entityId]);
sync.noteId = noteId;
sync.parentNoteId = parentNoteId;
} }
} }

View File

@@ -9,7 +9,7 @@
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<table class="table"> <table class="table table-borderless">
<tr> <tr>
<th>Homepage:</th> <th>Homepage:</th>
<td><a href="https://github.com/zadam/trilium" class="external">https://github.com/zadam/trilium</a></td> <td><a href="https://github.com/zadam/trilium" class="external">https://github.com/zadam/trilium</a></td>