mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-04 13:39:01 +01:00 
			
		
		
		
	add possibility to define a share index, closes #3265
This commit is contained in:
		
							parent
							
								
									eb68ab6776
								
							
						
					
					
						commit
						2467464433
					
				@ -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 <code>X-Robots-Tag: noindex</code> 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 
 | 
			
		||||
 | 
			
		||||
@ -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 },
 | 
			
		||||
 | 
			
		||||
@ -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 = '<p>This note type cannot be displayed.</p>';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        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 += '<ul id="index">';
 | 
			
		||||
 | 
			
		||||
                if (href?.startsWith("#")) {
 | 
			
		||||
                    const notePathSegments = href.split("/");
 | 
			
		||||
    const rootNote = shaca.getNote(shareRoot.SHARE_ROOT_NOTE_ID);
 | 
			
		||||
 | 
			
		||||
                    const noteId = notePathSegments[notePathSegments.length - 1];
 | 
			
		||||
                    const linkedNote = shaca.getNote(noteId);
 | 
			
		||||
    for (const childNote of rootNote.getChildNotes()) {
 | 
			
		||||
        result.content += `<li><a class="${childNote.type}" href="./${childNote.shareId}">${childNote.escapedTitle}</a></li>`;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
                    if (linkedNote) {
 | 
			
		||||
                        linkEl.setAttribute("href", linkedNote.shareId);
 | 
			
		||||
                        linkEl.classList.add("type-" + linkedNote.type);
 | 
			
		||||
                    }
 | 
			
		||||
                    else {
 | 
			
		||||
                        linkEl.removeAttribute("href");
 | 
			
		||||
                    }
 | 
			
		||||
    result.content += '</ul>';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function renderText(result, note) {
 | 
			
		||||
    const document = new JSDOM(result.content || "").window.document;
 | 
			
		||||
 | 
			
		||||
    result.isEmpty = document.body.textContent.trim().length === 0
 | 
			
		||||
        && document.querySelectorAll("img").length === 0;
 | 
			
		||||
 | 
			
		||||
    if (!result.isEmpty) {
 | 
			
		||||
        for (const linkEl of document.querySelectorAll("a")) {
 | 
			
		||||
            const href = linkEl.getAttribute("href");
 | 
			
		||||
 | 
			
		||||
            if (href?.startsWith("#")) {
 | 
			
		||||
                const notePathSegments = href.split("/");
 | 
			
		||||
 | 
			
		||||
                const noteId = notePathSegments[notePathSegments.length - 1];
 | 
			
		||||
                const linkedNote = shaca.getNote(noteId);
 | 
			
		||||
 | 
			
		||||
                if (linkedNote) {
 | 
			
		||||
                    linkEl.setAttribute("href", linkedNote.shareId);
 | 
			
		||||
                    linkEl.classList.add("type-" + linkedNote.type);
 | 
			
		||||
                } else {
 | 
			
		||||
                    linkEl.removeAttribute("href");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
            content = document.body.innerHTML;
 | 
			
		||||
        result.content = document.body.innerHTML;
 | 
			
		||||
 | 
			
		||||
            if (content.includes(`<span class="math-tex">`)) {
 | 
			
		||||
                header += `
 | 
			
		||||
        if (result.content.includes(`<span class="math-tex">`)) {
 | 
			
		||||
            result.header += `
 | 
			
		||||
<script src="../../${assetPath}/libraries/katex/katex.min.js"></script>
 | 
			
		||||
<link rel="stylesheet" href="../../${assetPath}/libraries/katex/katex.min.css">
 | 
			
		||||
<script src="../../${assetPath}/libraries/katex/auto-render.min.js"></script>
 | 
			
		||||
@ -54,54 +89,58 @@ document.addEventListener("DOMContentLoaded", function() {
 | 
			
		||||
    renderMathInElement(document.getElementById('content'));
 | 
			
		||||
});
 | 
			
		||||
</script>`;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (note.hasLabel("shareIndex")) {
 | 
			
		||||
            renderIndex(result);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    else if (note.type === 'code') {
 | 
			
		||||
        if (!content?.trim()) {
 | 
			
		||||
            isEmpty = true;
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            const document = new JSDOM().window.document;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
            const preEl = document.createElement('pre');
 | 
			
		||||
            preEl.appendChild(document.createTextNode(content));
 | 
			
		||||
function renderCode(result) {
 | 
			
		||||
    if (!result.content?.trim()) {
 | 
			
		||||
        result.isEmpty = true;
 | 
			
		||||
    } else {
 | 
			
		||||
        const document = new JSDOM().window.document;
 | 
			
		||||
 | 
			
		||||
            content = preEl.outerHTML;
 | 
			
		||||
        }
 | 
			
		||||
        const preEl = document.createElement('pre');
 | 
			
		||||
        preEl.appendChild(document.createTextNode(result.content));
 | 
			
		||||
 | 
			
		||||
        result.content = preEl.outerHTML;
 | 
			
		||||
    }
 | 
			
		||||
    else if (note.type === 'mermaid') {
 | 
			
		||||
        content = `
 | 
			
		||||
<div class="mermaid">${content}</div>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function renderMermaid(result) {
 | 
			
		||||
    result.content = `
 | 
			
		||||
<div class="mermaid">${result.content}</div>
 | 
			
		||||
<hr>
 | 
			
		||||
<details>
 | 
			
		||||
    <summary>Chart source</summary>
 | 
			
		||||
    <pre>${content}</pre>
 | 
			
		||||
    <pre>${result.content}</pre>
 | 
			
		||||
</details>`
 | 
			
		||||
        header += `<script src="../../${assetPath}/libraries/mermaid.min.js"></script>`;
 | 
			
		||||
    result.header += `<script src="../../${assetPath}/libraries/mermaid.min.js"></script>`;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function renderImage(result, note) {
 | 
			
		||||
    result.content = `<img src="api/images/${note.noteId}/${note.title}?${note.utcDateModified}">`;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function renderFile(note, result) {
 | 
			
		||||
    if (note.mime === 'application/pdf') {
 | 
			
		||||
        result.content = `<iframe class="pdf-view" src="api/notes/${note.noteId}/view"></iframe>`
 | 
			
		||||
    } else {
 | 
			
		||||
        result.content = `<button type="button" onclick="location.href='api/notes/${note.noteId}/download'">Download file</button>`;
 | 
			
		||||
    }
 | 
			
		||||
    else if (note.type === 'image') {
 | 
			
		||||
        content = `<img src="api/images/${note.noteId}/${note.title}?${note.utcDateModified}">`;
 | 
			
		||||
    }
 | 
			
		||||
    else if (note.type === 'file') {
 | 
			
		||||
        if (note.mime === 'application/pdf') {
 | 
			
		||||
            content = `<iframe class="pdf-view" src="api/notes/${note.noteId}/view"></iframe>`
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            content = `<button type="button" onclick="location.href='api/notes/${note.noteId}/download'">Download file</button>`;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    else if (note.type === 'book') {
 | 
			
		||||
        isEmpty = true;
 | 
			
		||||
    }
 | 
			
		||||
    else if (note.type === 'canvas') {
 | 
			
		||||
        header += `<script>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function renderCanvas(result, note) {
 | 
			
		||||
    result.header += `<script>
 | 
			
		||||
                    window.EXCALIDRAW_ASSET_PATH = window.location.origin + "/node_modules/@excalidraw/excalidraw/dist/";
 | 
			
		||||
                   </script>`;
 | 
			
		||||
        header += `<script src="../../${assetPath}/node_modules/react/umd/react.production.min.js"></script>`;
 | 
			
		||||
        header += `<script src="../../${assetPath}/node_modules/react-dom/umd/react-dom.production.min.js"></script>`;
 | 
			
		||||
        header += `<script src="../../${assetPath}/node_modules/@excalidraw/excalidraw/dist/excalidraw.production.min.js"></script>`;
 | 
			
		||||
        header += `<style>
 | 
			
		||||
    result.header += `<script src="../../${assetPath}/node_modules/react/umd/react.production.min.js"></script>`;
 | 
			
		||||
    result.header += `<script src="../../${assetPath}/node_modules/react-dom/umd/react-dom.production.min.js"></script>`;
 | 
			
		||||
    result.header += `<script src="../../${assetPath}/node_modules/@excalidraw/excalidraw/dist/excalidraw.production.min.js"></script>`;
 | 
			
		||||
    result.header += `<style>
 | 
			
		||||
 | 
			
		||||
            .excalidraw-wrapper {
 | 
			
		||||
                height: 100%;
 | 
			
		||||
@ -115,26 +154,16 @@ document.addEventListener("DOMContentLoaded", function() {
 | 
			
		||||
            }
 | 
			
		||||
        </style>`;
 | 
			
		||||
 | 
			
		||||
        content = `<div>
 | 
			
		||||
    result.content = `<div>
 | 
			
		||||
            <script>
 | 
			
		||||
                const {elements, appState, files} = JSON.parse(${JSON.stringify(content)});
 | 
			
		||||
                const {elements, appState, files} = JSON.parse(${JSON.stringify(result.content)});
 | 
			
		||||
                window.triliumExcalidraw = {elements, appState, files}
 | 
			
		||||
            </script>
 | 
			
		||||
            <div id="excalidraw-app"></div>
 | 
			
		||||
            <hr>
 | 
			
		||||
            <a href="api/images/${note.noteId}/${note.title}?utc=${note.utcDateModified}">Get Image Link</a>
 | 
			
		||||
            <a href="api/images/${note.noteId}/${note.escapedTitle}?utc=${note.utcDateModified}">Get Image Link</a>
 | 
			
		||||
            <script src="./canvas_share.js"></script>
 | 
			
		||||
        </div>`;
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        content = '<p>This note type cannot be displayed.</p>';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        header,
 | 
			
		||||
        content,
 | 
			
		||||
        isEmpty
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
 | 
			
		||||
@ -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());
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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`);
 | 
			
		||||
 | 
			
		||||
@ -43,6 +43,7 @@ class Branch extends AbstractEntity {
 | 
			
		||||
        return this.shaca.notes[this.noteId];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /** @return {Note} */
 | 
			
		||||
    getNote() {
 | 
			
		||||
        return this.childNote;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user