mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-04 13:39:01 +01:00 
			
		
		
		
	Merge remote-tracking branch 'origin/master'
This commit is contained in:
		
						commit
						bd7fa16e96
					
				@ -16,13 +16,13 @@ UPDATE attributes SET name  = 'name', value = 'value' WHERE type = 'label'
 | 
			
		||||
       'workspaceTabBackgroundColor', 'workspaceCalendarRoot', 'workspaceTemplate', 'searchHome', 'workspaceInbox',
 | 
			
		||||
       'workspaceSearchHome', 'sqlConsoleHome', 'datePattern', 'pageSize', 'viewType', 'mapRootNoteId',
 | 
			
		||||
       'bookmarkFolder', 'sorted', 'sortDirection', 'sortFoldersFirst', 'sortNatural', 'sortLocale', 'top',
 | 
			
		||||
       'fullContentWidth', 'shareHiddenFromTree', 'shareOmitDefaultCss', 'shareRoot', 'shareDescription',
 | 
			
		||||
       'fullContentWidth', 'shareHiddenFromTree', 'shareExternalLink', 'shareOmitDefaultCss', 'shareRoot', 'shareDescription',
 | 
			
		||||
       'shareRaw', 'shareDisallowRobotIndexing', 'shareIndex', 'displayRelations', 'hideRelations', 'titleTemplate',
 | 
			
		||||
       'template', 'toc', 'color', 'keepCurrentHoisting', 'executeButton', 'executeDescription', 'newNotesOnTop',
 | 
			
		||||
       'clipperInbox', 'internalLink', 'imageLink', 'relationMapLink', 'includeMapLink', 'runOnNoteCreation',
 | 
			
		||||
       'runOnNoteTitleChange', 'runOnNoteChange', 'runOnNoteContentChange', 'runOnNoteDeletion', 'runOnBranchCreation',
 | 
			
		||||
       'runOnBranchDeletion', 'runOnChildNoteCreation', 'runOnAttributeCreation', 'runOnAttributeChange', 'template',
 | 
			
		||||
       'inherit', 'widget', 'renderNote', 'shareCss', 'shareJs', 'shareFavicon');
 | 
			
		||||
       'inherit', 'widget', 'renderNote', 'shareCss', 'shareJs', 'shareTemplate', 'shareFavicon');
 | 
			
		||||
UPDATE attributes SET name = 'name' WHERE type = 'relation'
 | 
			
		||||
  AND name NOT IN
 | 
			
		||||
      ('inbox', 'disableVersioning', 'calendarRoot', 'archived', 'excludeFromExport', 'disableInclusion', 'appCss',
 | 
			
		||||
@ -33,13 +33,13 @@ UPDATE attributes SET name = 'name' WHERE type = 'relation'
 | 
			
		||||
       'workspaceTabBackgroundColor', 'workspaceCalendarRoot', 'workspaceTemplate', 'searchHome', 'workspaceInbox',
 | 
			
		||||
       'workspaceSearchHome', 'sqlConsoleHome', 'datePattern', 'pageSize', 'viewType', 'mapRootNoteId',
 | 
			
		||||
       'bookmarkFolder', 'sorted', 'sortDirection', 'sortFoldersFirst', 'sortNatural', 'sortLocale', 'top',
 | 
			
		||||
       'fullContentWidth', 'shareHiddenFromTree', 'shareOmitDefaultCss', 'shareRoot', 'shareDescription',
 | 
			
		||||
       'fullContentWidth', 'shareHiddenFromTree', 'shareExternalLink', 'shareOmitDefaultCss', 'shareRoot', 'shareDescription',
 | 
			
		||||
       'shareRaw', 'shareDisallowRobotIndexing', 'shareIndex', 'displayRelations', 'hideRelations', 'titleTemplate',
 | 
			
		||||
       'template', 'toc', 'color', 'keepCurrentHoisting', 'executeButton', 'executeDescription', 'newNotesOnTop',
 | 
			
		||||
       'clipperInbox', 'internalLink', 'imageLink', 'relationMapLink', 'includeMapLink', 'runOnNoteCreation',
 | 
			
		||||
       'runOnNoteTitleChange', 'runOnNoteChange', 'runOnNoteContentChange', 'runOnNoteDeletion', 'runOnBranchCreation',
 | 
			
		||||
       'runOnBranchDeletion', 'runOnChildNoteCreation', 'runOnAttributeCreation', 'runOnAttributeChange', 'template',
 | 
			
		||||
       'inherit', 'widget', 'renderNote', 'shareCss', 'shareJs', 'shareFavicon');
 | 
			
		||||
       'inherit', 'widget', 'renderNote', 'shareCss', 'shareJs', 'shareTemplate', 'shareFavicon');
 | 
			
		||||
