mirror of
https://github.com/zadam/trilium.git
synced 2025-10-27 08:16:40 +01:00
Compare commits
21 Commits
v0.46.1-be
...
v0.46.2-be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
19821b634f | ||
|
|
cde41b268e | ||
|
|
cb6d35236c | ||
|
|
7572ee284b | ||
|
|
d0eaf623a8 | ||
|
|
25c2db6c3a | ||
|
|
5a173ff14e | ||
|
|
7fab75b085 | ||
|
|
0f065536d0 | ||
|
|
d0747abded | ||
|
|
f62b4a581e | ||
|
|
dcf1c62ec1 | ||
|
|
93d55b3e7b | ||
|
|
90c6852423 | ||
|
|
208baa56e9 | ||
|
|
ddf8438b22 | ||
|
|
6008dc891f | ||
|
|
cc887a00f2 | ||
|
|
fbbd51d0b1 | ||
|
|
081b8b126a | ||
|
|
7a6bb81345 |
50
package-lock.json
generated
50
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "trilium",
|
||||
"version": "0.46.0-beta",
|
||||
"version": "0.46.1-beta",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -1263,9 +1263,9 @@
|
||||
}
|
||||
},
|
||||
"async-mutex": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.3.0.tgz",
|
||||
"integrity": "sha512-6VIpUM7s37EMXvnO3TvujgaS6gx4yJby13BhxovMYSap7nrbS0gJ1UzGcjD+HElNSdTz/+IlAIqj7H48N0ZlyQ==",
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.3.1.tgz",
|
||||
"integrity": "sha512-vRfQwcqBnJTLzVQo72Sf7KIUbcSUP5hNchx6udI1U6LuPQpfePgdjJzlCe76yFZ8pxlLjn9lwcl/Ya0TSOv0Tw==",
|
||||
"requires": {
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
@@ -1769,9 +1769,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"caniuse-lite": {
|
||||
"version": "1.0.30001187",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001187.tgz",
|
||||
"integrity": "sha512-w7/EP1JRZ9552CyrThUnay2RkZ1DXxKe/Q2swTC4+LElLh9RRYrL1Z+27LlakB8kzY0fSmHw9mc7XYDUKAKWMA==",
|
||||
"version": "1.0.30001191",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001191.tgz",
|
||||
"integrity": "sha512-xJJqzyd+7GCJXkcoBiQ1GuxEiOBCLQ0aVW9HMekifZsAVGdj5eJ4mFB9fEhSHipq9IOk/QXFJUiIr9lZT+EsGw==",
|
||||
"dev": true
|
||||
},
|
||||
"caseless": {
|
||||
@@ -3252,9 +3252,9 @@
|
||||
}
|
||||
},
|
||||
"electron-to-chromium": {
|
||||
"version": "1.3.667",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.667.tgz",
|
||||
"integrity": "sha512-Ot1pPtAVb5nd7jeVF651zmfLFilRVFomlDzwXmdlWe5jyzOGa6mVsQ06XnAurT7wWfg5VEIY+LopbAdD/bpo5w==",
|
||||
"version": "1.3.673",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.673.tgz",
|
||||
"integrity": "sha512-ms+QR2ckfrrpEAjXweLx6kNCbpAl66DcW//3BZD4BV5KhUgr0RZRce1ON/9J3QyA3JO28nzgb5Xv8DnPr05ILg==",
|
||||
"dev": true
|
||||
},
|
||||
"electron-window-state": {
|
||||
@@ -3346,9 +3346,9 @@
|
||||
}
|
||||
},
|
||||
"es-module-lexer": {
|
||||
"version": "0.3.26",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.3.26.tgz",
|
||||
"integrity": "sha512-Va0Q/xqtrss45hWzP8CZJwzGSZJjDM5/MJRE3IXXnUCcVLElR9BRaE9F62BopysASyc4nM3uwhSW7FFB9nlWAA==",
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.4.0.tgz",
|
||||
"integrity": "sha512-iuEGihqqhKWFgh72Q/Jtch7V2t/ft8w8IPP2aEN8ArYKO+IWyo6hsi96hCdgyeEDQIV3InhYQ9BlwUFPGXrbEQ==",
|
||||
"dev": true
|
||||
},
|
||||
"es6-error": {
|
||||
@@ -4638,6 +4638,11 @@
|
||||
"regenerator-runtime": "^0.13.3"
|
||||
}
|
||||
},
|
||||
"joplin-turndown-plugin-gfm": {
|
||||
"version": "1.0.12",
|
||||
"resolved": "https://registry.npmjs.org/joplin-turndown-plugin-gfm/-/joplin-turndown-plugin-gfm-1.0.12.tgz",
|
||||
"integrity": "sha512-qL4+1iycQjZ1fs8zk3jSRk7cg3ROBUHk7GKtiLAQLFzLPKErnILUvz5DLszSQvz3s1sTjPbywLDISVUtBY6HaA=="
|
||||
},
|
||||
"jpeg-js": {
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.2.tgz",
|
||||
@@ -5466,9 +5471,9 @@
|
||||
}
|
||||
},
|
||||
"node-releases": {
|
||||
"version": "1.1.70",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.70.tgz",
|
||||
"integrity": "sha512-Slf2s69+2/uAD79pVVQo8uSiC34+g8GWY8UH2Qtqv34ZfhYrxpYpfzs9Js9d6O0mbDmALuxaTlplnBTnSELcrw==",
|
||||
"version": "1.1.71",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.71.tgz",
|
||||
"integrity": "sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg==",
|
||||
"dev": true
|
||||
},
|
||||
"noop-logger": {
|
||||
@@ -7379,11 +7384,6 @@
|
||||
"domino": "^2.1.6"
|
||||
}
|
||||
},
|
||||
"turndown-plugin-gfm": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/turndown-plugin-gfm/-/turndown-plugin-gfm-1.0.2.tgz",
|
||||
"integrity": "sha512-vwz9tfvF7XN/jE0dGoBei3FXWuvll78ohzCZQuOb+ZjWrs3a0XhQVomJEb2Qh4VHTPNRO4GPZh0V7VRbiWwkRg=="
|
||||
},
|
||||
"tweetnacl": {
|
||||
"version": "0.14.5",
|
||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
|
||||
@@ -7662,9 +7662,9 @@
|
||||
"integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w=="
|
||||
},
|
||||
"webpack": {
|
||||
"version": "5.23.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.23.0.tgz",
|
||||
"integrity": "sha512-RC6dwDuRxiU75F8XC4H08NtzUrMfufw5LDnO8dTtaKU2+fszEdySCgZhNwSBBn516iNaJbQI7T7OPHIgCwcJmg==",
|
||||
"version": "5.24.2",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.24.2.tgz",
|
||||
"integrity": "sha512-uxxKYEY4kMNjP+D2Y+8aw5Vd7ar4pMuKCNemxV26ysr1nk0YDiQTylg9U3VZIdkmI0YHa0uC8ABxL+uGxGWWJg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/eslint-scope": "^3.7.0",
|
||||
@@ -7676,7 +7676,7 @@
|
||||
"browserslist": "^4.14.5",
|
||||
"chrome-trace-event": "^1.0.2",
|
||||
"enhanced-resolve": "^5.7.0",
|
||||
"es-module-lexer": "^0.3.26",
|
||||
"es-module-lexer": "^0.4.0",
|
||||
"eslint-scope": "^5.1.1",
|
||||
"events": "^3.2.0",
|
||||
"glob-to-regexp": "^0.4.1",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "trilium",
|
||||
"productName": "Trilium Notes",
|
||||
"description": "Trilium Notes",
|
||||
"version": "0.46.1-beta",
|
||||
"version": "0.46.2-beta",
|
||||
"license": "AGPL-3.0-only",
|
||||
"main": "electron.js",
|
||||
"bin": {
|
||||
@@ -24,7 +24,7 @@
|
||||
"test-all": "npm run test && npm run test-es6"
|
||||
},
|
||||
"dependencies": {
|
||||
"async-mutex": "0.3.0",
|
||||
"async-mutex": "0.3.1",
|
||||
"axios": "0.21.1",
|
||||
"better-sqlite3": "7.1.2",
|
||||
"body-parser": "1.19.0",
|
||||
@@ -70,7 +70,7 @@
|
||||
"striptags": "3.1.1",
|
||||
"tmp": "^0.2.1",
|
||||
"turndown": "7.0.0",
|
||||
"turndown-plugin-gfm": "1.0.2",
|
||||
"joplin-turndown-plugin-gfm": "1.0.12",
|
||||
"unescape": "1.0.1",
|
||||
"ws": "7.4.3",
|
||||
"yauzl": "2.10.0",
|
||||
@@ -87,7 +87,7 @@
|
||||
"jsdoc": "3.6.6",
|
||||
"lorem-ipsum": "2.0.3",
|
||||
"rcedit": "3.0.0",
|
||||
"webpack": "5.23.0",
|
||||
"webpack": "5.24.2",
|
||||
"webpack-cli": "4.5.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
|
||||
@@ -681,7 +681,7 @@ class Note extends Entity {
|
||||
* Update's given relation's value or creates it if it doesn't exist
|
||||
*
|
||||
* @param {string} name - relation name
|
||||
* @param {string} [value] - relation value (noteId)
|
||||
* @param {string} value - relation value (noteId)
|
||||
*/
|
||||
setRelation(name, value) { return this.setAttribute(RELATION, name, value); }
|
||||
|
||||
|
||||
@@ -129,7 +129,7 @@ ws.subscribeToMessages(async message => {
|
||||
toastService.showPersistent(makeToast(message.taskId, "Export in progress: " + message.progressCount));
|
||||
}
|
||||
else if (message.type === 'task-succeeded') {
|
||||
const toast = makeToast(message.taskId, "Import finished successfully.");
|
||||
const toast = makeToast(message.taskId, "Export finished successfully.");
|
||||
toast.closeAfter = 5000;
|
||||
|
||||
toastService.showPersistent(toast);
|
||||
|
||||
@@ -29,7 +29,7 @@ const TPL = `
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>Zooming can be controlled with CTRL-+ and CTRL-= shortcuts as well.</p>
|
||||
<p>Zooming can be controlled with CTRL+- and CTRL+= shortcuts as well.</p>
|
||||
|
||||
<h4>Font sizes</h4>
|
||||
|
||||
@@ -169,4 +169,4 @@ export default class ApperanceOptions {
|
||||
this.$body.get(0).style.setProperty("--tree-font-size", this.$treeFontSize.val() + "%");
|
||||
this.$body.get(0).style.setProperty("--detail-font-size", this.$detailFontSize.val() + "%");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,6 +158,26 @@ class NoteShort {
|
||||
return this.treeCache.getNotesFromCache(this.parents);
|
||||
}
|
||||
|
||||
// will sort the parents so that non-search & non-archived are first and archived at the end
|
||||
// this is done so that non-search & non-archived paths are always explored as first when looking for note path
|
||||
resortParents() {
|
||||
this.parents.sort((aNoteId, bNoteId) => {
|
||||
const aBranchId = this.parentToBranch[aNoteId];
|
||||
|
||||
if (aBranchId && aBranchId.startsWith('virt-')) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const aNote = this.treeCache.getNoteFromCache([aNoteId]);
|
||||
|
||||
if (aNote.hasLabel('archived')) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return -1;
|
||||
});
|
||||
}
|
||||
|
||||
/** @returns {string[]} */
|
||||
getChildNoteIds() {
|
||||
return this.children;
|
||||
|
||||
@@ -18,6 +18,8 @@ async function getTodayNote() {
|
||||
async function getDateNote(date) {
|
||||
const note = await server.get('date-notes/date/' + date, "date-note");
|
||||
|
||||
await ws.waitForMaxKnownEntityChangeId();
|
||||
|
||||
return await treeCache.getNote(note.noteId);
|
||||
}
|
||||
|
||||
@@ -25,6 +27,8 @@ async function getDateNote(date) {
|
||||
async function getMonthNote(month) {
|
||||
const note = await server.get('date-notes/month/' + month, "date-note");
|
||||
|
||||
await ws.waitForMaxKnownEntityChangeId();
|
||||
|
||||
return await treeCache.getNote(note.noteId);
|
||||
}
|
||||
|
||||
@@ -32,6 +36,8 @@ async function getMonthNote(month) {
|
||||
async function getYearNote(year) {
|
||||
const note = await server.get('date-notes/year/' + year, "date-note");
|
||||
|
||||
await ws.waitForMaxKnownEntityChangeId();
|
||||
|
||||
return await treeCache.getNote(note.noteId);
|
||||
}
|
||||
|
||||
@@ -39,21 +45,14 @@ async function getYearNote(year) {
|
||||
async function createSqlConsole() {
|
||||
const note = await server.post('sql-console');
|
||||
|
||||
await ws.waitForMaxKnownEntityChangeId();
|
||||
|
||||
return await treeCache.getNote(note.noteId);
|
||||
}
|
||||
|
||||
/** @return {NoteShort} */
|
||||
async function createSearchNote(opts = {}) {
|
||||
const note = await server.post('search-note');
|
||||
|
||||
const attrsToUpdate = [
|
||||
opts.ancestorNoteId ? { type: 'relation', name: 'ancestor', value: opts.ancestorNoteId } : undefined,
|
||||
{ type: 'label', name: 'searchString', value: opts.searchString }
|
||||
].filter(attr => !!attr);
|
||||
|
||||
if (attrsToUpdate.length > 0) {
|
||||
await server.put(`notes/${note.noteId}/attributes`, attrsToUpdate);
|
||||
}
|
||||
const note = await server.post('search-note', opts);
|
||||
|
||||
await ws.waitForMaxKnownEntityChangeId();
|
||||
|
||||
|
||||
@@ -166,8 +166,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
|
||||
}, "script");
|
||||
|
||||
if (ret.success) {
|
||||
// wait until all the changes done in the script has been synced to frontend before continuing
|
||||
await ws.waitForEntityChangeId(ret.maxEntityChangeId);
|
||||
await ws.waitForMaxKnownEntityChangeId();
|
||||
|
||||
return ret.executionResult;
|
||||
}
|
||||
|
||||
@@ -56,6 +56,14 @@ const TPL = `
|
||||
|
||||
.note-book-title {
|
||||
margin-bottom: 0;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* not-expanded title is limited to one line only */
|
||||
.note-book-card:not(.expanded) .note-book-title {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.note-book-title .rendered-note-attributes {
|
||||
@@ -148,7 +156,7 @@ class NoteListRenderer {
|
||||
/*
|
||||
* We're using noteIds so that it's not necessary to load all notes at once when paging
|
||||
*/
|
||||
constructor($parent, parentNote, noteIds) {
|
||||
constructor($parent, parentNote, noteIds, showNotePath = false) {
|
||||
this.$noteList = $(TPL);
|
||||
|
||||
// note list must be added to the DOM immediatelly, otherwise some functionality scripting (canvas) won't work
|
||||
@@ -200,6 +208,8 @@ class NoteListRenderer {
|
||||
|
||||
await this.renderList();
|
||||
});
|
||||
|
||||
this.showNotePath = showNotePath;
|
||||
}
|
||||
|
||||
/** @return {Set<string>} list of noteIds included (images, included notes) into a parent note and which
|
||||
@@ -298,7 +308,7 @@ class NoteListRenderer {
|
||||
.append(
|
||||
$('<h5 class="note-book-title">')
|
||||
.append($expander)
|
||||
.append(await linkService.createNoteLink(note.noteId, {showTooltip: false}))
|
||||
.append(await linkService.createNoteLink(note.noteId, {showTooltip: false, showNotePath: this.showNotePath}))
|
||||
.append($renderedAttributes)
|
||||
);
|
||||
|
||||
|
||||
@@ -56,6 +56,8 @@ async function resolveNotePathToSegments(notePath, logErrors = true) {
|
||||
return;
|
||||
}
|
||||
|
||||
child.resortParents();
|
||||
|
||||
const parents = child.getParentNotes();
|
||||
|
||||
if (!parents.length) {
|
||||
|
||||
@@ -193,6 +193,10 @@ function getNoteTypeClass(type) {
|
||||
}
|
||||
|
||||
function getMimeTypeClass(mime) {
|
||||
if (!mime) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const semicolonIdx = mime.indexOf(';');
|
||||
|
||||
if (semicolonIdx !== -1) {
|
||||
@@ -296,9 +300,13 @@ function dynamicRequire(moduleName) {
|
||||
}
|
||||
}
|
||||
|
||||
function timeLimit(promise, limitMs) {
|
||||
function timeLimit(promise, limitMs, errorMessage) {
|
||||
if (!promise || !promise.then) { // it's not actually a promise
|
||||
return promise;
|
||||
}
|
||||
|
||||
// better stack trace if created outside of promise
|
||||
const error = new Error('Process exceeded time limit ' + limitMs);
|
||||
const error = new Error(errorMessage || `Process exceeded time limit ${limitMs}`);
|
||||
|
||||
return new Promise((res, rej) => {
|
||||
let resolved = false;
|
||||
|
||||
@@ -43,7 +43,6 @@ const processedEntityChangeIds = new Set();
|
||||
function logRows(entityChanges) {
|
||||
const filteredRows = entityChanges.filter(row =>
|
||||
!processedEntityChangeIds.has(row.id)
|
||||
&& row.entityName !== 'recent_notes'
|
||||
&& (row.entityName !== 'options' || row.entityId !== 'openTabs'));
|
||||
|
||||
if (filteredRows.length > 0) {
|
||||
@@ -103,7 +102,7 @@ function waitForEntityChangeId(desiredEntityChangeId) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
console.debug("Waiting for", desiredEntityChangeId, 'current is', lastProcessedEntityChangeId);
|
||||
console.debug(`Waiting for ${desiredEntityChangeId}, last processed is ${lastProcessedEntityChangeId}, last accepted ${lastAcceptedEntityChangeId}`);
|
||||
|
||||
return new Promise((res, rej) => {
|
||||
entityChangeIdReachedListeners.push({
|
||||
@@ -127,7 +126,7 @@ function checkEntityChangeIdListeners() {
|
||||
.filter(l => l.desiredEntityChangeId > lastProcessedEntityChangeId);
|
||||
|
||||
entityChangeIdReachedListeners.filter(l => Date.now() > l.start - 60000)
|
||||
.forEach(l => console.log(`Waiting for entityChangeId ${l.desiredEntityChangeId} while current is ${lastProcessedEntityChangeId} for ${Math.floor((Date.now() - l.start) / 1000)}s`));
|
||||
.forEach(l => console.log(`Waiting for entityChangeId ${l.desiredEntityChangeId} while last processed is ${lastProcessedEntityChangeId} (last accepted ${lastAcceptedEntityChangeId}) for ${Math.floor((Date.now() - l.start) / 1000)}s`));
|
||||
}
|
||||
|
||||
async function runSafely(syncHandler, syncData) {
|
||||
@@ -230,25 +229,6 @@ subscribeToMessages(message => {
|
||||
});
|
||||
|
||||
async function processEntityChanges(entityChanges) {
|
||||
const missingNoteIds = [];
|
||||
|
||||
for (const {entityName, entity} of entityChanges) {
|
||||
if (entityName === 'branches' && !(entity.parentNoteId in treeCache.notes)) {
|
||||
missingNoteIds.push(entity.parentNoteId);
|
||||
}
|
||||
else if (entityName === 'attributes'
|
||||
&& entity.type === 'relation'
|
||||
&& entity.name === 'template'
|
||||
&& !(entity.noteId in treeCache.notes)) {
|
||||
|
||||
missingNoteIds.push(entity.value);
|
||||
}
|
||||
}
|
||||
|
||||
if (missingNoteIds.length > 0) {
|
||||
await treeCache.reloadNotes(missingNoteIds);
|
||||
}
|
||||
|
||||
const loadResults = new LoadResults(treeCache);
|
||||
|
||||
for (const ec of entityChanges.filter(ec => ec.entityName === 'notes')) {
|
||||
@@ -391,6 +371,25 @@ async function processEntityChanges(entityChanges) {
|
||||
loadResults.addOption(ec.entity.name);
|
||||
}
|
||||
|
||||
const missingNoteIds = [];
|
||||
|
||||
for (const {entityName, entity} of entityChanges) {
|
||||
if (entityName === 'branches' && !(entity.parentNoteId in treeCache.notes)) {
|
||||
missingNoteIds.push(entity.parentNoteId);
|
||||
}
|
||||
else if (entityName === 'attributes'
|
||||
&& entity.type === 'relation'
|
||||
&& entity.name === 'template'
|
||||
&& !(entity.value in treeCache.notes)) {
|
||||
|
||||
missingNoteIds.push(entity.value);
|
||||
}
|
||||
}
|
||||
|
||||
if (missingNoteIds.length > 0) {
|
||||
await treeCache.reloadNotes(missingNoteIds);
|
||||
}
|
||||
|
||||
if (!loadResults.isEmpty()) {
|
||||
if (loadResults.hasAttributeRelatedChanges()) {
|
||||
noteAttributeCache.invalidate();
|
||||
|
||||
@@ -12,6 +12,10 @@ const TPL = `
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="no-edited-notes-found">No edited notes on this day yet ...</div>
|
||||
|
||||
<div class="edited-notes-list"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -31,18 +35,20 @@ export default class EditedNotesWidget extends CollapsibleWidget {
|
||||
|
||||
async doRenderBody() {
|
||||
this.$body.html(TPL);
|
||||
this.$editedNotes = this.$body.find('.edited-notes-widget');
|
||||
this.$list = this.$body.find('.edited-notes-list');
|
||||
this.$noneFound = this.$body.find('.no-edited-notes-found');
|
||||
}
|
||||
|
||||
async refreshWithNote(note) {
|
||||
// remember which title was when we found the similar notes
|
||||
this.title = note.title;
|
||||
let editedNotes = await server.get('edited-notes/' + note.getLabelValue("dateNote"));
|
||||
|
||||
editedNotes = editedNotes.filter(n => n.noteId !== note.noteId);
|
||||
|
||||
this.$list.empty();
|
||||
this.$noneFound.hide();
|
||||
|
||||
if (editedNotes.length === 0) {
|
||||
this.$body.text("No edited notes on this day yet ...");
|
||||
this.$noneFound.show();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -50,8 +56,6 @@ export default class EditedNotesWidget extends CollapsibleWidget {
|
||||
|
||||
await treeCache.getNotes(noteIds, true); // preload all at once
|
||||
|
||||
const $list = $('<div>'); // not using <ul> because it's difficult to style correctly with text-overflow
|
||||
|
||||
for (const editedNote of editedNotes) {
|
||||
const $item = $('<div class="edited-note-line">');
|
||||
|
||||
@@ -67,9 +71,7 @@ export default class EditedNotesWidget extends CollapsibleWidget {
|
||||
$item.append(editedNote.notePath ? await linkService.createNoteLink(editedNote.notePath.join("/"), {showNotePath: true}) : editedNote.title);
|
||||
}
|
||||
|
||||
$list.append($item);
|
||||
this.$list.append($item);
|
||||
}
|
||||
|
||||
this.$editedNotes.empty().append($list);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,13 @@ export default class Component {
|
||||
console.log(`Call to ${fun.name} in ${this.componentId} took ${took}ms`);
|
||||
}
|
||||
|
||||
await promise;
|
||||
if (glob.isDev) {
|
||||
await utils.timeLimit(promise, 20000, `Time limit failed on ${this.constructor.name} with ${fun.name}`);
|
||||
}
|
||||
else {
|
||||
// cheaper and in non-dev the extra reporting is lost anyway through reload
|
||||
await promise;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -276,7 +276,7 @@ export default class NoteDetailWidget extends TabAwareWidget {
|
||||
|
||||
const label = attrs.find(attr =>
|
||||
attr.type === 'label'
|
||||
&& ['readOnly', 'autoReadOnlyDisabled', 'cssClass', 'bookZoomLevel'].includes(attr.name)
|
||||
&& ['readOnly', 'autoReadOnlyDisabled', 'cssClass', 'bookZoomLevel', 'displayRelations'].includes(attr.name)
|
||||
&& attr.isAffecting(this.note));
|
||||
|
||||
const relation = attrs.find(attr =>
|
||||
|
||||
@@ -476,7 +476,7 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
|
||||
let childNoteIds = note.getChildNoteIds();
|
||||
|
||||
if (childNoteIds.length > MAX_SEARCH_RESULTS_IN_TREE) {
|
||||
if (note.type === 'search' && childNoteIds.length > MAX_SEARCH_RESULTS_IN_TREE) {
|
||||
childNoteIds = childNoteIds.slice(0, MAX_SEARCH_RESULTS_IN_TREE);
|
||||
}
|
||||
|
||||
@@ -797,7 +797,7 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
const node = await this.expandToNote(activeContext.notePath);
|
||||
|
||||
await node.makeVisible({scrollIntoView: true});
|
||||
node.setFocus(true);
|
||||
node.setActive(true, {noEvents: true, noFocus: false});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1030,6 +1030,9 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
}
|
||||
|
||||
for (const branch of loadResults.getBranches()) {
|
||||
// adding noteId itself to update all potential clones
|
||||
noteIdsToUpdate.add(branch.noteId);
|
||||
|
||||
for (const node of this.getNodesByBranchId(branch.branchId)) {
|
||||
if (branch.isDeleted) {
|
||||
if (node.isActive()) {
|
||||
@@ -1048,9 +1051,6 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
|
||||
noteIdsToUpdate.add(branch.parentNoteId);
|
||||
}
|
||||
else {
|
||||
noteIdsToUpdate.add(branch.noteId);
|
||||
}
|
||||
}
|
||||
|
||||
if (!branch.isDeleted) {
|
||||
@@ -1126,7 +1126,12 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
node = await this.expandToNote(nextNotePath, false);
|
||||
|
||||
if (node) {
|
||||
await appContext.tabManager.getActiveTabContext().setNote(nextNotePath);
|
||||
// FIXME: this is conceptually wrong
|
||||
// here note tree is responsible for updating global state of the application
|
||||
// this should be done by tabcontext / tabmanager and note tree should only listen to
|
||||
// changes in active note and just set the "active" state
|
||||
// We don't await since that can bring up infinite cycles when e.g. custom widget does some backend requests which wait for max sync ID processed
|
||||
appContext.tabManager.getActiveTabContext().setNote(nextNotePath);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -90,12 +90,18 @@ export default class QuickSearchWidget extends BasicWidget {
|
||||
const $link = await linkService.createNoteLink(note.noteId, {showNotePath: true});
|
||||
$link.addClass('dropdown-item');
|
||||
$link.attr("tabIndex", "0");
|
||||
$link.on('click', () => this.$dropdownToggle.dropdown("hide"));
|
||||
$link.on('click', e => {
|
||||
this.$dropdownToggle.dropdown("hide");
|
||||
|
||||
if (!e.target || e.target.nodeName !== 'A') {
|
||||
// click on the link is handled by link handling but we want the whole item clickable
|
||||
appContext.tabManager.getActiveTabContext().setNote(note.noteId);
|
||||
}
|
||||
});
|
||||
utils.bindElShortcut($link, 'return', () => {
|
||||
$link.find('a').trigger({
|
||||
type: 'click',
|
||||
which: 1 // left click
|
||||
});
|
||||
this.$dropdownToggle.dropdown("hide");
|
||||
|
||||
appContext.tabManager.getActiveTabContext().setNote(note.noteId);
|
||||
});
|
||||
|
||||
this.$dropdownMenu.append($link);
|
||||
|
||||
@@ -52,7 +52,7 @@ export default class SearchResultWidget extends TabAwareWidget {
|
||||
this.$noResults.toggle(note.getChildNoteIds().length === 0 && !!note.searchResultsLoaded);
|
||||
this.$notExecutedYet.toggle(!note.searchResultsLoaded);
|
||||
|
||||
const noteListRenderer = new NoteListRenderer(this.$content, note, note.getChildNoteIds());
|
||||
const noteListRenderer = new NoteListRenderer(this.$content, note, note.getChildNoteIds(), true);
|
||||
await noteListRenderer.renderList();
|
||||
}
|
||||
|
||||
|
||||
@@ -285,7 +285,7 @@ export default class RelationMapTypeWidget extends TypeWidget {
|
||||
|
||||
async loadNotesAndRelations() {
|
||||
const noteIds = this.mapData.notes.map(note => note.noteId);
|
||||
const data = await server.post("notes/relation-map", {noteIds});
|
||||
const data = await server.post("notes/relation-map", {noteIds, relationMapNoteId: this.noteId});
|
||||
|
||||
this.relations = [];
|
||||
|
||||
|
||||
@@ -59,7 +59,10 @@ function createSqlConsole() {
|
||||
return note;
|
||||
}
|
||||
|
||||
function createSearchNote() {
|
||||
function createSearchNote(req) {
|
||||
const params = req.body;
|
||||
const searchString = params.searchString || "";
|
||||
|
||||
const today = dateUtils.localNowDate();
|
||||
|
||||
const searchHome =
|
||||
@@ -68,12 +71,18 @@ function createSearchNote() {
|
||||
|
||||
const {note} = noteService.createNewNote({
|
||||
parentNoteId: searchHome.noteId,
|
||||
title: 'Search: ',
|
||||
title: 'Search: ' + searchString,
|
||||
content: "",
|
||||
type: 'search',
|
||||
mime: 'application/json'
|
||||
});
|
||||
|
||||
note.setLabel('searchString', searchString);
|
||||
|
||||
if (params.ancestorNoteId) {
|
||||
note.setRelation('ancestor', params.ancestorNoteId);
|
||||
}
|
||||
|
||||
return note;
|
||||
}
|
||||
|
||||
|
||||
@@ -117,7 +117,8 @@ function setNoteTypeMime(req) {
|
||||
}
|
||||
|
||||
function getRelationMap(req) {
|
||||
const noteIds = req.body.noteIds;
|
||||
const {relationMapNoteId, noteIds} = req.body;
|
||||
|
||||
const resp = {
|
||||
// noteId => title
|
||||
noteTitles: {},
|
||||
@@ -134,12 +135,23 @@ function getRelationMap(req) {
|
||||
|
||||
const questionMarks = noteIds.map(noteId => '?').join(',');
|
||||
|
||||
const relationMapNote = repository.getNote(relationMapNoteId);
|
||||
|
||||
const displayRelationsVal = relationMapNote.getLabelValue('displayRelations');
|
||||
const displayRelations = !displayRelationsVal ? [] : displayRelationsVal
|
||||
.split(",")
|
||||
.map(token => token.trim());
|
||||
|
||||
console.log("displayRelations", displayRelations);
|
||||
|
||||
const notes = repository.getEntities(`SELECT * FROM notes WHERE isDeleted = 0 AND noteId IN (${questionMarks})`, noteIds);
|
||||
|
||||
for (const note of notes) {
|
||||
resp.noteTitles[note.noteId] = note.title;
|
||||
|
||||
resp.relations = resp.relations.concat(note.getRelations()
|
||||
.filter(relation => !relation.isAutoLink() || displayRelations.includes(relation.name))
|
||||
.filter(relation => displayRelations.length === 0 || displayRelations.includes(relation.name))
|
||||
.filter(relation => noteIds.includes(relation.value))
|
||||
.map(relation => ({
|
||||
attributeId: relation.attributeId,
|
||||
|
||||
@@ -135,6 +135,10 @@ function getTree(req) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!(subTreeNoteId in noteCache.notes)) {
|
||||
return [404, `Note ${subTreeNoteId} not found in the cache`];
|
||||
}
|
||||
|
||||
collect(noteCache.notes[subTreeNoteId]);
|
||||
|
||||
return getNotesAndBranchesAndAttributes(collectedNoteIds);
|
||||
|
||||
@@ -1 +1 @@
|
||||
module.exports = { buildDate:"2021-02-19T23:15:18+01:00", buildRevision: "56506d33a7fea668f9ba2683b2836a30d28cd96c" };
|
||||
module.exports = { buildDate:"2021-02-25T22:41:35+01:00", buildRevision: "cde41b268e4e88b3fe3601d9d19b3f5241625ada" };
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use strict";
|
||||
|
||||
const TurndownService = require('turndown');
|
||||
const turndownPluginGfm = require('turndown-plugin-gfm');
|
||||
const turndownPluginGfm = require('joplin-turndown-plugin-gfm');
|
||||
|
||||
let instance = null;
|
||||
|
||||
@@ -16,4 +16,4 @@ function toMarkdown(content) {
|
||||
|
||||
module.exports = {
|
||||
toMarkdown
|
||||
};
|
||||
};
|
||||
|
||||
@@ -53,7 +53,7 @@ eventService.subscribe(eventService.ENTITY_CREATED, ({ entityName, entity }) =>
|
||||
if (entity.type === 'relation' && entity.name === 'template') {
|
||||
const note = repository.getNote(entity.noteId);
|
||||
|
||||
if (!note.isStringNote()) {
|
||||
if (!["text", "code"].includes(note.type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -180,10 +180,14 @@ class Note {
|
||||
return !!this.ownedAttributes.find(attr => attr.type === 'label' && attr.name === 'archived' && attr.isInheritable);
|
||||
}
|
||||
|
||||
// will sort the parents so that non-archived are first and archived at the end
|
||||
// this is done so that non-archived paths are always explored as first when searching for note path
|
||||
// will sort the parents so that non-search & non-archived are first and archived at the end
|
||||
// this is done so that non-search & non-archived paths are always explored as first when looking for note path
|
||||
resortParents() {
|
||||
this.parents.sort((a, b) => a.hasInheritableOwnedArchivedLabel ? 1 : -1);
|
||||
this.parentBranches.sort((a, b) =>
|
||||
a.branchId.startsWith('virt-')
|
||||
|| a.parentNote.hasInheritableOwnedArchivedLabel ? 1 : -1);
|
||||
|
||||
this.parents = this.parentBranches.map(branch => branch.parentNote);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -238,7 +238,15 @@ async function findSimilarNotes(noteId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const dateLimits = buildDateLimits(baseNote);
|
||||
let dateLimits;
|
||||
|
||||
try {
|
||||
dateLimits = buildDateLimits(baseNote);
|
||||
}
|
||||
catch (e) {
|
||||
throw new Error(`Date limits failed with ${e.message}, entity: ${JSON.stringify(baseNote.pojo)}`);
|
||||
}
|
||||
|
||||
const rewardMap = buildRewardMap(baseNote);
|
||||
let ancestorRewardCache = {};
|
||||
const ancestorNoteIds = new Set(baseNote.ancestors.map(note => note.noteId));
|
||||
|
||||
@@ -19,6 +19,8 @@ class SearchResult {
|
||||
computeScore(tokens) {
|
||||
this.score = 0;
|
||||
|
||||
// matches in attributes don't get extra points and thus are implicitly valued less than note path matches
|
||||
|
||||
const chunks = this.notePathTitle.toLowerCase().split(" ");
|
||||
|
||||
for (const chunk of chunks) {
|
||||
|
||||
@@ -101,7 +101,7 @@ async function doLogin() {
|
||||
});
|
||||
|
||||
if (sourceIdService.isLocalSourceId(resp.sourceId)) {
|
||||
throw new Error(`Sync server has source ID ${resp.sourceId} which is also local. Your sync setup is probably trying to connect to itself.`);
|
||||
throw new Error(`Sync server has source ID ${resp.sourceId} which is also local. This usually happens when the sync client is (mis)configured to sync with itself (URL points back to client) instead of the correct sync server.`);
|
||||
}
|
||||
|
||||
syncContext.sourceId = resp.sourceId;
|
||||
|
||||
@@ -5,6 +5,7 @@ const repository = require('./repository');
|
||||
const Branch = require('../entities/branch');
|
||||
const entityChangesService = require('./entity_changes.js');
|
||||
const protectedSessionService = require('./protected_session');
|
||||
const noteCache = require('./note_cache/note_cache');
|
||||
|
||||
function getNotes(noteIds) {
|
||||
// we return also deleted notes which have been specifically asked for
|
||||
@@ -134,6 +135,8 @@ function sortNotesAlphabetically(parentNoteId, directoriesFirst = false) {
|
||||
sql.execute("UPDATE branches SET notePosition = ? WHERE branchId = ?",
|
||||
[position, note.branchId]);
|
||||
|
||||
noteCache.branches[note.branchId].notePosition = position;
|
||||
|
||||
position += 10;
|
||||
}
|
||||
|
||||
|
||||
@@ -246,9 +246,13 @@ function getNoteTitle(filePath, replaceUnderscoresWithSpaces, noteMeta) {
|
||||
}
|
||||
}
|
||||
|
||||
function timeLimit(promise, limitMs) {
|
||||
function timeLimit(promise, limitMs, errorMessage) {
|
||||
if (!promise || !promise.then) { // it's not actually a promise
|
||||
return promise;
|
||||
}
|
||||
|
||||
// better stack trace if created outside of promise
|
||||
const error = new Error('Process exceeded time limit ' + limitMs);
|
||||
const error = new Error(errorMessage || `Process exceeded time limit ${limitMs}`);
|
||||
|
||||
return new Promise((res, rej) => {
|
||||
let resolved = false;
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
|
||||
<p class="card-text">
|
||||
<ul>
|
||||
<li><kbd>#</kbd>, <kbd>##</kbd>, <kbd>###</kbd> etc. followed by space for headings</li>
|
||||
<li><kbd>##</kbd>, <kbd>###</kbd>, <kbd>####</kbd> etc. followed by space for headings</li>
|
||||
<li><kbd>*</kbd> or <kbd>-</kbd> followed by space for bullet list</li>
|
||||
<li><kbd>1.</kbd> or <kbd>1)</kbd> followed by space for numbered list</li>
|
||||
<li>start a line with <kbd>></kbd> followed by space for block quote</li>
|
||||
|
||||
Reference in New Issue
Block a user