Compare commits

...

29 Commits

Author SHA1 Message Date
zadam
5a85fe92aa release 0.49.2-beta 2022-01-02 22:43:30 +01:00
zadam
feffd57f24 added support for #pageUrl into shared notes 2022-01-02 22:43:00 +01:00
zadam
faf81ae056 fix closing new window, closes #2502 2022-01-02 21:35:02 +01:00
zadam
003fec4b11 fix interrupted initial sync 2022-01-02 21:20:56 +01:00
zadam
5ecb603e86 sharing fixes 2022-01-01 22:32:38 +01:00
zadam
1fed71a92e shaca now loads attributes, added favicon and shareJs 2022-01-01 13:23:09 +01:00
zadam
dad82ea4e8 Merge remote-tracking branch 'origin/stable'
# Conflicts:
#	package.json
#	src/services/build.js
2021-12-31 20:16:31 +01:00
zadam
067251861d Merge remote-tracking branch 'origin/stable' into stable 2021-12-30 21:06:52 +01:00
zadam
6bc8773d5f handle OPML with empty content, fixes #2495 2021-12-30 21:06:45 +01:00
Matt
a910034c96 Fix math rendering (#2487) 2021-12-28 20:12:02 +01:00
zadam
257cc66f62 ignore missing image notes when downloading/replacing images, #2486 2021-12-28 20:08:17 +01:00
zadam
00f24bdb63 share improvements 2021-12-28 13:57:37 +01:00
zadam
fada3fe623 share improvements 2021-12-28 13:40:51 +01:00
zadam
d97e454463 allow cloning into notes, not just branches, fixes #2484 2021-12-27 23:39:46 +01:00
zadam
3128a7d62f various tweaks to shared notes 2021-12-27 20:48:14 +01:00
zadam
b8fe9a41db fix consistency checks 2021-12-27 13:37:51 +01:00
Matt
8366a94bde Various share page improvements (#2471)
* Add clientside mermaid chart rendering

Merry Christmas :)

* Add katex math rendering client-side

* Update page.ejs

* Revert (wrong branch)

* Add children nodes to all notes under hr

* Add parent note button

* Add note type in child note info

* Fix parent, relative paths

* Add code rendering, fix space in HTML class
2021-12-27 13:27:00 +01:00
zadam
f56123b864 fix missing branch in case of out-of-order clones, closes #2464 2021-12-26 23:31:54 +01:00
DHMike57
ae951bfe23 Add option for vim keymap in codemirror (#2475) 2021-12-26 13:24:18 +01:00
zadam
ad8d35efe9 release 0.49.1-beta 2021-12-24 23:05:10 +01:00
zadam
0217b1c85d share pdf view is responsive 2021-12-24 22:46:55 +01:00
zadam
c0aa14f586 Merge remote-tracking branch 'origin/master' 2021-12-24 22:43:25 +01:00
zadam
b54cfab4ff share root itself is not shared, fixes #2468 2021-12-24 22:43:12 +01:00
Matt
a08985e7a6 Display PDF in shared notes (#2466)
* Add PDF rendering

* Cleanup
2021-12-24 22:36:31 +01:00
zadam
a789025025 don't show share switch for root and share root notes, #2465 2021-12-24 22:34:15 +01:00
zadam
3f307b117e fix webpack build 2021-12-24 22:18:05 +01:00
zadam
a232035d47 fix backlink count 2021-12-24 21:01:36 +01:00
zadam
9d38e9342d moved API docs button to the bottom of a code note 2021-12-24 20:40:27 +01:00
zadam
265401775b release 0.48.9 2021-12-22 22:39:24 +01:00
46 changed files with 6196 additions and 162 deletions

5734
libraries/codemirror/keymap/vim.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
"name": "trilium", "name": "trilium",
"productName": "Trilium Notes", "productName": "Trilium Notes",
"description": "Trilium Notes", "description": "Trilium Notes",
"version": "0.49.0-beta", "version": "0.49.2-beta",
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"main": "electron.js", "main": "electron.js",
"bin": { "bin": {

View File

@@ -1114,7 +1114,7 @@ class Note extends AbstractEntity {
const branch = this.becca.getNote(parentNoteId).getParentBranches()[0]; const branch = this.becca.getNote(parentNoteId).getParentBranches()[0];
return cloningService.cloneNoteToParent(this.noteId, branch.branchId); return cloningService.cloneNoteToBranch(this.noteId, branch.branchId);
} }
decrypt() { decrypt() {

View File

@@ -48,7 +48,7 @@ async function cloneNotesTo(notePath) {
const targetBranchId = await froca.getBranchId(parentNoteId, noteId); const targetBranchId = await froca.getBranchId(parentNoteId, noteId);
for (const cloneNoteId of clonedNoteIds) { for (const cloneNoteId of clonedNoteIds) {
await branchService.cloneNoteTo(cloneNoteId, targetBranchId, $clonePrefix.val()); await branchService.cloneNoteToBranch(cloneNoteId, targetBranchId, $clonePrefix.val());
const clonedNote = await froca.getNote(cloneNoteId); const clonedNote = await froca.getNote(cloneNoteId);
const targetNote = await froca.getBranch(targetBranchId).getNote(); const targetNote = await froca.getBranch(targetBranchId).getNote();

View File

@@ -1,7 +1,15 @@
import mimeTypesService from "../../services/mime_types.js"; import mimeTypesService from "../../services/mime_types.js";
import options from "../../services/options.js"; import options from "../../services/options.js";
import server from "../../services/server.js";
import toastService from "../../services/toast.js";
import utils from "../../services/utils.js";
const TPL = ` const TPL = `
<h4>Use vim keybindings in CodeNotes (no ex mode)</h4>
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="vim-keymap-enabled">
<label class="custom-control-label" for="vim-keymap-enabled">Enable Vim Keybindings</label>
</div>
<h4>Available MIME types in the dropdown</h4> <h4>Available MIME types in the dropdown</h4>
<ul id="options-mime-types" style="max-height: 500px; overflow: auto; list-style-type: none;"></ul>`; <ul id="options-mime-types" style="max-height: 500px; overflow: auto; list-style-type: none;"></ul>`;
@@ -10,12 +18,18 @@ export default class CodeNotesOptions {
constructor() { constructor() {
$("#options-code-notes").html(TPL); $("#options-code-notes").html(TPL);
this.$vimKeymapEnabled = $("#vim-keymap-enabled");
this.$vimKeymapEnabled.on('change', () => {
const opts = { 'vimKeymapEnabled': this.$vimKeymapEnabled.is(":checked") ? "true" : "false" };
server.put('options', opts).then(() => toastService.showMessage("Options change have been saved."));
return false;
});
this.$mimeTypes = $("#options-mime-types"); this.$mimeTypes = $("#options-mime-types");
} }
async optionsLoaded() { async optionsLoaded(options) {
this.$mimeTypes.empty(); this.$mimeTypes.empty();
this.$vimKeymapEnabled.prop("checked", options['vimKeymapEnabled'] === 'true');
let idCtr = 1; let idCtr = 1;
for (const mimeType of await mimeTypesService.getMimeTypes()) { for (const mimeType of await mimeTypesService.getMimeTypes()) {
@@ -45,4 +59,4 @@ export default class CodeNotesOptions {
mimeTypesService.loadMimeTypes(); mimeTypesService.loadMimeTypes();
} }
} }

View File

@@ -196,8 +196,18 @@ ws.subscribeToMessages(async message => {
} }
}); });
async function cloneNoteTo(childNoteId, parentBranchId, prefix) { async function cloneNoteToBranch(childNoteId, parentBranchId, prefix) {
const resp = await server.put(`notes/${childNoteId}/clone-to/${parentBranchId}`, { const resp = await server.put(`notes/${childNoteId}/clone-to-branch/${parentBranchId}`, {
prefix: prefix
});
if (!resp.success) {
alert(resp.message);
}
}
async function cloneNoteToNote(childNoteId, parentNoteId, prefix) {
const resp = await server.put(`notes/${childNoteId}/clone-to-note/${parentNoteId}`, {
prefix: prefix prefix: prefix
}); });
@@ -222,5 +232,6 @@ export default {
deleteNotes, deleteNotes,
moveNodeUpInHierarchy, moveNodeUpInHierarchy,
cloneNoteAfter, cloneNoteAfter,
cloneNoteTo cloneNoteToBranch,
cloneNoteToNote,
}; };

View File

@@ -51,7 +51,7 @@ async function pasteInto(parentBranchId) {
for (const clipboardBranch of clipboardBranches) { for (const clipboardBranch of clipboardBranches) {
const clipboardNote = await clipboardBranch.getNote(); const clipboardNote = await clipboardBranch.getNote();
await branchService.cloneNoteTo(clipboardNote.noteId, parentBranchId); await branchService.cloneNoteToBranch(clipboardNote.noteId, parentBranchId);
} }
// copy will keep clipboardBranchIds and clipboardMode so it's possible to paste into multiple places // copy will keep clipboardBranchIds and clipboardMode so it's possible to paste into multiple places

View File

@@ -10,6 +10,7 @@ const CODE_MIRROR = {
"libraries/codemirror/addon/edit/matchtags.js", "libraries/codemirror/addon/edit/matchtags.js",
"libraries/codemirror/addon/search/match-highlighter.js", "libraries/codemirror/addon/search/match-highlighter.js",
"libraries/codemirror/mode/meta.js", "libraries/codemirror/mode/meta.js",
"libraries/codemirror/keymap/vim.js",
"libraries/codemirror/addon/lint/lint.js", "libraries/codemirror/addon/lint/lint.js",
"libraries/codemirror/addon/lint/eslint.js" "libraries/codemirror/addon/lint/eslint.js"
], ],

21
src/public/app/share.js Normal file
View File

@@ -0,0 +1,21 @@
/**
* Fetch note with given ID from backend
*
* @param noteId of the given note to be fetched. If falsy, fetches current note.
*/
async function fetchNote(noteId = null) {
if (!noteId) {
noteId = document.body.getAttribute("data-note-id");
}
const resp = await fetch(`api/notes/${noteId}`);
return await resp.json();
}
document.addEventListener('DOMContentLoaded', () => {
const toggleMenuButton = document.getElementById('toggleMenuButton');
const layout = document.getElementById('layout');
toggleMenuButton.addEventListener('click', () => layout.classList.toggle('showMenu'));
}, false);

View File

@@ -226,6 +226,8 @@ const ATTR_HELP = {
"renderNote": 'notes of type "render HTML note" will be rendered using a code note (HTML or script) and it is necessary to point using this relation to which note should be rendered', "renderNote": 'notes of type "render HTML note" will be rendered using a code note (HTML or script) and it is necessary to point using this relation to which note should be rendered',
"widget": "target of this relation will be executed and rendered as a widget in the sidebar", "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.", "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'.",
"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'.",
} }
}; };

View File

@@ -103,17 +103,19 @@ export default class BacklinksWidget extends NoteContextAwareWidget {
async refreshWithNote(note) { async refreshWithNote(note) {
this.clearItems(); this.clearItems();
const targetRelationCount = note.getTargetRelations().length; // can't use froca since that would count only relations from loaded notes
if (targetRelationCount === 0) { const resp = await server.get(`notes/${this.noteId}/backlink-count`);
if (!resp || !resp.count) {
this.$ticker.hide(); this.$ticker.hide();
return;
} }
else {
this.$ticker.show(); this.$ticker.show();
this.$count.text( this.$count.text(
`${targetRelationCount} backlink` `${resp.count} backlink`
+ (targetRelationCount === 1 ? '' : 's') + (resp.count === 1 ? '' : 's')
); );
}
} }
clearItems() { clearItems() {
@@ -136,18 +138,22 @@ export default class BacklinksWidget extends NoteContextAwareWidget {
await froca.getNotes(backlinks.map(bl => bl.noteId)); // prefetch all await froca.getNotes(backlinks.map(bl => bl.noteId)); // prefetch all
for (const backlink of backlinks) { for (const backlink of backlinks) {
this.$items.append(await linkService.createNoteLink(backlink.noteId, { const $item = $("<div>");
$item.append(await linkService.createNoteLink(backlink.noteId, {
showNoteIcon: true, showNoteIcon: true,
showNotePath: true, showNotePath: true,
showTooltip: false showTooltip: false
})); }));
if (backlink.relationName) { if (backlink.relationName) {
this.$items.append($("<p>").text("relation: " + backlink.relationName)); $item.append($("<p>").text("relation: " + backlink.relationName));
} }
else { else {
this.$items.append(...backlink.excerpts); $item.append(...backlink.excerpts);
} }
this.$items.append($item);
} }
} }
} }

View File

@@ -18,7 +18,7 @@ const TPL = `
export default class SharedInfoWidget extends NoteContextAwareWidget { export default class SharedInfoWidget extends NoteContextAwareWidget {
isEnabled() { isEnabled() {
return super.isEnabled() && this.note.hasAncestor('share'); return super.isEnabled() && this.noteId !== 'share' && this.note.hasAncestor('share');
} }
doRender() { doRender() {

View File

@@ -4,6 +4,10 @@ import server from "../services/server.js";
import utils from "../services/utils.js"; import utils from "../services/utils.js";
export default class SharedSwitchWidget extends SwitchWidget { export default class SharedSwitchWidget extends SwitchWidget {
isEnabled() {
return super.isEnabled() && this.noteId !== 'root' && this.noteId !== 'share';
}
doRender() { doRender() {
super.doRender(); super.doRender();
@@ -18,7 +22,7 @@ export default class SharedSwitchWidget extends SwitchWidget {
} }
switchOn() { switchOn() {
branchService.cloneNoteTo(this.noteId, 'share'); branchService.cloneNoteToNote(this.noteId, 'share');
} }
async switchOff() { async switchOff() {

View File

@@ -6,6 +6,7 @@ import ws from "../../services/ws.js";
import appContext from "../../services/app_context.js"; import appContext from "../../services/app_context.js";
import toastService from "../../services/toast.js"; import toastService from "../../services/toast.js";
import treeService from "../../services/tree.js"; import treeService from "../../services/tree.js";
import options from "../../services/options.js";
const TPL = ` const TPL = `
<div class="note-detail-code note-detail-printable"> <div class="note-detail-code note-detail-printable">
@@ -14,20 +15,10 @@ const TPL = `
position: relative; position: relative;
} }
.trilium-api-docs-button {
/*display: none;*/
position: absolute;
top: 10px;
right: 10px;
}
.note-detail-code-editor { .note-detail-code-editor {
min-height: 50px; min-height: 50px;
} }
</style> </style>
<button class="btn bx bx-help-circle trilium-api-docs-button icon-button floating-button"
title="Open Trilium API docs"></button>
<div class="note-detail-code-editor"></div> <div class="note-detail-code-editor"></div>
@@ -37,6 +28,13 @@ const TPL = `
Execute <kbd data-command="runActiveNote"></kbd> Execute <kbd data-command="runActiveNote"></kbd>
</button> </button>
<button class="no-print trilium-api-docs-button btn btn-sm"
title="Open Trilium API docs">
<span class="bx bx-help-circle"></span>
API docs
</button>
<button class="no-print save-to-note-button btn btn-sm"> <button class="no-print save-to-note-button btn btn-sm">
<span class="bx bx-save"></span> <span class="bx bx-save"></span>
@@ -97,6 +95,7 @@ export default class EditableCodeTypeWidget extends TypeWidget {
viewportMargin: Infinity, viewportMargin: Infinity,
indentUnit: 4, indentUnit: 4,
matchBrackets: true, matchBrackets: true,
keyMap: options.is('vimKeymapEnabled') ? "vim": "default",
matchTags: {bothTags: true}, matchTags: {bothTags: true},
highlightSelectionMatches: {showToken: /\w/, annotateScrollbar: false}, highlightSelectionMatches: {showToken: /\w/, annotateScrollbar: false},
lint: true, lint: true,

View File

@@ -32,15 +32,18 @@ body {
#main { #main {
flex-basis: 0; flex-basis: 0;
flex-grow: 3; flex-grow: 3;
overflow: auto;
padding: 10px 20px 20px 20px;
}
#parentLink {
float: right;
margin-top: 20px;
} }
#title { #title {
margin: 0; margin: 0;
padding: 20px 20px 0 20px; padding-top: 10px;
}
#content {
padding: 20px;
} }
img { img {
@@ -52,7 +55,12 @@ pre {
word-wrap: anywhere; word-wrap: anywhere;
} }
#menuButton { iframe.pdf-view {
width: 100%;
height: 800px;
}
#toggleMenuButton {
display: none; display: none;
position: fixed; position: fixed;
top: 8px; top: 8px;
@@ -67,23 +75,74 @@ pre {
cursor: pointer; cursor: pointer;
} }
#menuButton::after { #childLinks.grid ul {
list-style-type: none;
display: flex;
flex-wrap: wrap;
padding: 0;
}
#childLinks.grid ul li {
width: 180px;
height: 140px;
padding: 10px;
}
#childLinks.grid ul li a {
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
border: 1px solid #ddd;
border-radius: 5px;
justify-content: center;
align-content: center;
text-align: center;
font-size: large;
}
#childLinks.grid ul li a:hover {
background: #eee;
}
#childLinks.list ul {
list-style-type: none;
display: inline-flex;
flex-wrap: wrap;
padding: 0;
margin-top: 5px;
}
#childLinks.list ul li {
margin-right: 20px;
}
#noteClippedFrom {
padding: 10px 0 10px 0;
margin: 20px 0 20px 0;
color: #666;
border: 1px solid #ddd;
border-left: 0;
border-right: 0;
}
#toggleMenuButton::after {
position: relative; position: relative;
top: -2px; top: -2px;
left: 1px; left: 1px;
} }
@media (max-width: 48em) { @media (max-width: 48em) {
#layout.navMenu #menu { #layout.showMenu #menu {
display: block; display: block;
margin-top: 40px; margin-top: 40px;
} }
#menuButton { #toggleMenuButton {
display: block; display: block;
} }
#layout.navMenu #main { #layout.showMenu #main {
display: none; display: none;
} }
@@ -91,11 +150,11 @@ pre {
padding-left: 60px; padding-left: 60px;
} }
#layout.navMenu #menuButton::after { #layout.showMenu #toggleMenuButton::after {
content: "«"; content: "«";
} }
#menuButton::after { #toggleMenuButton::after {
content: "»"; content: "»";
} }

View File

@@ -2,11 +2,18 @@
const cloningService = require('../../services/cloning'); const cloningService = require('../../services/cloning');
function cloneNoteToParent(req) { function cloneNoteToBranch(req) {
const {noteId, parentBranchId} = req.params; const {noteId, parentBranchId} = req.params;
const {prefix} = req.body; const {prefix} = req.body;
return cloningService.cloneNoteToParent(noteId, parentBranchId, prefix); return cloningService.cloneNoteToBranch(noteId, parentBranchId, prefix);
}
function cloneNoteToNote(req) {
const {noteId, parentNoteId} = req.params;
const {prefix} = req.body;
return cloningService.cloneNoteToNote(noteId, parentNoteId, prefix);
} }
function cloneNoteAfter(req) { function cloneNoteAfter(req) {
@@ -16,6 +23,7 @@ function cloneNoteAfter(req) {
} }
module.exports = { module.exports = {
cloneNoteToParent, cloneNoteToBranch,
cloneNoteToNote,
cloneNoteAfter cloneNoteAfter
}; };

View File

@@ -302,6 +302,21 @@ function uploadModifiedFile(req) {
note.setContent(fileContent); note.setContent(fileContent);
} }
function getBacklinkCount(req) {
const {noteId} = req.params;
const note = becca.getNote(noteId);
if (!note) {
return [404, "Not found"];
}
else {
return {
count: note.getTargetRelations().length
};
}
}
module.exports = { module.exports = {
getNote, getNote,
updateNote, updateNote,
@@ -316,5 +331,6 @@ module.exports = {
duplicateSubtree, duplicateSubtree,
eraseDeletedNotesNow, eraseDeletedNotesNow,
getDeleteNotesPreview, getDeleteNotesPreview,
uploadModifiedFile uploadModifiedFile,
getBacklinkCount
}; };

View File

@@ -33,6 +33,7 @@ const ALLOWED_OPTIONS = new Set([
'similarNotesWidget', 'similarNotesWidget',
'editedNotesWidget', 'editedNotesWidget',
'calendarWidget', 'calendarWidget',
'vimKeymapEnabled',
'codeNotesMimeTypes', 'codeNotesMimeTypes',
'spellCheckEnabled', 'spellCheckEnabled',
'spellCheckLanguageCode', 'spellCheckLanguageCode',

View File

@@ -220,6 +220,7 @@ function register(app) {
apiRoute(DELETE, '/api/notes/:noteId/revisions/:noteRevisionId', noteRevisionsApiRoute.eraseNoteRevision); apiRoute(DELETE, '/api/notes/:noteId/revisions/:noteRevisionId', noteRevisionsApiRoute.eraseNoteRevision);
route(GET, '/api/notes/:noteId/revisions/:noteRevisionId/download', [auth.checkApiAuthOrElectron], noteRevisionsApiRoute.downloadNoteRevision); route(GET, '/api/notes/:noteId/revisions/:noteRevisionId/download', [auth.checkApiAuthOrElectron], noteRevisionsApiRoute.downloadNoteRevision);
apiRoute(PUT, '/api/notes/:noteId/restore-revision/:noteRevisionId', noteRevisionsApiRoute.restoreNoteRevision); apiRoute(PUT, '/api/notes/:noteId/restore-revision/:noteRevisionId', noteRevisionsApiRoute.restoreNoteRevision);
apiRoute(GET, '/api/notes/:noteId/backlink-count', notesApiRoute.getBacklinkCount);
apiRoute(POST, '/api/notes/relation-map', notesApiRoute.getRelationMap); apiRoute(POST, '/api/notes/relation-map', notesApiRoute.getRelationMap);
apiRoute(POST, '/api/notes/erase-deleted-notes-now', notesApiRoute.eraseDeletedNotesNow); apiRoute(POST, '/api/notes/erase-deleted-notes-now', notesApiRoute.eraseDeletedNotesNow);
apiRoute(PUT, '/api/notes/:noteId/change-title', notesApiRoute.changeTitle); apiRoute(PUT, '/api/notes/:noteId/change-title', notesApiRoute.changeTitle);
@@ -228,7 +229,8 @@ function register(app) {
apiRoute(GET, '/api/edited-notes/:date', noteRevisionsApiRoute.getEditedNotesOnDate); apiRoute(GET, '/api/edited-notes/:date', noteRevisionsApiRoute.getEditedNotesOnDate);
apiRoute(PUT, '/api/notes/:noteId/clone-to/:parentBranchId', cloningApiRoute.cloneNoteToParent); apiRoute(PUT, '/api/notes/:noteId/clone-to-branch/:parentBranchId', cloningApiRoute.cloneNoteToBranch);
apiRoute(PUT, '/api/notes/:noteId/clone-to-note/:parentNoteId', cloningApiRoute.cloneNoteToNote);
apiRoute(PUT, '/api/notes/:noteId/clone-after/:afterBranchId', cloningApiRoute.cloneNoteAfter); apiRoute(PUT, '/api/notes/:noteId/clone-after/:afterBranchId', cloningApiRoute.cloneNoteAfter);
route(GET, '/api/notes/:branchId/export/:type/:format/:version/:taskId', [auth.checkApiAuthOrElectron], exportRoute.exportBranch); route(GET, '/api/notes/:branchId/export/:type/:format/:version/:taskId', [auth.checkApiAuthOrElectron], exportRoute.exportBranch);

View File

@@ -67,6 +67,8 @@ const BUILTIN_ATTRIBUTES = [
{ type: 'relation', name: 'widget', isDangerous: true }, { type: 'relation', name: 'widget', isDangerous: true },
{ type: 'relation', name: 'renderNote', isDangerous: true }, { type: 'relation', name: 'renderNote', isDangerous: true },
{ type: 'relation', name: 'shareCss', isDangerous: false }, { type: 'relation', name: 'shareCss', isDangerous: false },
{ type: 'relation', name: 'shareJs', isDangerous: false },
{ type: 'relation', name: 'shareFavicon', isDangerous: false },
]; ];
/** @returns {Note[]} */ /** @returns {Note[]} */

View File

@@ -1 +1 @@
module.exports = { buildDate:"2021-12-23T23:03:21+01:00", buildRevision: "f0217cae5eb4bdac12efe1d15bf26dc128e7f854" }; module.exports = { buildDate:"2022-01-02T22:43:30+01:00", buildRevision: "feffd57f240438d107c1ed1c1772545611a97dee" };

View File

@@ -10,20 +10,18 @@ const utils = require('./utils');
const becca = require("../becca/becca"); const becca = require("../becca/becca");
const beccaService = require("../becca/becca_service"); const beccaService = require("../becca/becca_service");
function cloneNoteToParent(noteId, parentBranchId, prefix) { function cloneNoteToNote(noteId, parentNoteId, prefix) {
if (parentBranchId === 'share') { if (parentNoteId === 'share') {
const specialNotesService = require('./special_notes'); const specialNotesService = require('./special_notes');
// share root note is created lazily // share root note is created lazily
specialNotesService.getShareRoot(); specialNotesService.getShareRoot();
} }
const parentBranch = becca.getBranch(parentBranchId); if (isNoteDeleted(noteId) || isNoteDeleted(parentNoteId)) {
if (isNoteDeleted(noteId) || isNoteDeleted(parentBranch.noteId)) {
return { success: false, message: 'Note is deleted.' }; return { success: false, message: 'Note is deleted.' };
} }
const validationResult = treeService.validateParentChild(parentBranch.noteId, noteId); const validationResult = treeService.validateParentChild(parentNoteId, noteId);
if (!validationResult.success) { if (!validationResult.success) {
return validationResult; return validationResult;
@@ -31,21 +29,33 @@ function cloneNoteToParent(noteId, parentBranchId, prefix) {
const branch = new Branch({ const branch = new Branch({
noteId: noteId, noteId: noteId,
parentNoteId: parentBranch.noteId, parentNoteId: parentNoteId,
prefix: prefix, prefix: prefix,
isExpanded: 0 isExpanded: 0
}).save(); }).save();
parentBranch.isExpanded = true; // the new target should be expanded so it immediately shows up to the user
parentBranch.save();
return { return {
success: true, success: true,
branchId: branch.branchId, branchId: branch.branchId,
notePath: beccaService.getNotePath(parentBranch.noteId).path + "/" + noteId notePath: beccaService.getNotePath(parentNoteId).path + "/" + noteId
}; };
} }
function cloneNoteToBranch(noteId, parentBranchId, prefix) {
const parentBranch = becca.getBranch(parentBranchId);
if (!parentBranch) {
return { success: false, message: `Parent branch ${parentBranchId} does not exist.` };
}
const ret = cloneNoteToNote(noteId, parentBranch.noteId, prefix);
parentBranch.isExpanded = true; // the new target should be expanded so it immediately shows up to the user
parentBranch.save();
return ret;
}
function ensureNoteIsPresentInParent(noteId, parentNoteId, prefix) { function ensureNoteIsPresentInParent(noteId, parentNoteId, prefix) {
if (isNoteDeleted(noteId) || isNoteDeleted(parentNoteId)) { if (isNoteDeleted(noteId) || isNoteDeleted(parentNoteId)) {
return { success: false, message: 'Note is deleted.' }; return { success: false, message: 'Note is deleted.' };
@@ -121,7 +131,8 @@ function isNoteDeleted(noteId) {
} }
module.exports = { module.exports = {
cloneNoteToParent, cloneNoteToBranch,
cloneNoteToNote,
ensureNoteIsPresentInParent, ensureNoteIsPresentInParent,
ensureNoteIsAbsentFromParent, ensureNoteIsAbsentFromParent,
toggleNoteInParent, toggleNoteInParent,

View File

@@ -259,7 +259,7 @@ class ConsistencyChecks {
WHERE noteId = ? WHERE noteId = ?
and parentNoteId = ? and parentNoteId = ?
and isDeleted = 0 and isDeleted = 0
ORDER BY utcDateCreated`, [noteId, parentNoteId]); ORDER BY utcDateModified`, [noteId, parentNoteId]);
const branches = branchIds.map(branchId => becca.getBranch(branchId)); const branches = branchIds.map(branchId => becca.getBranch(branchId));

View File

@@ -54,7 +54,7 @@ function getYearNote(dateStr, rootNote) {
rootNote = getRootCalendarNote(); rootNote = getRootCalendarNote();
} }
const yearStr = dateStr.substr(0, 4); const yearStr = dateStr.trim().substr(0, 4);
let yearNote = attributeService.getNoteWithLabel(YEAR_LABEL, yearStr); let yearNote = attributeService.getNoteWithLabel(YEAR_LABEL, yearStr);
@@ -138,6 +138,8 @@ function getDateNoteTitle(rootNote, dayNumber, dateObj) {
/** @returns {Note} */ /** @returns {Note} */
function getDateNote(dateStr) { function getDateNote(dateStr) {
dateStr = dateStr.trim().substr(0, 10);
let dateNote = attributeService.getNoteWithLabel(DATE_LABEL, dateStr); let dateNote = attributeService.getNoteWithLabel(DATE_LABEL, dateStr);
if (dateNote) { if (dateNote) {

View File

@@ -3,6 +3,10 @@ const sanitizeHtml = require('sanitize-html');
// intended mainly as protection against XSS via import // intended mainly as protection against XSS via import
// secondarily it (partly) protects against "CSS takeover" // secondarily it (partly) protects against "CSS takeover"
function sanitize(dirtyHtml) { function sanitize(dirtyHtml) {
if (!dirtyHtml) {
return dirtyHtml;
}
// avoid H1 per https://github.com/zadam/trilium/issues/1552 // avoid H1 per https://github.com/zadam/trilium/issues/1552
// demote H1, and if that conflicts with existing H2, demote that, etc // demote H1, and if that conflicts with existing H2, demote that, etc
const transformTags = {}; const transformTags = {};

View File

@@ -51,7 +51,7 @@ async function importOpml(taskContext, fileBuffer, parentNote) {
throw new Error("Unrecognized OPML version " + opmlVersion); throw new Error("Unrecognized OPML version " + opmlVersion);
} }
content = htmlSanitizer.sanitize(content); content = htmlSanitizer.sanitize(content || "");
const {note} = noteService.createNewNote({ const {note} = noteService.createNewNote({
parentNoteId, parentNoteId,

View File

@@ -240,13 +240,15 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
} }
if (noteMeta && noteMeta.isClone) { if (noteMeta && noteMeta.isClone) {
new Branch({ if (!becca.getBranchFromChildAndParent(noteId, parentNoteId)) {
noteId, new Branch({
parentNoteId, noteId,
isExpanded: noteMeta.isExpanded, parentNoteId,
prefix: noteMeta.prefix, isExpanded: noteMeta.isExpanded,
notePosition: noteMeta.notePosition prefix: noteMeta.prefix,
}).save(); notePosition: noteMeta.notePosition
}).save();
}
return; return;
} }
@@ -365,6 +367,16 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
} }
note.setContent(content); note.setContent(content);
if (!becca.getBranchFromChildAndParent(noteId, parentNoteId)) {
new Branch({
noteId,
parentNoteId,
isExpanded: noteMeta.isExpanded,
prefix: noteMeta.prefix,
notePosition: noteMeta.notePosition
}).save();
}
} }
else { else {
({note} = noteService.createNewNote({ ({note} = noteService.createNewNote({

View File

@@ -360,7 +360,7 @@ function downloadImages(noteId, content) {
// which upon the download of all the images will update the note if the links have not been fixed before // which upon the download of all the images will update the note if the links have not been fixed before
sql.transactional(() => { sql.transactional(() => {
const imageNotes = becca.getNotes(Object.values(imageUrlToNoteIdMapping)); const imageNotes = becca.getNotes(Object.values(imageUrlToNoteIdMapping), true);
const origNote = becca.getNote(noteId); const origNote = becca.getNote(noteId);

View File

@@ -1,7 +1,16 @@
const becca = require('../becca/becca'); const becca = require('../becca/becca');
const sql = require("./sql.js");
function getOption(name) { function getOption(name) {
const option = require('../becca/becca').getOption(name); let option;
if (becca.loaded) {
option = becca.getOption(name);
}
else {
// e.g. in initial sync becca is not loaded because DB is not initialized
option = sql.getRow("SELECT * FROM options WHERE name = ?", name);
}
if (!option) { if (!option) {
throw new Error(`Option "${name}" doesn't exist`); throw new Error(`Option "${name}" doesn't exist`);
@@ -39,12 +48,12 @@ function getOptionBool(name) {
} }
function setOption(name, value) { function setOption(name, value) {
const option = becca.getOption(name);
if (value === true || value === false) { if (value === true || value === false) {
value = value.toString(); value = value.toString();
} }
const option = becca.getOption(name);
if (option) { if (option) {
option.value = value; option.value = value;

View File

@@ -70,6 +70,7 @@ const defaultOptions = [
{ name: 'imageMaxWidthHeight', value: '2000', isSynced: true }, { name: 'imageMaxWidthHeight', value: '2000', isSynced: true },
{ name: 'imageJpegQuality', value: '75', isSynced: true }, { name: 'imageJpegQuality', value: '75', isSynced: true },
{ name: 'autoFixConsistencyIssues', value: 'true', isSynced: false }, { name: 'autoFixConsistencyIssues', value: 'true', isSynced: false },
{ name: 'vimKeymapEnabled', value: 'false', isSynced: false },
{ name: 'codeNotesMimeTypes', value: '["text/x-csrc","text/x-c++src","text/x-csharp","text/css","text/x-go","text/x-groovy","text/x-haskell","text/html","message/http","text/x-java","application/javascript;env=frontend","application/javascript;env=backend","application/json","text/x-kotlin","text/x-markdown","text/x-perl","text/x-php","text/x-python","text/x-ruby",null,"text/x-sql","text/x-sqlite;schema=trilium","text/x-swift","text/xml","text/x-yaml"]', isSynced: true }, { name: 'codeNotesMimeTypes', value: '["text/x-csrc","text/x-c++src","text/x-csharp","text/css","text/x-go","text/x-groovy","text/x-haskell","text/html","message/http","text/x-java","application/javascript;env=frontend","application/javascript;env=backend","application/json","text/x-kotlin","text/x-markdown","text/x-perl","text/x-php","text/x-python","text/x-ruby",null,"text/x-sql","text/x-sqlite;schema=trilium","text/x-swift","text/xml","text/x-yaml"]', isSynced: true },
{ name: 'leftPaneWidth', value: '25', isSynced: false }, { name: 'leftPaneWidth', value: '25', isSynced: false },
{ name: 'leftPaneVisible', value: 'true', isSynced: false }, { name: 'leftPaneVisible', value: 'true', isSynced: false },

View File

@@ -371,7 +371,10 @@ function getLastSyncedPull() {
function setLastSyncedPull(entityChangeId) { function setLastSyncedPull(entityChangeId) {
const lastSyncedPullOption = becca.getOption('lastSyncedPull'); const lastSyncedPullOption = becca.getOption('lastSyncedPull');
lastSyncedPullOption.value = entityChangeId + '';
if (lastSyncedPullOption) { // might be null in initial sync when becca is not loaded
lastSyncedPullOption.value = entityChangeId + '';
}
// this way we avoid updating entity_changes which otherwise means that we've never pushed all entity_changes // this way we avoid updating entity_changes which otherwise means that we've never pushed all entity_changes
sql.execute("UPDATE options SET value = ? WHERE name = ?", [entityChangeId, 'lastSyncedPull']); sql.execute("UPDATE options SET value = ? WHERE name = ?", [entityChangeId, 'lastSyncedPull']);
@@ -389,7 +392,10 @@ function setLastSyncedPush(entityChangeId) {
ws.setLastSyncedPush(entityChangeId); ws.setLastSyncedPush(entityChangeId);
const lastSyncedPushOption = becca.getOption('lastSyncedPush'); const lastSyncedPushOption = becca.getOption('lastSyncedPush');
lastSyncedPushOption.value = entityChangeId + '';
if (lastSyncedPushOption) { // might be null in initial sync when becca is not loaded
lastSyncedPushOption.value = entityChangeId + '';
}
// this way we avoid updating entity_changes which otherwise means that we've never pushed all entity_changes // this way we avoid updating entity_changes which otherwise means that we've never pushed all entity_changes
sql.execute("UPDATE options SET value = ? WHERE name = ?", [entityChangeId, 'lastSyncedPush']); sql.execute("UPDATE options SET value = ? WHERE name = ?", [entityChangeId, 'lastSyncedPush']);

View File

@@ -16,7 +16,10 @@ let mainWindow;
let setupWindow; let setupWindow;
async function createExtraWindow(notePath, hoistedNoteId = 'root') { async function createExtraWindow(notePath, hoistedNoteId = 'root') {
const spellcheckEnabled = optionService.getOptionBool('spellCheckEnabled');
const {BrowserWindow} = require('electron'); const {BrowserWindow} = require('electron');
const win = new BrowserWindow({ const win = new BrowserWindow({
width: 1000, width: 1000,
height: 800, height: 800,
@@ -25,7 +28,7 @@ async function createExtraWindow(notePath, hoistedNoteId = 'root') {
enableRemoteModule: true, enableRemoteModule: true,
nodeIntegration: true, nodeIntegration: true,
contextIsolation: false, contextIsolation: false,
spellcheck: optionService.getOptionBool('spellCheckEnabled') spellcheck: spellcheckEnabled
}, },
frame: optionService.getOptionBool('nativeTitleBarVisible'), frame: optionService.getOptionBool('nativeTitleBarVisible'),
icon: getIcon() icon: getIcon()
@@ -33,6 +36,8 @@ async function createExtraWindow(notePath, hoistedNoteId = 'root') {
win.setMenuBarVisibility(false); win.setMenuBarVisibility(false);
win.loadURL('http://127.0.0.1:' + await port + '/?extra=1&extraHoistedNoteId=' + hoistedNoteId + '#' + notePath); win.loadURL('http://127.0.0.1:' + await port + '/?extra=1&extraHoistedNoteId=' + hoistedNoteId + '#' + notePath);
configureWebContents(win.webContents, spellcheckEnabled);
} }
ipcMain.on('create-extra-window', (event, arg) => { ipcMain.on('create-extra-window', (event, arg) => {
@@ -59,6 +64,7 @@ async function createMainWindow() {
height: mainWindowState.height, height: mainWindowState.height,
title: 'Trilium Notes', title: 'Trilium Notes',
webPreferences: { webPreferences: {
enableRemoteModule: true,
nodeIntegration: true, nodeIntegration: true,
contextIsolation: false, contextIsolation: false,
spellcheck: spellcheckEnabled spellcheck: spellcheckEnabled
@@ -73,8 +79,10 @@ async function createMainWindow() {
mainWindow.loadURL('http://127.0.0.1:' + await port); mainWindow.loadURL('http://127.0.0.1:' + await port);
mainWindow.on('closed', () => mainWindow = null); mainWindow.on('closed', () => mainWindow = null);
const {webContents} = mainWindow; configureWebContents(mainWindow.webContents, spellcheckEnabled);
}
function configureWebContents(webContents, spellcheckEnabled) {
require("@electron/remote/main").enable(webContents); require("@electron/remote/main").enable(webContents);
webContents.on('new-window', (e, url) => { webContents.on('new-window', (e, url) => {

View File

@@ -1,43 +1,18 @@
const {JSDOM} = require("jsdom"); const {JSDOM} = require("jsdom");
const NO_CONTENT = '<p>This note has no content.</p>';
const shaca = require("./shaca/shaca"); const shaca = require("./shaca/shaca");
function getChildrenList(note) {
if (note.hasChildren()) {
const document = new JSDOM().window.document;
const ulEl = document.createElement("ul");
for (const childNote of note.getChildNotes()) {
const li = document.createElement("li");
const link = document.createElement("a");
link.appendChild(document.createTextNode(childNote.title));
link.setAttribute("href", childNote.noteId);
li.appendChild(link);
ulEl.appendChild(li);
}
return '<p>Child notes:</p>' + ulEl.outerHTML;
}
else {
return '';
}
}
function getContent(note) { function getContent(note) {
let content = note.getContent(); let content = note.getContent();
let header = '';
let isEmpty = false;
if (note.type === 'text') { if (note.type === 'text') {
const document = new JSDOM(content || "").window.document; const document = new JSDOM(content || "").window.document;
const isEmpty = document.body.textContent.trim().length === 0 isEmpty = document.body.textContent.trim().length === 0
&& document.querySelectorAll("img").length === 0; && document.querySelectorAll("img").length === 0;
if (isEmpty) { if (!isEmpty) {
content = NO_CONTENT + getChildrenList(note);
}
else {
for (const linkEl of document.querySelectorAll("a")) { for (const linkEl of document.querySelectorAll("a")) {
const href = linkEl.getAttribute("href"); const href = linkEl.getAttribute("href");
@@ -49,6 +24,7 @@ function getContent(note) {
if (linkedNote) { if (linkedNote) {
linkEl.setAttribute("href", linkedNote.shareId); linkEl.setAttribute("href", linkedNote.shareId);
linkEl.classList.add("type-" + linkedNote.type);
} }
else { else {
linkEl.removeAttribute("href"); linkEl.removeAttribute("href");
@@ -57,11 +33,24 @@ function getContent(note) {
} }
content = document.body.innerHTML; content = document.body.innerHTML;
if (content.includes(`<span class="math-tex">`)) {
header += `
<script src="../../libraries/katex/katex.min.js"></script>
<link rel="stylesheet" href="../../libraries/katex/katex.min.css">
<script src="../../libraries/katex/auto-render.min.js"></script>
<script src="../../libraries/katex/mhchem.min.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function() {
renderMathInElement(document.getElementById('content'));
});
</script>`;
}
} }
} }
else if (note.type === 'code' || note.type === 'mermaid') { else if (note.type === 'code') {
if (!content?.trim()) { if (!content?.trim()) {
content = NO_CONTENT + getChildrenList(note); isEmpty = true;
} }
else { else {
const document = new JSDOM().window.document; const document = new JSDOM().window.document;
@@ -72,22 +61,45 @@ function getContent(note) {
content = preEl.outerHTML; content = preEl.outerHTML;
} }
} }
else if (note.type === 'mermaid') {
content = `
<div class="mermaid">${content}</div>
<hr>
<details>
<summary>Chart source</summary>
<pre>${content}</pre>
</details>`
header += `<script src="../../libraries/mermaid.min.js"></script>`;
}
else if (note.type === 'image') { else if (note.type === 'image') {
content = `<img src="api/images/${note.noteId}/${note.title}?${note.utcDateModified}">`; content = `<img src="api/images/${note.noteId}/${note.title}?${note.utcDateModified}">`;
} }
else if (note.type === 'file') { else if (note.type === 'file') {
content = `<button type="button" onclick="location.href='api/notes/${note.noteId}/download'">Download file</button>`; 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') { else if (note.type === 'book') {
content = getChildrenList(note); isEmpty = true;
} }
else { else {
content = '<p>This note type cannot be displayed.</p>' + getChildrenList(note); content = '<p>This note type cannot be displayed.</p>';
} }
return content; return {
header,
content,
isEmpty
};
} }
module.exports = { module.exports = {
getContent getContent
}; };

View File

@@ -29,13 +29,15 @@ function register(router) {
const note = shaca.aliasToNote[shareId] || shaca.notes[shareId]; const note = shaca.aliasToNote[shareId] || shaca.notes[shareId];
if (note) { if (note) {
const content = contentRenderer.getContent(note); const {header, content, isEmpty} = contentRenderer.getContent(note);
const subRoot = getSharedSubTreeRoot(note); const subRoot = getSharedSubTreeRoot(note);
res.render("share/page", { res.render("share/page", {
note, note,
header,
content, content,
isEmpty,
subRoot subRoot
}); });
} }
@@ -44,19 +46,15 @@ function register(router) {
} }
}); });
router.get('/share/api/images/:noteId/:filename', (req, res, next) => { router.get('/share/api/notes/:noteId', (req, res, next) => {
const image = shaca.getNote(req.params.noteId); const {noteId} = req.params;
const note = shaca.getNote(noteId);
if (!image) { if (!note) {
return res.status(404).send("Not found"); return res.status(404).send(`Note ${noteId} not found`);
}
else if (image.type !== 'image') {
return res.status(400).send("Requested note is not an image");
} }
res.set('Content-Type', image.mime); res.json(note.getPojoWithAttributes());
res.send(image.getContent());
}); });
router.get('/share/api/notes/:noteId/download', (req, res, next) => { router.get('/share/api/notes/:noteId/download', (req, res, next) => {
@@ -64,7 +62,7 @@ function register(router) {
const note = shaca.getNote(noteId); const note = shaca.getNote(noteId);
if (!note) { if (!note) {
return res.status(404).send(`Not found`); return res.status(404).send(`Note ${noteId} not found`);
} }
const utils = require("../services/utils"); const utils = require("../services/utils");
@@ -78,6 +76,36 @@ function register(router) {
res.send(note.getContent()); res.send(note.getContent());
}); });
router.get('/share/api/images/:noteId/:filename', (req, res, next) => {
const image = shaca.getNote(req.params.noteId);
if (!image) {
return res.status(404).send(`Note ${noteId} not found`);
}
else if (image.type !== 'image') {
return res.status(400).send("Requested note is not an image");
}
res.set('Content-Type', image.mime);
res.send(image.getContent());
});
// used for PDF viewing
router.get('/share/api/notes/:noteId/view', (req, res, next) => {
const {noteId} = req.params;
const note = shaca.getNote(noteId);
if (!note) {
return res.status(404).send(`Note ${noteId} not found`);
}
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
res.setHeader('Content-Type', note.mime);
res.send(note.getContent());
});
} }
module.exports = { module.exports = {

View File

@@ -89,6 +89,18 @@ class Attribute extends AbstractEntity {
return this.shaca.getNote(this.value); return this.shaca.getNote(this.value);
} }
getPojo() {
return {
attributeId: this.attributeId,
noteId: this.noteId,
type: this.type,
name: this.name,
position: this.position,
value: this.value,
isInheritable: this.isInheritable
};
}
} }
module.exports = Attribute; module.exports = Attribute;

View File

@@ -410,6 +410,19 @@ class Note extends AbstractEntity {
return sharedAlias || this.noteId; return sharedAlias || this.noteId;
} }
getPojoWithAttributes() {
return {
noteId: this.noteId,
title: this.title,
type: this.type,
mime: this.mime,
utcDateModified: this.utcDateModified,
attributes: this.getAttributes().map(attr => attr.getPojo()),
parentNoteIds: this.parents.map(parentNote => parentNote.noteId),
childNoteIds: this.children.map(child => child.noteId)
};
}
} }
module.exports = Note; module.exports = Note;

View File

@@ -59,11 +59,7 @@ function load() {
SELECT attributeId, noteId, type, name, value, isInheritable, position, utcDateModified SELECT attributeId, noteId, type, name, value, isInheritable, position, utcDateModified
FROM attributes FROM attributes
WHERE isDeleted = 0 WHERE isDeleted = 0
AND noteId IN (${noteIdStr}) AND noteId IN (${noteIdStr})`);
AND (
(type = 'label' AND name IN ('archived', 'shareHiddenFromTree', 'shareAlias', 'shareOmitDefaultCss'))
OR (type = 'relation' AND name IN ('imageLink', 'template', 'shareCss'))
)`, []);
for (const row of rawAttributeRows) { for (const row of rawAttributeRows) {
new Attribute(row); new Attribute(row);

View File

@@ -61,7 +61,7 @@ async function start() {
const parentNoteId = getRandomNoteId(); const parentNoteId = getRandomNoteId();
const prefix = Math.random() > 0.8 ? "prefix" : null; const prefix = Math.random() > 0.8 ? "prefix" : null;
const result = await cloningService.cloneNoteToParent(noteIdToClone, parentNoteId, prefix); const result = await cloningService.cloneNoteToBranch(noteIdToClone, parentNoteId, prefix);
console.log(`Cloning ${i}:`, result.success ? "succeeded" : "FAILED"); console.log(`Cloning ${i}:`, result.success ? "succeeded" : "FAILED");
} }

View File

@@ -41,6 +41,8 @@
<%- include('dialogs/delete_notes.ejs') %> <%- include('dialogs/delete_notes.ejs') %>
<script type="text/javascript"> <script type="text/javascript">
global = globalThis; /* fixes https://github.com/webpack/webpack/issues/10035 */
window.baseApiUrl = 'api/'; window.baseApiUrl = 'api/';
window.device = "desktop"; window.device = "desktop";
window.glob = { window.glob = {

View File

@@ -105,6 +105,8 @@
<%- include('dialogs/confirm.ejs') %> <%- include('dialogs/confirm.ejs') %>
<script type="text/javascript"> <script type="text/javascript">
global = globalThis; /* fixes https://github.com/webpack/webpack/issues/10035 */
window.baseApiUrl = 'api/'; window.baseApiUrl = 'api/';
window.device = "mobile"; window.device = "mobile";
window.glob = { window.glob = {

View File

@@ -189,6 +189,8 @@
</div> </div>
<script type="text/javascript"> <script type="text/javascript">
global = globalThis; /* fixes https://github.com/webpack/webpack/issues/10035 */
window.glob = { window.glob = {
sourceId: '' sourceId: ''
}; };

View File

@@ -2,45 +2,79 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<% if (note.hasRelation("shareFavicon")) { %>
<link rel="shortcut icon" href="api/notes/<%= note.getRelation("shareFavicon").value %>/download">
<% } else { %>
<link rel="shortcut icon" href="../favicon.ico"> <link rel="shortcut icon" href="../favicon.ico">
<% } %>
<script src="../app/share.js"></script>
<% if (!note.hasLabel("shareOmitDefaultCss")) { %> <% if (!note.hasLabel("shareOmitDefaultCss")) { %>
<link href="../libraries/normalize.min.css" rel="stylesheet"> <link href="../libraries/normalize.min.css" rel="stylesheet">
<link href="../stylesheets/share.css" rel="stylesheet"> <link href="../stylesheets/share.css" rel="stylesheet">
<% } %> <% } %>
<% if (note.type === 'text' || note.type === 'book') { %> <% if (note.type === 'text' || note.type === 'book') { %>
<link href="../libraries/ckeditor/ckeditor-content.css" rel="stylesheet"> <link href="../libraries/ckeditor/ckeditor-content.css" rel="stylesheet">
<% } %> <% } %>
<% for (const cssRelation of note.getRelations("shareCss")) { %> <% for (const cssRelation of note.getRelations("shareCss")) { %>
<link href="api/notes/<%= cssRelation.value %>/download" rel="stylesheet"> <link href="api/notes/<%= cssRelation.value %>/download" rel="stylesheet">
<% } %> <% } %>
<% for (const jsRelation of note.getRelations("shareJs")) { %>
<script type="module" src="api/notes/<%= jsRelation.value %>/download"></script>
<% } %>
<%- header %>
<title><%= note.title %></title> <title><%= note.title %></title>
</head> </head>
<body> <body data-note-id="<%= note.noteId %>">
<div id="layout"> <div id="layout">
<div id="main"> <div id="main">
<h1 id="title"><%= note.title %></h1> <% if (note.parents[0].noteId !== 'share' && note.parents.length !== 0) { %>
<nav id="parentLink">
parent: <a href="<%= note.parents[0].noteId %>"
class="type-<%= note.parents[0].type %>"><%= note.parents[0].title %></a>
</nav>
<% } %>
<div id="content" class="note-<%= note.type %> <% if (note.type === 'text') { %>ck-content<% } %>"> <h1 id="title"><%= note.title %></h1>
<% if (note.hasLabel("pageUrl")) { %>
<div id="noteClippedFrom">This note was originally clipped from <a href="<%= note.getLabelValue("pageUrl") %>"><%= note.getLabelValue("pageUrl") %></a></div>
<% } %>
<% if (note.type === 'book') { %>
<% } else if (isEmpty) { %>
<p>This note has no content.</p>
<% } else { %>
<div id="content" class="type-<%= note.type %><% if (note.type === 'text') { %> ck-content<% } %>">
<%- content %> <%- content %>
</div> </div>
</div> <% } %>
<% if (subRoot.hasChildren()) { %> <% if (note.hasChildren()) { %>
<button id="menuButton"></button> <nav id="childLinks" class="<% if (isEmpty) { %>grid<% } else { %>list<% } %>">
<% if (!isEmpty) { %>
<hr>
<span>Child notes: </span>
<% } %>
<ul>
<% for (const childNote of note.getChildNotes()) { %>
<li>
<a href="<%= childNote.shareId %>"
class="type-<%= childNote.type %>"><%= childNote.title %></a>
</li>
<% } %>
</ul>
</nav>
<% } %>
</div>
<% if (subRoot.hasChildren()) { %>
<button id="toggleMenuButton"></button>
<nav id="menu"> <nav id="menu">
<%- include('tree_item', {note: subRoot, activeNote: note}) %> <%- include('tree_item', {note: subRoot, activeNote: note}) %>
</nav> </nav>
<% } %> <% } %>
</div> </div>
<script>
(function () {
const menuButton = document.getElementById('menuButton');
const layout = document.getElementById('layout');
menuButton.addEventListener('click', () => layout.classList.toggle('navMenu'));
}());
</script>
</body> </body>
</html> </html>

View File

@@ -2,7 +2,7 @@
<% if (activeNote.noteId === note.noteId) { %> <% if (activeNote.noteId === note.noteId) { %>
<strong><%= note.title %></strong> <strong><%= note.title %></strong>
<% } else { %> <% } else { %>
<a href="./<%= note.shareId %>"><%= note.title %></a> <a class="type-<%= note.type %>" href="./<%= note.shareId %>"><%= note.title %></a>
<% } %> <% } %>
</p> </p>

View File

@@ -11,5 +11,5 @@ module.exports = {
filename: 'desktop.js' filename: 'desktop.js'
}, },
devtool: 'source-map', devtool: 'source-map',
target: 'electron-main' target: 'electron-renderer'
}; };

View File

@@ -11,5 +11,5 @@ module.exports = {
filename: 'mobile.js' filename: 'mobile.js'
}, },
devtool: 'source-map', devtool: 'source-map',
target: 'electron-main' target: 'electron-renderer'
}; };

View File

@@ -11,5 +11,5 @@ module.exports = {
filename: 'setup.js' filename: 'setup.js'
}, },
devtool: 'source-map', devtool: 'source-map',
target: 'electron-main' target: 'electron-renderer'
}; };