Compare commits

...

21 Commits

Author SHA1 Message Date
zadam
19821b634f release 0.46.2-beta 2021-02-25 22:41:35 +01:00
zadam
cde41b268e allow copying type, mime, content from template only for text and code note types, fixes #1689 2021-02-25 22:33:26 +01:00
zadam
cb6d35236c update correctly "multi-parent" node status, fixes #1688 2021-02-25 21:16:21 +01:00
zadam
7572ee284b set search note title correctly during creation 2021-02-25 20:02:49 +01:00
zadam
d0eaf623a8 activate node after calling "scroll to active note", fixes #1685 2021-02-24 22:41:24 +01:00
zadam
25c2db6c3a sort parent relationships so that clones in search notes appear last, fixes #1686 2021-02-24 22:38:26 +01:00
zadam
5a173ff14e quick search now allows clicking anywhere 2021-02-24 22:19:09 +01:00
zadam
7fab75b085 Merge remote-tracking branch 'origin/master' 2021-02-23 22:01:10 +01:00
zadam
0f065536d0 use 100 children limit on only search notes (as was initially intended), fixes #1673 2021-02-23 22:01:02 +01:00
sigaloid
d0747abded no h1 headings (#1679)
#1552
2021-02-23 21:23:23 +01:00
Mirwaisse Djanbaz
f62b4a581e Typo in export.js (#1682) 2021-02-23 21:20:19 +01:00
Mirwaisse Djanbaz
dcf1c62ec1 Typo in appearance settings (#1683) 2021-02-23 21:19:57 +01:00
zadam
93d55b3e7b put back waiting for sync after frontend API backend request 2021-02-22 22:43:54 +01:00
zadam
90c6852423 hide auto links by default in relation map, allow configuring displayed relations, fixes #1674 2021-02-22 22:34:33 +01:00
zadam
208baa56e9 error handling 2021-02-22 21:59:37 +01:00
zadam
ddf8438b22 fix edited notes widget note transitions, fixes #1672 2021-02-21 22:45:32 +01:00
zadam
6008dc891f fix note activation right after creation from calendar 2021-02-21 21:54:16 +01:00
zadam
cc887a00f2 runOnBackend now does not automatically wait for the maxSyncId since it can easily result in infinite cycle 2021-02-21 21:28:00 +01:00
zadam
fbbd51d0b1 small changes for better debugging infos 2021-02-20 23:17:29 +01:00
zadam
081b8b126a fix note_reordering not updating note cache 2021-02-20 21:28:22 +01:00
zadam
7a6bb81345 display note path in search results 2021-02-20 20:10:45 +01:00
32 changed files with 212 additions and 110 deletions

50
package-lock.json generated
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -56,6 +56,8 @@ async function resolveNotePathToSegments(notePath, logErrors = true) {
return;
}
child.resortParents();
const parents = child.getParentNotes();
if (!parents.length) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 = [];

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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>&gt;</kbd> followed by space for block quote</li>