UPDATE branches SET prefix = 'prefix' WHERE prefix IS NOT NULL AND prefix != 'recovered';
 | 
			
		||||
UPDATE options SET value = 'anonymized' WHERE name IN
 | 
			
		||||
      ('documentId', 'documentSecret', 'encryptedDataKey',
 | 
			
		||||
 | 
			
		||||
@ -225,6 +225,7 @@ const ATTR_HELP = {
 | 
			
		||||
        "sqlConsoleHome": "default location of SQL console notes",
 | 
			
		||||
        "bookmarkFolder": "note with this label will appear in bookmarks as folder (allowing access to its children)",
 | 
			
		||||
        "shareHiddenFromTree": "this note is hidden from left navigation tree, but still accessible with its URL",
 | 
			
		||||
        "shareExternalLink": "note will act as a link to an external website in the share tree",
 | 
			
		||||
        "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.",
 | 
			
		||||
@ -271,6 +272,7 @@ const ATTR_HELP = {
 | 
			
		||||
        "widget": "target of this relation will be executed and rendered as a widget in the sidebar",
 | 
			
		||||
        "shareCss": "CSS note which will be injected into the share page. CSS note must be in the shared sub-tree as well. Consider using 'shareHiddenFromTree' and 'shareOmitDefaultCss' as well.",
 | 
			
		||||
        "shareJs": "JavaScript note which will be injected into the share page. JS note must be in the shared sub-tree as well. Consider using 'shareHiddenFromTree'.",
 | 
			
		||||
        "shareTemplate": "Embedded JavaScript note that will be used as the template for displaying the shared note. Falls back to the default template. Consider using 'shareHiddenFromTree'.",
 | 
			
		||||
        "shareFavicon": "Favicon note to be set in the shared page. Typically you want to set it to share root and make it inheritable. Favicon note must be in the shared sub-tree as well. Consider using 'shareHiddenFromTree'.",
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -48,6 +48,7 @@ module.exports = [
 | 
			
		||||
    { type: 'label', name: 'bottom' },
 | 
			
		||||
    { type: 'label', name: 'fullContentWidth' },
 | 
			
		||||
    { type: 'label', name: 'shareHiddenFromTree' },
 | 
			
		||||
    { type: 'label', name: 'shareExternalLink' },
 | 
			
		||||
    { type: 'label', name: 'shareAlias' },
 | 
			
		||||
    { type: 'label', name: 'shareOmitDefaultCss' },
 | 
			
		||||
    { type: 'label', name: 'shareRoot' },
 | 
			
		||||
@ -89,5 +90,6 @@ module.exports = [
 | 
			
		||||
    { type: 'relation', name: 'renderNote', isDangerous: true },
 | 
			
		||||
    { type: 'relation', name: 'shareCss' },
 | 
			
		||||
    { type: 'relation', name: 'shareJs' },
 | 
			
		||||
    { type: 'relation', name: 'shareTemplate' },
 | 
			
		||||
    { type: 'relation', name: 'shareFavicon' },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@ -44,7 +44,10 @@ function renderIndex(result) {
 | 
			
		||||
    const rootNote = shaca.getNote(shareRoot.SHARE_ROOT_NOTE_ID);
 | 
			
		||||
 | 
			
		||||
    for (const childNote of rootNote.getChildNotes()) {
 | 
			
		||||
        result.content += `<li><a class="${childNote.type}" href="./${childNote.shareId}">${childNote.escapedTitle}</a></li>`;
 | 
			
		||||
        const isExternalLink = childNote.hasLabel("shareExternalLink");
 | 
			
		||||
        const href = isExternalLink ? childNote.getLabelValue("shareExternalLink") : `./${childNote.shareId}`;
 | 
			
		||||
        const target = isExternalLink ? `target="_blank" rel="noopener noreferrer"` : "";
 | 
			
		||||
        result.content += `<li><a class="${childNote.type}" href="${href}" ${target}>${childNote.escapedTitle}</a></li>`;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    result.content += '</ul>';
 | 
			
		||||
@ -84,7 +87,13 @@ function renderText(result, note) {
 | 
			
		||||
                const noteId = notePathSegments[notePathSegments.length - 1];
 | 
			
		||||
                const linkedNote = shaca.getNote(noteId);
 | 
			
		||||
                if (linkedNote) {
 | 
			
		||||
                    linkEl.setAttribute("href", linkedNote.shareId);
 | 
			
		||||
                    const isExternalLink = linkedNote.hasLabel("shareExternalLink");
 | 
			
		||||
                    const href = isExternalLink ? linkedNote.getLabelValue("shareExternalLink") : `./${linkedNote.shareId}`;
 | 
			
		||||
                    linkEl.setAttribute("href", href);
 | 
			
		||||
                    if (isExternalLink) {
 | 
			
		||||
                        linkEl.setAttribute("target", "_blank");
 | 
			
		||||
                        linkEl.setAttribute("rel", "noopener noreferrer");
 | 
			
		||||
                    }
 | 
			
		||||
                    linkEl.classList.add(`type-${linkedNote.type}`);
 | 
			
		||||
                } else {
 | 
			
		||||
                    linkEl.removeAttribute("href");
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
const express = require('express');
 | 
			
		||||
const path = require('path');
 | 
			
		||||
const safeCompare = require('safe-compare');
 | 
			
		||||
const ejs = require("ejs");
 | 
			
		||||
 | 
			
		||||
const shaca = require("./shaca/shaca");
 | 
			
		||||
const shacaLoader = require("./shaca/shaca_loader");
 | 
			
		||||
@ -8,6 +9,9 @@ const shareRoot = require("./share_root");
 | 
			
		||||
const contentRenderer = require("./content_renderer");
 | 
			
		||||
const assetPath = require("../services/asset_path");
 | 
			
		||||
const appPath = require("../services/app_path");
 | 
			
		||||
const searchService = require("../services/search/services/search");
 | 
			
		||||
const SearchContext = require("../services/search/search_context");
 | 
			
		||||
const log = require("../services/log");
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param {SNote} note
 | 
			
		||||
@ -128,18 +132,42 @@ function register(router) {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const {header, content, isEmpty} = contentRenderer.getContent(note);
 | 
			
		||||
 | 
			
		||||
        const subRoot = getSharedSubTreeRoot(note);
 | 
			
		||||
        const opts = {note, header, content, isEmpty, subRoot, assetPath, appPath};
 | 
			
		||||
        let useDefaultView = true;
 | 
			
		||||
 | 
			
		||||
        res.render("share/page", {
 | 
			
		||||
            note,
 | 
			
		||||
            header,
 | 
			
		||||
            content,
 | 
			
		||||
            isEmpty,
 | 
			
		||||
            subRoot,
 | 
			
		||||
            assetPath,
 | 
			
		||||
            appPath
 | 
			
		||||
        });
 | 
			
		||||
        // Check if the user has their own template
 | 
			
		||||
        if (note.hasRelation('shareTemplate')) {
 | 
			
		||||
            // Get the template note and content
 | 
			
		||||
            const templateId = note.getRelation('shareTemplate').value;
 | 
			
		||||
            const templateNote = shaca.getNote(templateId);
 | 
			
		||||
 | 
			
		||||
            // Make sure the note type is correct
 | 
			
		||||
            if (templateNote.type === 'code' && templateNote.mime === 'application/x-ejs') {
 | 
			
		||||
 | 
			
		||||
                // EJS caches the result of this so we don't need to pre-cache
 | 
			
		||||
                const includer = (path) => {
 | 
			
		||||
                    const childNote = templateNote.children.find(n => path === n.title);
 | 
			
		||||
                    if (!childNote) return null;
 | 
			
		||||
                    if (childNote.type !== 'code' || childNote.mime !== 'application/x-ejs') return null;
 | 
			
		||||
                    return { template: childNote.getContent() };
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                // Try to render user's template, w/ fallback to default view
 | 
			
		||||
                try {
 | 
			
		||||
                    const ejsResult = ejs.render(templateNote.getContent(), opts, {includer});
 | 
			
		||||
                    res.send(ejsResult);
 | 
			
		||||
                    useDefaultView = false; // Rendering went okay, don't use default view
 | 
			
		||||
                }
 | 
			
		||||
                catch (e) {
 | 
			
		||||
                    log.error(`Rendering user provided share template (${templateId}) threw exception ${e.message} with stacktrace: ${e.stack}`);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (useDefaultView) {
 | 
			
		||||
            res.render('share/page', opts);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    router.get('/share/', (req, res, next) => {
 | 
			
		||||
@ -303,6 +331,37 @@ function register(router) {
 | 
			
		||||
 | 
			
		||||
        res.send(note.getContent());
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // Used for searching, require noteId so we know the subTreeRoot
 | 
			
		||||
    router.get('/share/api/notes', (req, res, next) => {
 | 
			
		||||
        shacaLoader.ensureLoad();
 | 
			
		||||
 | 
			
		||||
        const ancestorNoteId = req.query.ancestorNoteId ?? "_share";
 | 
			
		||||
        let note;
 | 
			
		||||
 | 
			
		||||
        // This will automatically return if no ancestorNoteId is provided and there is no shareIndex
 | 
			
		||||
        if (!(note = checkNoteAccess(ancestorNoteId, req, res))) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const {search} = req.query;
 | 
			
		||||
 | 
			
		||||
        if (!search?.trim()) {
 | 
			
		||||
            return res.status(400).json({ message: "'search' parameter is mandatory." });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const searchContext = new SearchContext({ancestorNoteId: ancestorNoteId});
 | 
			
		||||
        const searchResults = searchService.findResultsWithQuery(search, searchContext);
 | 
			
		||||
        const filteredResults = searchResults.map(sr => {
 | 
			
		||||
            const fullNote = shaca.notes[sr.noteId];
 | 
			
		||||
            const startIndex = sr.notePathArray.indexOf(ancestorNoteId);
 | 
			
		||||
            const localPathArray = sr.notePathArray.slice(startIndex + 1).filter(id => shaca.notes[id]);
 | 
			
		||||
            const pathTitle = localPathArray.map(id => shaca.notes[id].title).join(" / ");
 | 
			
		||||
            return { id: fullNote.shareId, title: fullNote.title, score: sr.score, path: pathTitle };
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        res.json({ results: filteredResults });
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
 | 
			
		||||
@ -32,7 +32,7 @@
 | 
			
		||||
    <%- header %>
 | 
			
		||||
    <title><%= note.title %></title>
 | 
			
		||||
</head>
 | 
			
		||||
<body data-note-id="<%= note.noteId %>">
 | 
			
		||||
<body data-note-id="<%= note.noteId %>" data-ancestor-note-id="<%= subRoot.note.noteId %>">
 | 
			
		||||
<div id="layout">
 | 
			
		||||
    <div id="main">
 | 
			
		||||
        <% if (note.parents[0].noteId !== '_share' && note.parents.length !== 0) { %>
 | 
			
		||||
@ -62,9 +62,14 @@
 | 
			
		||||
                <% } %>
 | 
			
		||||
 | 
			
		||||
                <ul>
 | 
			
		||||
                    <% for (const childNote of note.getVisibleChildNotes()) { %>
 | 
			
		||||
                    <%
 | 
			
		||||
                    for (const childNote of note.getVisibleChildNotes()) {
 | 
			
		||||
                        const isExternalLink = childNote.hasLabel('shareExternalLink');
 | 
			
		||||
                        const linkHref = isExternalLink ? childNote.getLabelValue('shareExternalLink') : `./${childNote.shareId}`;
 | 
			
		||||
                        const target = isExternalLink ? `target="_blank" rel="noopener noreferrer"` : '';
 | 
			
		||||
                    %>
 | 
			
		||||
                        <li>
 | 
			
		||||
                            <a href="<%= childNote.shareId %>"
 | 
			
		||||
                            <a href="<%= linkHref %>" <%= target %>
 | 
			
		||||
                               class="type-<%= childNote.type %>"><%= childNote.title %></a>
 | 
			
		||||
                        </li>
 | 
			
		||||
                    <% } %>
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,15 @@
 | 
			
		||||
<%
 | 
			
		||||
const isExternalLink = note.hasLabel('shareExternalLink');
 | 
			
		||||
const linkHref = isExternalLink ? note.getLabelValue('shareExternalLink') : `./${note.shareId}`;
 | 
			
		||||
const target = isExternalLink ? ` target="_blank" rel="noopener noreferrer"` : '';
 | 
			
		||||
%>
 | 
			
		||||
<p>
 | 
			
		||||
    <% const titleWithPrefix = (branch.prefix ? `${branch.prefix} - ` : '') + note.title; %>
 | 
			
		||||
 | 
			
		||||
    <% if (activeNote.noteId === note.noteId) { %>
 | 
			
		||||
    <strong><%= titleWithPrefix %></strong>
 | 
			
		||||
    <% } else { %>
 | 
			
		||||
    <a class="type-<%= note.type %>" href="./<%= note.shareId %>"><%= titleWithPrefix %></a>
 | 
			
		||||
    <a class="type-<%= note.type %>" href="<%= linkHref %>"<%= target %>><%= titleWithPrefix %></a>
 | 
			
		||||
    <% } %>
 | 
			
		||||
</p>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user