diff --git a/src/public/app/widgets/attribute_widgets/attribute_detail.js b/src/public/app/widgets/attribute_widgets/attribute_detail.js
index fb15b92ef..027fa5429 100644
--- a/src/public/app/widgets/attribute_widgets/attribute_detail.js
+++ b/src/public/app/widgets/attribute_widgets/attribute_detail.js
@@ -223,6 +223,7 @@ const ATTR_HELP = {
"shareRaw": "note will be served in its raw format, without HTML wrapper",
"shareDisallowRobotIndexing": `will forbid robot indexing of this note via X-Robots-Tag: noindex
header`,
"shareCredentials": "require credentials to access this shared note. Value is expected to be in format 'username:password'. Don't forget to make this inheritable to apply to child-notes/images.",
+ "shareIndex": "note with this this label will list all roots of shared notes",
"displayRelations": "comma delimited names of relations which should be displayed. All other ones will be hidden.",
"hideRelations": "comma delimited names of relations which should be hidden. All other ones will be displayed.",
"titleTemplate": `default title of notes created as children of this note. The value is evaluated as JavaScript string
diff --git a/src/services/builtin_attributes.js b/src/services/builtin_attributes.js
index c25e10d05..d538a169f 100644
--- a/src/services/builtin_attributes.js
+++ b/src/services/builtin_attributes.js
@@ -54,6 +54,7 @@ module.exports = [
{ type: 'label', name: 'shareRaw' },
{ type: 'label', name: 'shareDisallowRobotIndexing' },
{ type: 'label', name: 'shareCredentials' },
+ { type: 'label', name: 'shareIndex' },
{ type: 'label', name: 'displayRelations' },
{ type: 'label', name: 'hideRelations' },
{ type: 'label', name: 'titleTemplate', isDangerous: true },
diff --git a/src/share/content_renderer.js b/src/share/content_renderer.js
index a795ee084..bd2a5734e 100644
--- a/src/share/content_renderer.js
+++ b/src/share/content_renderer.js
@@ -1,6 +1,7 @@
const {JSDOM} = require("jsdom");
const shaca = require("./shaca/shaca");
const assetPath = require("../services/asset_path");
+const shareRoot = require('./share_root');
function getContent(note) {
if (note.isProtected) {
@@ -11,40 +12,74 @@ function getContent(note) {
};
}
- let content = note.getContent();
- let header = '';
- let isEmpty = false;
+ const result = {
+ content: note.getContent(),
+ header: '',
+ isEmpty: false
+ };
if (note.type === 'text') {
- const document = new JSDOM(content || "").window.document;
+ renderText(result, note);
+ } else if (note.type === 'code') {
+ renderCode(result);
+ } else if (note.type === 'mermaid') {
+ renderMermaid(result);
+ } else if (note.type === 'image') {
+ renderImage(result, note);
+ } else if (note.type === 'file') {
+ renderFile(note, result);
+ } else if (note.type === 'book') {
+ result.isEmpty = true;
+ } else if (note.type === 'canvas') {
+ renderCanvas(result, note);
+ } else {
+ result.content = '
This note type cannot be displayed.
'; + } - isEmpty = document.body.textContent.trim().length === 0 - && document.querySelectorAll("img").length === 0; + return result; +} - if (!isEmpty) { - for (const linkEl of document.querySelectorAll("a")) { - const href = linkEl.getAttribute("href"); +function renderIndex(result) { + result.content += '${content}+
${result.content}
This note type cannot be displayed.
'; - } - - return { - header, - content, - isEmpty - }; } module.exports = { diff --git a/src/share/routes.js b/src/share/routes.js index 11917afe1..34f0a8f5c 100644 --- a/src/share/routes.js +++ b/src/share/routes.js @@ -88,7 +88,7 @@ function register(router) { addNoIndexHeader(note, res); - if (note.hasLabel('shareRaw') || ['image', 'file'].includes(note.type)) { + if (note.hasLabel('shareRaw')) { res.setHeader('Content-Type', note.mime) .send(note.getContent()); diff --git a/src/share/shaca/entities/attribute.js b/src/share/shaca/entities/attribute.js index 1696312ab..365d1c434 100644 --- a/src/share/shaca/entities/attribute.js +++ b/src/share/shaca/entities/attribute.js @@ -49,39 +49,40 @@ class Attribute extends AbstractEntity { } } + /** @returns {boolean} */ get isAffectingSubtree() { return this.isInheritable || (this.type === 'relation' && this.name === 'template'); } + /** @returns {string} */ get targetNoteId() { // alias return this.type === 'relation' ? this.value : undefined; } + /** @returns {boolean} */ isAutoLink() { return this.type === 'relation' && ['internalLink', 'imageLink', 'relationMapLink', 'includeNoteLink'].includes(this.name); } + /** @returns {Note|null} */ get note() { return this.shaca.notes[this.noteId]; } + /** @returns {Note|null} */ get targetNote() { if (this.type === 'relation') { return this.shaca.notes[this.value]; } } - /** - * @returns {Note|null} - */ + /** @returns {Note|null} */ getNote() { return this.shaca.getNote(this.noteId); } - /** - * @returns {Note|null} - */ + /** @returns {Note|null} */ getTargetNote() { if (this.type !== 'relation') { throw new Error(`Attribute ${this.attributeId} is not relation`); diff --git a/src/share/shaca/entities/branch.js b/src/share/shaca/entities/branch.js index a1b55538e..869a8ecd9 100644 --- a/src/share/shaca/entities/branch.js +++ b/src/share/shaca/entities/branch.js @@ -43,6 +43,7 @@ class Branch extends AbstractEntity { return this.shaca.notes[this.noteId]; } + /** @return {Note} */ getNote() { return this.childNote; } diff --git a/src/share/shaca/entities/note.js b/src/share/shaca/entities/note.js index 27f54fc44..ce5a5aa44 100644 --- a/src/share/shaca/entities/note.js +++ b/src/share/shaca/entities/note.js @@ -3,6 +3,7 @@ const sql = require('../../sql'); const utils = require('../../../services/utils'); const AbstractEntity = require('./abstract_entity'); +const escape = require('escape-html'); const LABEL = 'label'; const RELATION = 'relation'; @@ -47,22 +48,32 @@ class Note extends AbstractEntity { this.shaca.notes[this.noteId] = this; } + /** @returns {Branch[]} */ getParentBranches() { return this.parentBranches; } + /** @returns {Branch[]} */ getBranches() { return this.parentBranches; } + /** @returns {Branch[]} */ + getChildBranches() { + return this.children.map(childNote => this.shaca.getBranchFromChildAndParent(childNote.noteId, this.noteId)); + } + + /** @returns {Note[]} */ getParentNotes() { return this.parents; } + /** @returns {Note[]} */ getChildNotes() { return this.children; } + /** @returns {Note[]} */ getVisibleChildNotes() { return this.getChildBranches() .filter(branch => !branch.isHidden) @@ -70,18 +81,16 @@ class Note extends AbstractEntity { .filter(childNote => !childNote.hasLabel('shareHiddenFromTree')); } + /** @returns {boolean} */ hasChildren() { return this.children && this.children.length > 0; } + /** @returns {boolean} */ hasVisibleChildren() { return this.getVisibleChildNotes().length > 0; } - getChildBranches() { - return this.children.map(childNote => this.shaca.getBranchFromChildAndParent(childNote.noteId, this.noteId)); - } - getContent(silentNotFoundError = false) { const row = sql.getRow(`SELECT content FROM note_contents WHERE noteId = ?`, [this.noteId]); @@ -133,6 +142,7 @@ class Note extends AbstractEntity { } } + /** @returns {Attribute[]} */ getCredentials() { this.__getAttributes([]); @@ -203,10 +213,12 @@ class Note extends AbstractEntity { return this.inheritableAttributeCache; } + /** @returns {boolean} */ hasAttribute(type, name) { return !!this.getAttributes().find(attr => attr.type === type && attr.name === name); } + /** @returns {Note|null} */ getRelationTarget(name) { const relation = this.getAttributes().find(attr => attr.type === 'relation' && attr.name === name); @@ -411,22 +423,27 @@ class Note extends AbstractEntity { return attrs.length > 0 ? attrs[0] : null; } + /** @returns {boolean} */ get isArchived() { return this.hasAttribute('label', 'archived'); } + /** @returns {boolean} */ hasInheritableOwnedArchivedLabel() { return !!this.ownedAttributes.find(attr => attr.type === 'label' && attr.name === 'archived' && attr.isInheritable); } + /** @returns {boolean} */ isTemplate() { return !!this.targetRelations.find(rel => rel.name === 'template'); } + /** @returns {Attribute[]} */ getTargetRelations() { return this.targetRelations; } + /** @returns {string} */ get shareId() { if (this.hasOwnedLabel('shareRoot')) { return ""; @@ -437,6 +454,10 @@ class Note extends AbstractEntity { return sharedAlias || this.noteId; } + get escapedTitle() { + return escape(this.title); + } + getPojoWithAttributes() { return { noteId: this.noteId, diff --git a/src/share/shaca/shaca.js b/src/share/shaca/shaca.js index 56c77778b..360b1b169 100644 --- a/src/share/shaca/shaca.js +++ b/src/share/shaca/shaca.js @@ -23,14 +23,17 @@ class Shaca { this.loaded = false; } + /** @returns {Note|null} */ getNote(noteId) { return this.notes[noteId]; } + /** @returns {boolean} */ hasNote(noteId) { return noteId in this.notes; } + /** @returns {Note[]} */ getNotes(noteIds, ignoreMissing = false) { const filteredNotes = []; @@ -51,18 +54,21 @@ class Shaca { return filteredNotes; } + /** @returns {Branch|null} */ getBranch(branchId) { return this.branches[branchId]; } - getAttribute(attributeId) { - return this.attributes[attributeId]; - } - + /** @returns {Branch|null} */ getBranchFromChildAndParent(childNoteId, parentNoteId) { return this.childParentToBranch[`${childNoteId}-${parentNoteId}`]; } + /** @returns {Attribute|null} */ + getAttribute(attributeId) { + return this.attributes[attributeId]; + } + getEntity(entityName, entityId) { if (!entityName || !entityId) { return null;