From 52b118df7f49455b773217749a10d225d2aeb373 Mon Sep 17 00:00:00 2001 From: zadam Date: Mon, 17 Jan 2022 23:13:56 +0100 Subject: [PATCH] add #shareRoot label to define an "index" note, closes #2567 --- package-lock.json | 18 +++--- package.json | 2 +- src/public/app/services/note_create.js | 2 +- .../attribute_widgets/attribute_detail.js | 1 + src/public/app/widgets/note_tree.js | 1 + src/public/app/widgets/shared_info.js | 10 ++- src/services/attributes.js | 64 +------------------ src/services/builtin_attributes.js | 63 ++++++++++++++++++ src/share/routes.js | 27 +++++--- src/share/shaca/entities/attribute.js | 4 ++ src/share/shaca/entities/note.js | 4 ++ src/share/shaca/shaca.js | 3 + 12 files changed, 115 insertions(+), 84 deletions(-) create mode 100644 src/services/builtin_attributes.js diff --git a/package-lock.json b/package-lock.json index 682fc8731..33ba9f88a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "trilium", - "version": "0.49.4", + "version": "0.49.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "trilium", - "version": "0.49.4", + "version": "0.49.5", "license": "AGPL-3.0-only", "dependencies": { "@electron/remote": "2.0.1", @@ -62,7 +62,7 @@ "tmp": "0.2.1", "turndown": "7.1.1", "unescape": "1.0.1", - "ws": "8.4.0", + "ws": "8.4.2", "yauzl": "2.10.0" }, "bin": { @@ -11045,9 +11045,9 @@ } }, "node_modules/ws": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.4.0.tgz", - "integrity": "sha512-IHVsKe2pjajSUIl4KYMQOdlyliovpEPquKkqbwswulszzI7r0SfQrxnXdWAEqOlDCLrVSJzo+O1hAwdog2sKSQ==", + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.4.2.tgz", + "integrity": "sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA==", "engines": { "node": ">=10.0.0" }, @@ -19881,9 +19881,9 @@ } }, "ws": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.4.0.tgz", - "integrity": "sha512-IHVsKe2pjajSUIl4KYMQOdlyliovpEPquKkqbwswulszzI7r0SfQrxnXdWAEqOlDCLrVSJzo+O1hAwdog2sKSQ==", + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.4.2.tgz", + "integrity": "sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA==", "requires": {} }, "xdg-basedir": { diff --git a/package.json b/package.json index 77bb82e59..aa1bd3c89 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "tmp": "0.2.1", "turndown": "7.1.1", "unescape": "1.0.1", - "ws": "8.4.1", + "ws": "8.4.2", "yauzl": "2.10.0" }, "devDependencies": { diff --git a/src/public/app/services/note_create.js b/src/public/app/services/note_create.js index 790f96adc..f1bbc2e35 100644 --- a/src/public/app/services/note_create.js +++ b/src/public/app/services/note_create.js @@ -40,7 +40,7 @@ async function createNote(parentNotePath, options = {}) { C-->D;` } - const {note, branch} = await server.post(`notes/${parentNoteId}/children?target=${options.target}&targetBranchId=${options.targetBranchId}`, { + const {note, branch} = await server.post(`notes/${parentNoteId}/children?target=${options.target}&targetBranchId=${options.targetBranchId || ""}`, { title: newNoteName, content: options.content || "", isProtected: options.isProtected, diff --git a/src/public/app/widgets/attribute_widgets/attribute_detail.js b/src/public/app/widgets/attribute_widgets/attribute_detail.js index a8eb3ba30..fd22207c7 100644 --- a/src/public/app/widgets/attribute_widgets/attribute_detail.js +++ b/src/public/app/widgets/attribute_widgets/attribute_detail.js @@ -214,6 +214,7 @@ const ATTR_HELP = { "shareHiddenFromTree": "this note is hidden from left navigation tree, but still accessible with its URL", "shareAlias": "define an alias using which the note will be available under https://your_trilium_host/share/[your_alias]", "shareOmitDefaultCss": "default share page CSS will be omitted. Use when you make extensive styling changes.", + "shareRoot": "marks note which is served on /share root.", }, "relation": { "runOnNoteCreation": "executes when note is created on backend", diff --git a/src/public/app/widgets/note_tree.js b/src/public/app/widgets/note_tree.js index 84d811bb7..38d64bbed 100644 --- a/src/public/app/widgets/note_tree.js +++ b/src/public/app/widgets/note_tree.js @@ -617,6 +617,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { /** * @param {Branch} branch + * @param {boolean} forceLazy */ prepareNode(branch, forceLazy = false) { const note = branch.getNoteFromCache(); diff --git a/src/public/app/widgets/shared_info.js b/src/public/app/widgets/shared_info.js index 6a830f8b6..db9ed6835 100644 --- a/src/public/app/widgets/shared_info.js +++ b/src/public/app/widgets/shared_info.js @@ -32,7 +32,7 @@ export default class SharedInfoWidget extends NoteContextAwareWidget { const syncServerHost = options.get("syncServerHost"); let link; - const shareId = note.getOwnedLabelValue('shareAlias') || note.noteId; + const shareId = this.getShareId(note); if (syncServerHost) { link = syncServerHost + "/share/" + shareId; @@ -46,6 +46,14 @@ export default class SharedInfoWidget extends NoteContextAwareWidget { this.$sharedLink.attr("href", link).text(link); } + getShareId(note) { + if (note.hasOwnedLabel('shareRoot')) { + return ''; + } + + return note.getOwnedLabelValue('shareAlias') || note.noteId; + } + entitiesReloadedEvent({loadResults}) { if (loadResults.getAttributes().find(attr => attr.name.startsWith("share") && attributeService.isAffecting(attr, this.note))) { this.refresh(); diff --git a/src/services/attributes.js b/src/services/attributes.js index 89a637e00..c2f800cef 100644 --- a/src/services/attributes.js +++ b/src/services/attributes.js @@ -5,72 +5,10 @@ const sql = require('./sql'); const becca = require('../becca/becca'); const Attribute = require('../becca/entities/attribute'); const {formatAttrForSearch} = require("./attribute_formatter"); +const BUILTIN_ATTRIBUTES = require("./builtin_attributes"); const ATTRIBUTE_TYPES = [ 'label', 'relation' ]; -const BUILTIN_ATTRIBUTES = [ - // label names - { type: 'label', name: 'inbox' }, - { type: 'label', name: 'disableVersioning' }, - { type: 'label', name: 'calendarRoot' }, - { type: 'label', name: 'archived' }, - { type: 'label', name: 'excludeFromExport' }, - { type: 'label', name: 'disableInclusion' }, - { type: 'label', name: 'appCss' }, - { type: 'label', name: 'appTheme' }, - { type: 'label', name: 'hidePromotedAttributes' }, - { type: 'label', name: 'readOnly' }, - { type: 'label', name: 'autoReadOnlyDisabled' }, - { type: 'label', name: 'hoistedCssClass' }, - { type: 'label', name: 'cssClass' }, - { type: 'label', name: 'iconClass' }, - { type: 'label', name: 'keyboardShortcut' }, - { type: 'label', name: 'run', isDangerous: true }, - { type: 'label', name: 'runOnInstance', isDangerous: false }, - { type: 'label', name: 'runAtHour', isDangerous: false }, - { type: 'label', name: 'customRequestHandler', isDangerous: true }, - { type: 'label', name: 'customResourceProvider', isDangerous: true }, - { type: 'label', name: 'widget', isDangerous: true }, - { type: 'label', name: 'noteInfoWidgetDisabled' }, - { type: 'label', name: 'linkMapWidgetDisabled' }, - { type: 'label', name: 'noteRevisionsWidgetDisabled' }, - { type: 'label', name: 'whatLinksHereWidgetDisabled' }, - { type: 'label', name: 'similarNotesWidgetDisabled' }, - { type: 'label', name: 'workspace' }, - { type: 'label', name: 'workspaceIconClass' }, - { type: 'label', name: 'workspaceTabBackgroundColor' }, - { type: 'label', name: 'searchHome' }, - { type: 'label', name: 'hoistedInbox' }, - { type: 'label', name: 'hoistedSearchHome' }, - { type: 'label', name: 'sqlConsoleHome' }, - { type: 'label', name: 'datePattern' }, - { type: 'label', name: 'pageSize' }, - { type: 'label', name: 'viewType' }, - { type: 'label', name: 'mapRootNoteId' }, - { type: 'label', name: 'bookmarked' }, - { type: 'label', name: 'bookmarkFolder' }, - { type: 'label', name: 'sorted' }, - { type: 'label', name: 'top' }, - { type: 'label', name: 'fullContentWidth' }, - { type: 'label', name: 'shareHiddenFromTree' }, - { type: 'label', name: 'shareAlias' }, - { type: 'label', name: 'shareOmitDefaultCss' }, - - // relation names - { type: 'relation', name: 'runOnNoteCreation', isDangerous: true }, - { type: 'relation', name: 'runOnNoteTitleChange', isDangerous: true }, - { type: 'relation', name: 'runOnNoteChange', isDangerous: true }, - { type: 'relation', name: 'runOnChildNoteCreation', isDangerous: true }, - { type: 'relation', name: 'runOnAttributeCreation', isDangerous: true }, - { type: 'relation', name: 'runOnAttributeChange', isDangerous: true }, - { type: 'relation', name: 'template' }, - { type: 'relation', name: 'widget', isDangerous: true }, - { type: 'relation', name: 'renderNote', isDangerous: true }, - { type: 'relation', name: 'shareCss', isDangerous: false }, - { type: 'relation', name: 'shareJs', isDangerous: false }, - { type: 'relation', name: 'shareFavicon', isDangerous: false }, -]; - /** @returns {Note[]} */ function getNotesWithLabel(name, value) { const query = formatAttrForSearch({type: 'label', name, value}, true); diff --git a/src/services/builtin_attributes.js b/src/services/builtin_attributes.js new file mode 100644 index 000000000..d046121b7 --- /dev/null +++ b/src/services/builtin_attributes.js @@ -0,0 +1,63 @@ +module.exports = [ + // label names + { type: 'label', name: 'inbox' }, + { type: 'label', name: 'disableVersioning' }, + { type: 'label', name: 'calendarRoot' }, + { type: 'label', name: 'archived' }, + { type: 'label', name: 'excludeFromExport' }, + { type: 'label', name: 'disableInclusion' }, + { type: 'label', name: 'appCss' }, + { type: 'label', name: 'appTheme' }, + { type: 'label', name: 'hidePromotedAttributes' }, + { type: 'label', name: 'readOnly' }, + { type: 'label', name: 'autoReadOnlyDisabled' }, + { type: 'label', name: 'hoistedCssClass' }, + { type: 'label', name: 'cssClass' }, + { type: 'label', name: 'iconClass' }, + { type: 'label', name: 'keyboardShortcut' }, + { type: 'label', name: 'run', isDangerous: true }, + { type: 'label', name: 'runOnInstance', isDangerous: false }, + { type: 'label', name: 'runAtHour', isDangerous: false }, + { type: 'label', name: 'customRequestHandler', isDangerous: true }, + { type: 'label', name: 'customResourceProvider', isDangerous: true }, + { type: 'label', name: 'widget', isDangerous: true }, + { type: 'label', name: 'noteInfoWidgetDisabled' }, + { type: 'label', name: 'linkMapWidgetDisabled' }, + { type: 'label', name: 'noteRevisionsWidgetDisabled' }, + { type: 'label', name: 'whatLinksHereWidgetDisabled' }, + { type: 'label', name: 'similarNotesWidgetDisabled' }, + { type: 'label', name: 'workspace' }, + { type: 'label', name: 'workspaceIconClass' }, + { type: 'label', name: 'workspaceTabBackgroundColor' }, + { type: 'label', name: 'searchHome' }, + { type: 'label', name: 'hoistedInbox' }, + { type: 'label', name: 'hoistedSearchHome' }, + { type: 'label', name: 'sqlConsoleHome' }, + { type: 'label', name: 'datePattern' }, + { type: 'label', name: 'pageSize' }, + { type: 'label', name: 'viewType' }, + { type: 'label', name: 'mapRootNoteId' }, + { type: 'label', name: 'bookmarked' }, + { type: 'label', name: 'bookmarkFolder' }, + { type: 'label', name: 'sorted' }, + { type: 'label', name: 'top' }, + { type: 'label', name: 'fullContentWidth' }, + { type: 'label', name: 'shareHiddenFromTree' }, + { type: 'label', name: 'shareAlias' }, + { type: 'label', name: 'shareOmitDefaultCss' }, + { type: 'label', name: 'shareRoot' }, + + // relation names + { type: 'relation', name: 'runOnNoteCreation', isDangerous: true }, + { type: 'relation', name: 'runOnNoteTitleChange', isDangerous: true }, + { type: 'relation', name: 'runOnNoteChange', isDangerous: true }, + { type: 'relation', name: 'runOnChildNoteCreation', isDangerous: true }, + { type: 'relation', name: 'runOnAttributeCreation', isDangerous: true }, + { type: 'relation', name: 'runOnAttributeChange', isDangerous: true }, + { type: 'relation', name: 'template' }, + { type: 'relation', name: 'widget', isDangerous: true }, + { type: 'relation', name: 'renderNote', isDangerous: true }, + { type: 'relation', name: 'shareCss', isDangerous: false }, + { type: 'relation', name: 'shareJs', isDangerous: false }, + { type: 'relation', name: 'shareFavicon', isDangerous: false }, +]; diff --git a/src/share/routes.js b/src/share/routes.js index 91096e55a..6bd700c6c 100644 --- a/src/share/routes.js +++ b/src/share/routes.js @@ -21,13 +21,7 @@ function getSharedSubTreeRoot(note) { } function register(router) { - router.get('/share/:shareId', (req, res, next) => { - const {shareId} = req.params; - - shacaLoader.ensureLoad(); - - const note = shaca.aliasToNote[shareId] || shaca.notes[shareId]; - + function renderNote(note, res) { if (note) { const {header, content, isEmpty} = contentRenderer.getContent(note); @@ -40,10 +34,25 @@ function register(router) { isEmpty, subRoot }); - } - else { + } else { res.status(404).render("share/404"); } + } + + router.get(['/share', '/share/'], (req, res, next) => { + shacaLoader.ensureLoad(); + + renderNote(shaca.shareRootNote, res); + }); + + router.get('/share/:shareId', (req, res, next) => { + const {shareId} = req.params; + + shacaLoader.ensureLoad(); + + const note = shaca.aliasToNote[shareId] || shaca.notes[shareId]; + + renderNote(note, res); }); router.get('/share/api/notes/:noteId', (req, res, next) => { diff --git a/src/share/shaca/entities/attribute.js b/src/share/shaca/entities/attribute.js index ab8a8583d..471a3793e 100644 --- a/src/share/shaca/entities/attribute.js +++ b/src/share/shaca/entities/attribute.js @@ -43,6 +43,10 @@ class Attribute extends AbstractEntity { if (this.type === 'label' && this.name === 'shareAlias' && this.value.trim()) { this.shaca.aliasToNote[this.value.trim()] = this.note; } + + if (this.type === 'label' && this.name === 'shareRoot') { + this.shaca.shareRootNote = this.note; + } } get isAffectingSubtree() { diff --git a/src/share/shaca/entities/note.js b/src/share/shaca/entities/note.js index e14f040a1..f66ce27fb 100644 --- a/src/share/shaca/entities/note.js +++ b/src/share/shaca/entities/note.js @@ -406,6 +406,10 @@ class Note extends AbstractEntity { } get shareId() { + if (this.hasOwnedLabel('shareRoot')) { + return ""; + } + const sharedAlias = this.getOwnedLabelValue("shareAlias"); return sharedAlias || this.noteId; diff --git a/src/share/shaca/shaca.js b/src/share/shaca/shaca.js index 4ae3ce166..56c77778b 100644 --- a/src/share/shaca/shaca.js +++ b/src/share/shaca/shaca.js @@ -17,6 +17,9 @@ class Shaca { /** @type {Object.} */ this.aliasToNote = {}; + /** @type {Note|null} */ + this.shareRootNote = null; + this.loaded = false; }