mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
Merge pull request #4298 from rauenzi/share
Add ~shareTemplate and #shareExternalLink
This commit is contained in:
commit
db8014c0ef
@ -13,13 +13,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',
|
||||
@ -30,13 +30,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