Compare commits

..

29 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
zadam f0dfe7d552 release 0.39.4 2020-01-04 22:01:20 +01:00
zadam 3b8b4da149 task context progress fixes 2020-01-04 21:59:28 +01:00
zadam 2150619d62 activateNewNote does not reload whole tree anymore 2020-01-04 21:53:49 +01:00
zadam acb76e0630 added notification to empty book notes otherwise they look suspiciously empty 2020-01-04 21:24:39 +01:00
zadam fdb46f9329 fallback image saving without optimization will still compress image 2020-01-04 20:10:30 +01:00
zadam ca587cccf6 book note type has automatically empty mime type 2020-01-04 19:34:01 +01:00
zadam 571772069a force SQLite to choose particular index for attribute search since it sometimes led to very inefficient query plans 2020-01-04 19:22:16 +01:00
zadam 79e7762c72 indent data notes 2020-01-04 18:44:54 +01:00
zadam d025cfee1b empty note with just included note should be saved, closes #807 2020-01-04 13:22:07 +01:00
zadam 7793552443 fix display of show sidebar button 2020-01-04 10:05:03 +01:00
zadam f377a84fa1 hide/show button z-index tweak 2020-01-04 09:21:42 +01:00
zadam b8f2797abf better behavior of note info widget in tight width 2020-01-04 09:04:08 +01:00
zadam 50431dd55a hide "show/hide sidebar" button in zen mode, fixes #805 2020-01-03 20:12:19 +01:00
zadam 1d3608b7bf fix recent note dialog for deleted notes 2020-01-03 09:04:38 +01:00
zadam d0c655f66a fix removeAttribute to take into account attribute name, closes #804 2020-01-03 08:55:13 +01:00
zadam ac75fd2ca3 focus and activate should work together 2020-01-02 19:07:50 +01:00
zadam 3b98428c8c change order of execution to mitigate race conditions 2020-01-02 19:03:54 +01:00
39 changed files with 583 additions and 434 deletions
+2 -1
View File
@@ -2,4 +2,5 @@
/workspace.xml
# Datasource local storage ignored files
/dataSources.local.xml
/dataSources.local.xml
/dataSources/
BIN
View File
Binary file not shown.
+1 -1
View File
@@ -553,7 +553,7 @@ class Note extends Entity {
const attributes = await this.loadOwnedAttributesToCache();
for (const attribute of attributes) {
if (attribute.type === type &amp;&amp; (value === undefined || value === attribute.value)) {
if (attribute.type === type &amp;&amp; attribute.name === name &amp;&amp; (value === undefined || value === attribute.value)) {
attribute.isDeleted = true;
await attribute.save();
@@ -232,7 +232,7 @@ function BackendScriptApi(currentNote, apiParams) {
this.createDataNote = async (parentNoteId, title, content = {}) => await noteService.createNewNote({
parentNoteId,
title,
content: JSON.stringify(content),
content: JSON.stringify(content, null, '\t'),
type: 'code',
mime: 'application/json'
});
+264 -14
View File
@@ -807,7 +807,7 @@
<div class="description">
Activates newly created note. Compared to this.activateNote() also refreshes tree.
Activates newly created note. Compared to this.activateNote() also makes sure that frontend has been fully synced.
</div>
@@ -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>
<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>
@@ -1785,7 +1922,7 @@
<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#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>
@@ -1891,7 +2028,7 @@
<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#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>
@@ -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>
@@ -2050,7 +2300,7 @@
<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#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>
@@ -2312,7 +2562,7 @@ if some action needs to happen on only one specific instance.
<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#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>
@@ -2775,7 +3025,7 @@ otherwise (by e.g. createNoteLink())
<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#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>
@@ -2930,7 +3180,7 @@ otherwise (by e.g. createNoteLink())
<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#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>
@@ -3039,7 +3289,7 @@ note.
<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#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>
@@ -3194,7 +3444,7 @@ note.
<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#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>
@@ -3433,7 +3683,7 @@ note.
<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#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>
@@ -4606,7 +4856,7 @@ Internally this serializes the anonymous function into string and sends it to ba
<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#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>
@@ -4757,7 +5007,7 @@ Internally this serializes the anonymous function into string and sends it to ba
<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#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>
@@ -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>
<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>
@@ -86,13 +86,13 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, tabConte
};
/**
* Activates newly created note. Compared to this.activateNote() also refreshes tree.
* Activates newly created note. Compared to this.activateNote() also makes sure that frontend has been fully synced.
*
* @param {string} notePath (or noteId)
* @return {Promise&lt;void>}
*/
this.activateNewNote = async notePath => {
await treeService.reload();
await ws.waitForMaxKnownSyncId();
await treeService.activateNote(notePath, noteDetailService.focusAndSelectTitle);
};
@@ -324,12 +324,28 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, tabConte
*/
this.createNoteLink = linkService.createNoteLink;
/**
* Adds given text to the editor cursor
*
* @param {string} text
* @method
*/
this.addTextToActiveTabEditor = linkService.addTextToEditor;
/**
* @method
* @returns {NoteFull} active note (loaded into right pane)
*/
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
* @returns {Promise&lt;string|null>} returns note path of active note or null if there isn't active note
+41 -269
View File
@@ -1,6 +1,6 @@
{
"name": "trilium",
"version": "0.39.0-beta",
"version": "0.39.5",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -722,29 +722,29 @@
"integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg=="
},
"app-builder-bin": {
"version": "3.4.4",
"resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-3.4.4.tgz",
"integrity": "sha512-Xib+wgdK+8zZhbZr5pma3pNB23Y4JRY5Yt6h8peou6MTFSQzXdIkqalh/ezy9SMLuS43S4b0s7jTVAmUs8WVmA==",
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-3.4.3.tgz",
"integrity": "sha512-qMhayIwi3juerQEVJMQ76trObEbfQT0nhUdxZz9a26/3NLT3pE6awmQ8S1cEnrGugaaM5gYqR8OElcDezfmEsg==",
"dev": true
},
"app-builder-lib": {
"version": "22.1.0",
"resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-22.1.0.tgz",
"integrity": "sha512-jDTfWsVS/MePO4FexqiSQcsWM9Yfr81ETIYbmVbKmW05o0dn9k1DvMOMoLb0kTLQpW+pWBVvGMAOPfk68HnBrg==",
"version": "21.2.0",
"resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-21.2.0.tgz",
"integrity": "sha512-aOX/nv77/Bti6NymJDg7p9T067xD8m1ipIEJR7B4Mm1GsJWpMm9PZdXtCRiMNRjHtQS5KIljT0g17781y6qn5A==",
"dev": true,
"requires": {
"7zip-bin": "~5.0.3",
"@develar/schema-utils": "~2.1.0",
"async-exit-hook": "^2.0.1",
"bluebird-lst": "^1.0.9",
"builder-util": "22.1.0",
"builder-util-runtime": "8.4.0",
"builder-util": "21.2.0",
"builder-util-runtime": "8.3.0",
"chromium-pickle-js": "^0.2.0",
"debug": "^4.1.1",
"ejs": "^2.7.1",
"electron-publish": "22.1.0",
"ejs": "^2.6.2",
"electron-publish": "21.2.0",
"fs-extra": "^8.1.0",
"hosted-git-info": "^3.0.0",
"hosted-git-info": "^2.7.1",
"is-ci": "^2.0.0",
"isbinaryfile": "^4.0.2",
"js-yaml": "^3.13.1",
@@ -752,26 +752,11 @@
"minimatch": "^3.0.4",
"normalize-package-data": "^2.5.0",
"read-config-file": "5.0.0",
"sanitize-filename": "^1.6.3",
"sanitize-filename": "^1.6.2",
"semver": "^6.3.0",
"temp-file": "^3.3.4"
},
"dependencies": {
"ejs": {
"version": "2.7.4",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.4.tgz",
"integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==",
"dev": true
},
"hosted-git-info": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.2.tgz",
"integrity": "sha512-ezZMWtHXm7Eb7Rq4Mwnx2vs79WUx2QmRg3+ZqeGroKzfDO+EprOcgRPYghsOP9JuYBfK18VojmRTGCg8Ma+ktw==",
"dev": true,
"requires": {
"lru-cache": "^5.1.1"
}
},
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
@@ -1381,16 +1366,16 @@
}
},
"builder-util": {
"version": "22.1.0",
"resolved": "https://registry.npmjs.org/builder-util/-/builder-util-22.1.0.tgz",
"integrity": "sha512-BPvpWvxQ5XOzm2WepIgmOAyo2IyaM/Bd1LJmeTYy5CtknNAtxgmAPQJfCHCikMKKQA4Obz/KYecXQiGpGJ2ThA==",
"version": "21.2.0",
"resolved": "https://registry.npmjs.org/builder-util/-/builder-util-21.2.0.tgz",
"integrity": "sha512-Nd6CUb6YgDY8EXAXEIegx+1kzKqyFQ5ZM5BoYkeunAlwz/zDJoH1UCyULjoS5wQe5czNClFQy07zz2bzYD0Z4A==",
"dev": true,
"requires": {
"7zip-bin": "~5.0.3",
"@types/debug": "^4.1.5",
"app-builder-bin": "3.4.4",
"@types/debug": "^4.1.4",
"app-builder-bin": "3.4.3",
"bluebird-lst": "^1.0.9",
"builder-util-runtime": "8.4.0",
"builder-util-runtime": "8.3.0",
"chalk": "^2.4.2",
"debug": "^4.1.1",
"fs-extra": "^8.1.0",
@@ -1439,9 +1424,9 @@
}
},
"builder-util-runtime": {
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-8.4.0.tgz",
"integrity": "sha512-CJB/eKfPf2vHrkmirF5eicVnbDCkMBbwd5tRYlTlgud16zFeqD7QmrVUAOEXdnsrcNkiLg9dbuUsQKtl/AwsYQ==",
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-8.3.0.tgz",
"integrity": "sha512-CSOdsYqf4RXIHh1HANPbrZHlZ9JQJXSuDDloblZPcWQVN62inyYoTQuSmY3KrgefME2Sv3Kn2MxHvbGQHRf8Iw==",
"dev": true,
"requires": {
"debug": "^4.1.1",
@@ -2424,122 +2409,6 @@
"sanitize-filename": "^1.6.2"
},
"dependencies": {
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "^1.9.0"
}
},
"app-builder-bin": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-3.4.3.tgz",
"integrity": "sha512-qMhayIwi3juerQEVJMQ76trObEbfQT0nhUdxZz9a26/3NLT3pE6awmQ8S1cEnrGugaaM5gYqR8OElcDezfmEsg==",
"dev": true
},
"app-builder-lib": {
"version": "21.2.0",
"resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-21.2.0.tgz",
"integrity": "sha512-aOX/nv77/Bti6NymJDg7p9T067xD8m1ipIEJR7B4Mm1GsJWpMm9PZdXtCRiMNRjHtQS5KIljT0g17781y6qn5A==",
"dev": true,
"requires": {
"7zip-bin": "~5.0.3",
"@develar/schema-utils": "~2.1.0",
"async-exit-hook": "^2.0.1",
"bluebird-lst": "^1.0.9",
"builder-util": "21.2.0",
"builder-util-runtime": "8.3.0",
"chromium-pickle-js": "^0.2.0",
"debug": "^4.1.1",
"ejs": "^2.6.2",
"electron-publish": "21.2.0",
"fs-extra": "^8.1.0",
"hosted-git-info": "^2.7.1",
"is-ci": "^2.0.0",
"isbinaryfile": "^4.0.2",
"js-yaml": "^3.13.1",
"lazy-val": "^1.0.4",
"minimatch": "^3.0.4",
"normalize-package-data": "^2.5.0",
"read-config-file": "5.0.0",
"sanitize-filename": "^1.6.2",
"semver": "^6.3.0",
"temp-file": "^3.3.4"
},
"dependencies": {
"ejs": {
"version": "2.7.4",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.4.tgz",
"integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==",
"dev": true
},
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"dev": true
}
}
},
"builder-util": {
"version": "21.2.0",
"resolved": "https://registry.npmjs.org/builder-util/-/builder-util-21.2.0.tgz",
"integrity": "sha512-Nd6CUb6YgDY8EXAXEIegx+1kzKqyFQ5ZM5BoYkeunAlwz/zDJoH1UCyULjoS5wQe5czNClFQy07zz2bzYD0Z4A==",
"dev": true,
"requires": {
"7zip-bin": "~5.0.3",
"@types/debug": "^4.1.4",
"app-builder-bin": "3.4.3",
"bluebird-lst": "^1.0.9",
"builder-util-runtime": "8.3.0",
"chalk": "^2.4.2",
"debug": "^4.1.1",
"fs-extra": "^8.1.0",
"is-ci": "^2.0.0",
"js-yaml": "^3.13.1",
"source-map-support": "^0.5.13",
"stat-mode": "^0.3.0",
"temp-file": "^3.3.4"
}
},
"builder-util-runtime": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-8.3.0.tgz",
"integrity": "sha512-CSOdsYqf4RXIHh1HANPbrZHlZ9JQJXSuDDloblZPcWQVN62inyYoTQuSmY3KrgefME2Sv3Kn2MxHvbGQHRf8Iw==",
"dev": true,
"requires": {
"debug": "^4.1.1",
"sax": "^1.2.4"
}
},
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
}
},
"electron-publish": {
"version": "21.2.0",
"resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-21.2.0.tgz",
"integrity": "sha512-mWavuoWJe87iaeKd0I24dNWIaR+0yRzshjNVqGyK019H766fsPWl3caQJnVKFaEyrZRP397v4JZVG0e7s16AxA==",
"dev": true,
"requires": {
"bluebird-lst": "^1.0.9",
"builder-util": "~21.2.0",
"builder-util-runtime": "8.3.0",
"chalk": "^2.4.2",
"fs-extra": "^8.1.0",
"lazy-val": "^1.0.4",
"mime": "^2.4.4"
}
},
"iconv-lite": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.0.tgz",
@@ -2548,27 +2417,6 @@
"requires": {
"safer-buffer": ">= 2.1.2 < 3"
}
},
"mime": {
"version": "2.4.4",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz",
"integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==",
"dev": true
},
"stat-mode": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-0.3.0.tgz",
"integrity": "sha512-QjMLR0A3WwFY2aZdV0okfFEJB5TRjkggXZjxP3A1RsWsNHNu3YPv8btmtc6iCFZ0Rul3FE93OYogvhOUClU+ng==",
"dev": true
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}
}
}
},
@@ -2748,32 +2596,26 @@
}
},
"electron-builder": {
"version": "22.1.0",
"resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-22.1.0.tgz",
"integrity": "sha512-uu2W9BLG38D0i2PG6dHupmOYc+q/TRL+Ztf8xitqK+2Quq33PFbeN0ipfySuVEDg4I6whDRBOgxBEWwnUYqZZQ==",
"version": "21.2.0",
"resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-21.2.0.tgz",
"integrity": "sha512-x8EXrqFbAb2L3N22YlGar3dGh8vwptbB3ovo3OF6K7NTpcsmM2zEoJv7GhFyX73rNzSG2HaWpXwGAtOp2JWiEw==",
"dev": true,
"requires": {
"app-builder-lib": "22.1.0",
"app-builder-lib": "21.2.0",
"bluebird-lst": "^1.0.9",
"builder-util": "22.1.0",
"builder-util-runtime": "8.4.0",
"builder-util": "21.2.0",
"builder-util-runtime": "8.3.0",
"chalk": "^2.4.2",
"dmg-builder": "21.2.0",
"fs-extra": "^8.1.0",
"is-ci": "^2.0.0",
"lazy-val": "^1.0.4",
"read-config-file": "5.0.0",
"sanitize-filename": "^1.6.3",
"sanitize-filename": "^1.6.2",
"update-notifier": "^3.0.1",
"yargs": "^14.0.0"
"yargs": "^13.3.0"
},
"dependencies": {
"ansi-regex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
"dev": true
},
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
@@ -2783,12 +2625,6 @@
"color-convert": "^1.9.0"
}
},
"camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"dev": true
},
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
@@ -2800,41 +2636,6 @@
"supports-color": "^5.3.0"
}
},
"find-up": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
"integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
"dev": true,
"requires": {
"locate-path": "^3.0.0"
}
},
"is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
"dev": true
},
"string-width": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
"dev": true,
"requires": {
"emoji-regex": "^7.0.1",
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^5.1.0"
}
},
"strip-ansi": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"dev": true,
"requires": {
"ansi-regex": "^4.1.0"
}
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
@@ -2843,35 +2644,6 @@
"requires": {
"has-flag": "^3.0.0"
}
},
"yargs": {
"version": "14.2.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-14.2.0.tgz",
"integrity": "sha512-/is78VKbKs70bVZH7w4YaZea6xcJWOAwkhbR0CFuZBmYtfTYF0xjGJF43AYd8g2Uii1yJwmS5GR2vBmrc32sbg==",
"dev": true,
"requires": {
"cliui": "^5.0.0",
"decamelize": "^1.2.0",
"find-up": "^3.0.0",
"get-caller-file": "^2.0.1",
"require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^3.0.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
"yargs-parser": "^15.0.0"
}
},
"yargs-parser": {
"version": "15.0.0",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-15.0.0.tgz",
"integrity": "sha512-xLTUnCMc4JhxrPEPUYD5IBR1mWCK/aT6+RJ/K29JY2y1vD+FhtgKK0AXRWvI262q3QSffAQuTouFIKUuHX89wQ==",
"dev": true,
"requires": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
}
}
}
},
@@ -3191,14 +2963,14 @@
}
},
"electron-publish": {
"version": "22.1.0",
"resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-22.1.0.tgz",
"integrity": "sha512-jHjMCaL2dFU+iOq8wW568F59+DW1jFJGT3vc2xqm9iXyZ8gWlQ+NVve4bq9HZG7m4iNqWbGw9StmZcOzmIBxMQ==",
"version": "21.2.0",
"resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-21.2.0.tgz",
"integrity": "sha512-mWavuoWJe87iaeKd0I24dNWIaR+0yRzshjNVqGyK019H766fsPWl3caQJnVKFaEyrZRP397v4JZVG0e7s16AxA==",
"dev": true,
"requires": {
"bluebird-lst": "^1.0.9",
"builder-util": "~22.1.0",
"builder-util-runtime": "8.4.0",
"builder-util": "~21.2.0",
"builder-util-runtime": "8.3.0",
"chalk": "^2.4.2",
"fs-extra": "^8.1.0",
"lazy-val": "^1.0.4",
@@ -5449,9 +5221,9 @@
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
},
"isbinaryfile": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.2.tgz",
"integrity": "sha512-C3FSxJdNrEr2F4z6uFtNzECDM5hXk+46fxaa+cwBe5/XrWSmzdG8DDgyjfX6/NRdBB21q2JXuRAzPCUs+fclnQ==",
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.3.tgz",
"integrity": "sha512-GQ9Gjhp3AsEbo8/L/pA+MYl/c4hRm5O/+uCkF4LMx1a556Wh4/d75H13qu9LldmhU4yKnlfNKBmEcCaze3b2Gw==",
"dev": true
},
"isexe": {
@@ -9600,9 +9372,9 @@
"integrity": "sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0="
},
"temp-file": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.3.4.tgz",
"integrity": "sha512-qSZ5W5q54iyGnP8cNl49RE0jTJc5CrzNocux5APD5yIxcgonoMuMSbsZfaZy8rTGCYo0Xz6ySVv3adagZ8gffg==",
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.3.6.tgz",
"integrity": "sha512-7TPldi8QJqRlPIF/Y33mVvo8+xDfi6+aVTCK4CrCaLqCoaOnVtf3SA4hCU0T5nhYDdOC7erw7o2uWfvijlk4Ug==",
"dev": true,
"requires": {
"async-exit-hook": "^2.0.1",
+2 -2
View File
@@ -2,7 +2,7 @@
"name": "trilium",
"productName": "Trilium Notes",
"description": "Trilium Notes",
"version": "0.39.3",
"version": "0.39.6",
"license": "AGPL-3.0-only",
"main": "electron.js",
"bin": {
@@ -78,7 +78,7 @@
},
"devDependencies": {
"electron": "6.0.12",
"electron-builder": "22.1.0",
"electron-builder": "21.2.0",
"electron-packager": "14.1.1",
"electron-rebuild": "1.8.8",
"jsdoc": "3.6.3",
+2 -2
View File
@@ -525,7 +525,7 @@ class Note extends Entity {
const attributes = await this.loadOwnedAttributesToCache();
for (const attribute of attributes) {
if (attribute.type === type && (value === undefined || value === attribute.value)) {
if (attribute.type === type && attribute.name === name && (value === undefined || value === attribute.value)) {
attribute.isDeleted = true;
await attribute.save();
@@ -805,7 +805,7 @@ class Note extends Entity {
WHERE noteId = ? AND
isDeleted = 0 AND
type = 'relation' AND
name IN ('internalLink', 'imageLink', 'relationMapLink')`, [this.noteId]);
name IN ('internalLink', 'imageLink', 'relationMapLink', 'includeNoteLink')`, [this.noteId]);
}
/**
@@ -6,7 +6,7 @@ const TPL = `
<div>
<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">
<input type="checkbox" class="custom-control-input" id="spell-check-enabled">
@@ -12,7 +12,7 @@ const TPL = `
<div class="form-group">
<label for="sync-server-timeout">Sync timeout (milliseconds)</label>
<input class="form-control" id="sync-server-timeout" min="1" max="10000000" type="number">
<input class="form-control" id="sync-server-timeout" min="1" max="10000000" type="number" style="text-align: left;">
</div>
<div class="form-group">
@@ -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
const clickPosition = event.pageY;
const clientHeight = document.documentElement.clientHeight;
const contextMenuHeight = $contextMenuContainer.height();
const contextMenuHeight = $contextMenuContainer.outerHeight() + 30;
let top;
if (clickPosition + contextMenuHeight > clientHeight) {
@@ -58,13 +58,13 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, tabConte
};
/**
* Activates newly created note. Compared to this.activateNote() also refreshes tree.
* Activates newly created note. Compared to this.activateNote() also makes sure that frontend has been fully synced.
*
* @param {string} notePath (or noteId)
* @return {Promise<void>}
*/
this.activateNewNote = async notePath => {
await treeService.reload();
await ws.waitForMaxKnownSyncId();
await treeService.activateNote(notePath, noteDetailService.focusAndSelectTitle);
};
@@ -296,12 +296,28 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, tabConte
*/
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
* @returns {NoteFull} active note (loaded into right pane)
*/
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
* @returns {Promise<string|null>} returns note path of active note or null if there isn't active note
+7 -5
View File
@@ -35,13 +35,15 @@ async function createNoteLink(notePath, options = {}) {
if (showNotePath) {
notePath = await treeService.resolveNotePath(notePath);
const noteIds = notePath.split("/");
noteIds.pop(); // remove last element
if (notePath) {
const noteIds = notePath.split("/");
noteIds.pop(); // remove last element
const parentNotePath = noteIds.join("/").trim();
const parentNotePath = noteIds.join("/").trim();
if (parentNotePath) {
$container.append($("<small>").text(" (" + await treeUtils.getNotePathTitle(parentNotePath) + ")"));
if (parentNotePath) {
$container.append($("<small>").text(" (" + await treeUtils.getNotePathTitle(parentNotePath) + ")"));
}
}
}
@@ -201,17 +201,13 @@ async function loadNoteDetail(origNotePath, options = {}) {
const newTab = !!options.newTab;
const activate = !!options.activate;
const notePath = await treeService.resolveNotePath(origNotePath);
let notePath = await treeService.resolveNotePath(origNotePath);
if (!notePath) {
console.error(`Cannot resolve note path ${origNotePath}`);
// fallback to display something
if (tabContexts.length === 0) {
await openEmptyTab();
}
return;
notePath = 'root';
}
const noteId = treeUtils.getNoteIdFromNotePath(notePath);
@@ -43,6 +43,7 @@ class NoteDetailBook {
this.$zoomInButton = this.$component.find('.book-zoom-in-button');
this.$zoomOutButton = this.$component.find('.book-zoom-out-button');
this.$expandChildrenButton = this.$component.find('.expand-children-button');
this.$help = this.$component.find('.note-detail-book-help');
this.$zoomInButton.on('click', () => this.setZoom(this.zoomLevel - 1));
this.$zoomOutButton.on('click', () => this.setZoom(this.zoomLevel + 1));
@@ -105,6 +106,7 @@ class NoteDetailBook {
async render() {
this.$content.empty();
this.$help.hide();
if (this.isAutoBook()) {
const $addTextLink = $('<a href="javascript:">here</a>').on('click', () => {
@@ -124,7 +126,9 @@ class NoteDetailBook {
}
async renderIntoElement(note, $container) {
for (const childNote of await note.getChildNotes()) {
const childNotes = await note.getChildNotes();
for (const childNote of childNotes) {
const childNotePath = this.ctx.notePath + '/' + childNote.noteId;
const {type, renderedContent} = await noteContentRenderer.getRenderedContent(childNote);
@@ -152,6 +156,10 @@ class NoteDetailBook {
$container.append($card);
}
if (childNotes.length === 0) {
this.$help.show();
}
}
/** @return {boolean} true if this is "auto book" activated (empty text note) and not explicit book note */
@@ -47,6 +47,7 @@ class NoteDetailText {
this.ctx = ctx;
this.$component = ctx.$tabContent.find('.note-detail-text');
this.$editorEl = this.$component.find('.note-detail-text-editor');
this.textEditorPromise = null;
this.textEditor = null;
this.$component.on("dblclick", "img", e => {
@@ -67,44 +68,12 @@ class NoteDetailText {
}
async render() {
if (!this.textEditor) {
await libraryLoader.requireLibrary(libraryLoader.CKEDITOR);
const codeBlockLanguages =
(await mimeTypesService.getMimeTypes())
.filter(mt => mt.enabled)
.map(mt => {
return {
language: mt.mime.toLowerCase().replace(/[\W_]+/g,"-"),
label: mt.title
}
});
// CKEditor since version 12 needs the element to be visible before initialization. At the same time
// we want to avoid flicker - i.e. show editor only once everything is ready. That's why we have separate
// display of $component in both branches.
this.$component.show();
// textEditor might have been initialized during previous await so checking again
// 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 ...",
mention: mentionSetup,
codeBlock: {
languages: codeBlockLanguages
}
});
if (glob.isDev && ENABLE_INSPECTOR) {
await import('../../libraries/ckeditor/inspector.js');
CKEditorInspector.attach(this.textEditor);
}
this.onNoteChange(() => this.ctx.noteChanged());
}
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();
@@ -115,16 +84,56 @@ class NoteDetailText {
}
}
async initEditor() {
await libraryLoader.requireLibrary(libraryLoader.CKEDITOR);
const codeBlockLanguages =
(await mimeTypesService.getMimeTypes())
.filter(mt => mt.enabled)
.map(mt => {
return {
language: mt.mime.toLowerCase().replace(/[\W_]+/g,"-"),
label: mt.title
}
});
// CKEditor since version 12 needs the element to be visible before initialization. At the same time
// we want to avoid flicker - i.e. show editor only once everything is ready. That's why we have separate
// display of $component in both branches.
this.$component.show();
const textEditorInstance = await BalloonEditor.create(this.$editorEl[0], {
placeholder: "Type the content of your note here ...",
mention: mentionSetup,
codeBlock: {
languages: codeBlockLanguages
}
});
if (glob.isDev && ENABLE_INSPECTOR) {
await import('../../libraries/ckeditor/inspector.js');
CKEditorInspector.attach(textEditorInstance);
}
this.textEditor = textEditorInstance;
this.onNoteChange(() => this.ctx.noteChanged());
}
getContent() {
let content = this.textEditor.getData();
const content = this.textEditor.getData();
// if content is only tags/whitespace (typically <p>&nbsp;</p>), then just make it empty
// this is important when setting new note to code
if (jQuery(content).text().trim() === '' && !content.includes("<img")) {
content = '';
}
return this.isContentEmpty(content) ? '' : content;
}
return content;
isContentEmpty(content) {
content = content.toLowerCase();
return jQuery(content).text().trim() === ''
&& !content.includes("<img")
&& !content.includes("<section")
}
async isReadOnly() {
@@ -42,6 +42,8 @@ async function setupProtectedSession(password) {
return;
}
$("#container").addClass('protected-session-active');
protectedSessionHolder.setProtectedSessionId(response.protectedSessionId);
protectedSessionHolder.touchProtectedSession();
@@ -3,15 +3,16 @@ import optionsService from "./options.js";
export async function initSpellCheck() {
const options = await optionsService.waitForOptions();
if (!options.is('spellCheckEnabled')) {
return;
}
const {SpellCheckHandler, ContextMenuListener, ContextMenuBuilder} = require('electron-spellchecker');
const {remote, shell} = require('electron');
const spellCheckHandler = new SpellCheckHandler();
spellCheckHandler.attachToInput();
// 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.switchLanguage(options.get('spellCheckLanguageCode'));
@@ -162,9 +162,6 @@ class TabContext {
this.noteChangeDisabled = false;
}
// after loading new note make sure editor is scrolled to the top
this.getComponent().scrollToTop();
this.setTitleBar();
this.cleanup(); // esp. on windows autocomplete is not getting closed automatically
@@ -186,6 +183,9 @@ class TabContext {
}
bundleService.executeRelationBundles(this.note, 'runOnNoteView', this);
// after loading new note make sure editor is scrolled to the top
this.getComponent().scrollToTop();
}
async show() {
@@ -350,8 +350,6 @@ class TabContext {
this.$savedIndicator.fadeIn();
this.$scriptArea.empty();
// run async
bundleService.executeRelationBundles(this.note, 'runOnNoteChange', this);
+3 -3
View File
@@ -191,9 +191,7 @@ async function activateNote(notePath, noteLoadedListener) {
noteDetailService.addDetailLoadedListener(node.data.noteId, noteLoadedListener);
}
// we use noFocus because when we reload the tree because of background changes
// we don't want the reload event to steal focus from whatever was focused before
await node.setActive(true, { noFocus: true });
await node.setActive(true);
clearSelectedNodes();
@@ -318,6 +316,8 @@ async function getSomeNotePath(note) {
cur = parents[0];
}
path.push('root');
return path.reverse().join('/');
}
+5 -1
View File
@@ -213,7 +213,11 @@ function closeActiveDialog() {
}
function isHtmlEmpty(html) {
return $("<div>").html(html).text().trim().length === 0 && !html.toLowerCase().includes('<img');
html = html.toLowerCase();
return $("<div>").html(html).text().trim().length === 0
&& !html.includes('<img')
&& !html.includes('<section');
}
async function clearBrowserCache() {
+19 -10
View File
@@ -1,18 +1,18 @@
import StandardWidget from "./standard_widget.js";
const TPL = `
<table class="note-info-table">
<table class="note-info-table" style="table-layout: fixed; width: 100%;">
<tr>
<th>Note ID:</th>
<td colspan="3" class="note-info-note-id"></td>
<th nowrap>Note ID:</th>
<td nowrap colspan="3" class="note-info-note-id"></td>
</tr>
<tr>
<th>Created:</th>
<td colspan="3" class="note-info-date-created"></td>
<th nowrap>Created:</th>
<td nowrap colspan="3" style="overflow: hidden; text-overflow: ellipsis;" class="note-info-date-created"></td>
</tr>
<tr>
<th>Modified:</th>
<td colspan="3" class="note-info-date-modified"></td>
<th nowrap>Modified:</th>
<td nowrap colspan="3" style="overflow: hidden; text-overflow: ellipsis;" class="note-info-date-modified"></td>
</tr>
<tr>
<th>Type:</th>
@@ -39,10 +39,19 @@ class NoteInfoWidget extends StandardWidget {
const note = this.ctx.note;
$noteId.text(note.noteId);
$dateCreated.text(note.dateCreated);
$dateModified.text(note.dateModified);
$dateCreated
.text(note.dateCreated)
.attr("title", note.dateCreated);
$dateModified
.text(note.dateModified)
.attr("title", note.dateCreated);
$type.text(note.type);
$mime.text(note.mime).attr("title", note.mime);
$mime
.text(note.mime)
.attr("title", note.mime);
}
eventReceived(name, data) {
+2 -2
View File
@@ -110,7 +110,7 @@ body {
#context-menu-container {
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 {
@@ -407,7 +407,7 @@ body {
position: fixed;
bottom: 10px;
right: 10px;
z-index: 100000;
z-index: 1000;
}
#right-pane {
+11 -1
View File
@@ -594,7 +594,7 @@ table.promoted-attributes-in-tooltip td, table.promoted-attributes-in-tooltip th
padding: 10px;
}
.note-detail-render-help {
.note-detail-render-help, .note-detail-book-help {
margin: 50px;
padding: 20px;
}
@@ -967,4 +967,14 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href
padding: 20px;
border-radius: 10px;
background-color: var(--accented-background-color);
}
.include-note.ck-placeholder::before { /* remove placeholder in otherwise empty note */
content: '' !important;
}
.alert-warning {
color: var(--main-text-color) !important;
background-color: var(--accented-background-color) !important;
border-color: var(--main-border-color) !important;
}
+1 -1
View File
@@ -204,7 +204,7 @@ function BackendScriptApi(currentNote, apiParams) {
this.createDataNote = async (parentNoteId, title, content = {}) => await noteService.createNewNote({
parentNoteId,
title,
content: JSON.stringify(content),
content: JSON.stringify(content, null, '\t'),
type: 'code',
mime: 'application/json'
});
+1 -1
View File
@@ -1 +1 @@
module.exports = { buildDate:"2020-01-02T10:43:41+01:00", buildRevision: "cb79f2c7eb51904537307f4ffc1135a7383da29f" };
module.exports = { buildDate:"2020-01-18T20:52:14+01:00", buildRevision: "0ec11d29ba2d524ff80b49ab8dc40f3f8cf3835b" };
+2 -1
View File
@@ -31,7 +31,8 @@ module.exports = function(filters, selectedColumns = 'notes.*') {
// can match notes because @tag can be both "shopping" and "christmas"
const alias = "attr_" + property + "_" + attrFilterId++;
joins[alias] = `LEFT JOIN attributes AS ${alias} `
// forcing to use particular index since SQLite query planner would often choose something pretty bad
joins[alias] = `LEFT JOIN attributes AS ${alias} INDEXED BY IDX_attributes_noteId_index `
+ `ON ${alias}.noteId = notes.noteId `
+ `AND ${alias}.name = '${property}' AND ${alias}.isDeleted = 0`;
+35 -25
View File
@@ -18,18 +18,6 @@ class ConsistencyChecks {
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) {
const results = await sql.getRows(query);
@@ -175,13 +163,6 @@ class ConsistencyChecks {
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() {
@@ -335,13 +316,22 @@ class ConsistencyChecks {
}
});
await this.findIssues(`
await this.findAndFixIssues(`
SELECT noteId
FROM notes
JOIN note_contents USING (noteId)
WHERE isErased = 1
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(`
SELECT noteId, noteRevisionId
@@ -398,20 +388,40 @@ class ConsistencyChecks {
}
});
await this.findIssues(`
await this.findAndFixIssues(`
SELECT noteRevisionId
FROM note_revisions
JOIN note_revision_contents USING (noteRevisionId)
WHERE isErased = 1
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
FROM notes
WHERE isErased = 1
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(`
SELECT parentNoteId
+3 -1
View File
@@ -40,7 +40,9 @@ async function getRootCalendarNote() {
parentNoteId: 'root',
title: 'Calendar',
target: 'into',
isProtected: false
isProtected: false,
type: 'text',
content: ''
})).note;
await attributeService.createLabel(rootNote.noteId, CALENDAR_ROOT_LABEL);
+10 -8
View File
@@ -75,14 +75,17 @@ async function saveImage(parentNoteId, uploadBuffer, originalName, shrinkImageSw
}
async function shrinkImage(buffer, originalName) {
const resizedImage = await resize(buffer);
// we do resizing with max (100) quality which will be trimmed during optimization step next
const resizedImage = await resize(buffer, 100);
let finalImageBuffer;
const jpegQuality = await optionService.getOptionInt('imageJpegQuality');
try {
finalImageBuffer = await optimize(resizedImage);
finalImageBuffer = await optimize(resizedImage, jpegQuality);
} catch (e) {
log.error("Failed to optimize image '" + originalName + "'\nStack: " + e.stack);
finalImageBuffer = resizedImage;
finalImageBuffer = await resize(buffer, jpegQuality);
}
// if resizing & shrinking did not help with size then save the original
@@ -94,7 +97,7 @@ async function shrinkImage(buffer, originalName) {
return finalImageBuffer;
}
async function resize(buffer) {
async function resize(buffer, quality) {
const imageMaxWidthHeight = await optionService.getOptionInt('imageMaxWidthHeight');
const image = await jimp.read(buffer);
@@ -106,8 +109,7 @@ async function resize(buffer) {
image.resize(jimp.AUTO, imageMaxWidthHeight);
}
// we do resizing with max quality which will be trimmed during optimization step next
image.quality(100);
image.quality(quality);
// when converting PNG to JPG we lose alpha channel, this is replaced by white to match Trilium white background
image.background(0xFFFFFFFF);
@@ -115,11 +117,11 @@ async function resize(buffer) {
return image.getBufferAsync(jimp.MIME_JPEG);
}
async function optimize(buffer) {
async function optimize(buffer, jpegQuality) {
return await imagemin.buffer(buffer, {
plugins: [
imageminMozJpeg({
quality: await optionService.getOptionInt('imageJpegQuality')
quality: jpegQuality
}),
imageminPngQuant({
quality: [0, 0.7]
+1 -1
View File
@@ -147,7 +147,7 @@ async function importTar(taskContext, fileBuffer, importRootNote) {
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
continue;
}
+18 -1
View File
@@ -48,7 +48,7 @@ function deriveMime(type, mime) {
mime = 'text/plain';
} else if (['relation-map', 'search'].includes(type)) {
mime = 'application/json';
} else if (type === 'render') {
} else if (['render', 'book'].includes(type)) {
mime = '';
}
@@ -242,6 +242,20 @@ function findInternalLinks(content, foundLinks) {
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) {
const obj = JSON.parse(content);
@@ -267,6 +281,7 @@ async function saveLinks(note, content) {
if (note.type === 'text') {
content = findImageLinks(content, foundLinks);
content = findInternalLinks(content, foundLinks);
content = findIncludeNoteLinks(content, foundLinks);
}
else if (note.type === 'relation-map') {
findRelationMapLinks(content, foundLinks);
@@ -490,6 +505,8 @@ async function eraseDeletedNotes() {
SET isErased = 1,
title = NULL
WHERE isErased = 0 AND noteId IN (???)`, noteIdsToErase);
log.info(`Erased notes: ${JSON.stringify(noteIdsToErase)}`);
}
async function duplicateNote(noteId, parentNoteId) {
+1 -1
View File
@@ -1,6 +1,6 @@
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;
function calculateSmartValue(v) {
+8 -2
View File
@@ -12,8 +12,14 @@ class TaskContext {
this.data = data;
// progressCount is meant to represent just some progress - to indicate the task is not stuck
this.progressCount = 0;
this.lastSentCountTs = Date.now();
this.progressCount = -1; // we're incrementing immediatelly
this.lastSentCountTs = 0; // 0 will guarantee first message will be sent
// just the fact this has been initialized is a progress which should be sent to clients
// this is esp. important when importing big files/images which take long time to upload/process
// which means that first "real" increaseProgressCount() will be called quite late and user is without
// feedback until then
this.increaseProgressCount();
}
/** @return {TaskContext} */
+25 -11
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) {
const syncData = require('./sync_table').getEntitySyncsNewerThan(lastAcceptedSyncIds[client.id]);
for (const sync of syncData) {
// 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]);
try {
await fillInAdditionalProperties(sync);
}
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;
catch (e) {
log.error("Could not fill additional properties for sync " + JSON.stringify(sync)
+ " because of error: " + e.message + ": " + e.stack);
}
}
+4
View File
@@ -13,5 +13,9 @@
title="Zoom Out"></button>
</div>
<div class="note-detail-book-help alert alert-warning">
This note of type Book doesn't have any child notes so there's nothing to display. See <a href="https://github.com/zadam/trilium/wiki/Book-note">wiki</a> for details.
</div>
<div class="note-detail-book-content"></div>
</div>
+1 -1
View File
@@ -9,7 +9,7 @@
</button>
</div>
<div class="modal-body">
<table class="table">
<table class="table table-borderless">
<tr>
<th>Homepage:</th>
<td><a href="https://github.com/zadam/trilium" class="external">https://github.com/zadam/trilium</a></td>
+2 -2
View File
@@ -1,5 +1,5 @@
<button id="hide-sidebar-button" class="btn btn-sm icon-button bx bx-chevrons-right" title="Hide sidebar"></button>
<button id="show-sidebar-button" class="btn btn-sm icon-button bx bx-chevrons-left" title="Show sidebar"></button>
<button id="hide-sidebar-button" class="btn btn-sm icon-button bx bx-chevrons-right hide-in-zen-mode" title="Hide sidebar"></button>
<button id="show-sidebar-button" class="btn btn-sm icon-button bx bx-chevrons-left hide-in-zen-mode" title="Show sidebar"></button>
<div id="right-pane" class="hide-in-zen-mode">
<div id="sidebar-container"></div>