Compare commits

...

14 Commits

22 changed files with 88 additions and 59 deletions

18
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "trilium", "name": "trilium",
"version": "0.62.1-beta", "version": "0.62.3",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "trilium", "name": "trilium",
"version": "0.62.1-beta", "version": "0.62.3",
"hasInstallScript": true, "hasInstallScript": true,
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"dependencies": { "dependencies": {
@@ -81,7 +81,7 @@
}, },
"devDependencies": { "devDependencies": {
"cross-env": "7.0.3", "cross-env": "7.0.3",
"electron": "25.9.5", "electron": "25.9.8",
"electron-builder": "24.6.4", "electron-builder": "24.6.4",
"electron-packager": "17.1.2", "electron-packager": "17.1.2",
"electron-rebuild": "3.2.9", "electron-rebuild": "3.2.9",
@@ -4366,9 +4366,9 @@
} }
}, },
"node_modules/electron": { "node_modules/electron": {
"version": "25.9.5", "version": "25.9.8",
"resolved": "https://registry.npmjs.org/electron/-/electron-25.9.5.tgz", "resolved": "https://registry.npmjs.org/electron/-/electron-25.9.8.tgz",
"integrity": "sha512-gM7GXUSd3JVRcYbBnNOtZeNnE5MCJjtZTT8QyIxJvpQ0Dh9dz3hTuEL62dOwnMFW/l47ACQ6es/8qi01P4QGZA==", "integrity": "sha512-PGgp6PH46QVENHuAHc2NT1Su8Q1qov7qIl2jI5tsDpTibwV2zD8539AeWBQySeBU4dhbj9onIl7+1bXQ0wefBg==",
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@electron/get": "^2.0.0", "@electron/get": "^2.0.0",
@@ -16964,9 +16964,9 @@
} }
}, },
"electron": { "electron": {
"version": "25.9.5", "version": "25.9.8",
"resolved": "https://registry.npmjs.org/electron/-/electron-25.9.5.tgz", "resolved": "https://registry.npmjs.org/electron/-/electron-25.9.8.tgz",
"integrity": "sha512-gM7GXUSd3JVRcYbBnNOtZeNnE5MCJjtZTT8QyIxJvpQ0Dh9dz3hTuEL62dOwnMFW/l47ACQ6es/8qi01P4QGZA==", "integrity": "sha512-PGgp6PH46QVENHuAHc2NT1Su8Q1qov7qIl2jI5tsDpTibwV2zD8539AeWBQySeBU4dhbj9onIl7+1bXQ0wefBg==",
"requires": { "requires": {
"@electron/get": "^2.0.0", "@electron/get": "^2.0.0",
"@types/node": "^18.11.18", "@types/node": "^18.11.18",

View File

@@ -2,7 +2,7 @@
"name": "trilium", "name": "trilium",
"productName": "Trilium Notes", "productName": "Trilium Notes",
"description": "Trilium Notes", "description": "Trilium Notes",
"version": "0.62.2", "version": "0.62.4",
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"main": "electron.js", "main": "electron.js",
"bin": { "bin": {
@@ -104,7 +104,7 @@
}, },
"devDependencies": { "devDependencies": {
"cross-env": "7.0.3", "cross-env": "7.0.3",
"electron": "25.9.5", "electron": "25.9.8",
"electron-builder": "24.6.4", "electron-builder": "24.6.4",
"electron-packager": "17.1.2", "electron-packager": "17.1.2",
"electron-rebuild": "3.2.9", "electron-rebuild": "3.2.9",

View File

@@ -117,14 +117,14 @@ async function renderCode(note, $renderedContent) {
} }
function renderImage(entity, $renderedContent, options = {}) { function renderImage(entity, $renderedContent, options = {}) {
const sanitizedTitle = entity.title.replace(/[^a-z0-9-.]/gi, ""); const encodedTitle = encodeURIComponent(entity.title);
let url; let url;
if (entity instanceof FNote) { if (entity instanceof FNote) {
url = `api/images/${entity.noteId}/${sanitizedTitle}?${Math.random()}`; url = `api/images/${entity.noteId}/${encodedTitle}?${Math.random()}`;
} else if (entity instanceof FAttachment) { } else if (entity instanceof FAttachment) {
url = `api/attachments/${entity.attachmentId}/image/${sanitizedTitle}?${entity.utcDateModified}">`; url = `api/attachments/${entity.attachmentId}/image/${encodedTitle}?${entity.utcDateModified}">`;
} }
$renderedContent // styles needed for the zoom to work well $renderedContent // styles needed for the zoom to work well

View File

@@ -42,6 +42,7 @@ async function createLink(notePath, options = {}) {
const showNotePath = options.showNotePath === undefined ? false : options.showNotePath; const showNotePath = options.showNotePath === undefined ? false : options.showNotePath;
const showNoteIcon = options.showNoteIcon === undefined ? false : options.showNoteIcon; const showNoteIcon = options.showNoteIcon === undefined ? false : options.showNoteIcon;
const referenceLink = options.referenceLink === undefined ? false : options.referenceLink; const referenceLink = options.referenceLink === undefined ? false : options.referenceLink;
const autoConvertToImage = options.autoConvertToImage === undefined ? false : options.autoConvertToImage;
const { noteId, parentNoteId } = treeService.getNoteIdAndParentIdFromUrl(notePath); const { noteId, parentNoteId } = treeService.getNoteIdAndParentIdFromUrl(notePath);
const viewScope = options.viewScope || {}; const viewScope = options.viewScope || {};
@@ -58,6 +59,16 @@ async function createLink(notePath, options = {}) {
} }
} }
const note = await froca.getNote(noteId);
if (autoConvertToImage && ['image', 'canvas', 'mermaid'].includes(note.type) && viewMode === 'default') {
const encodedTitle = encodeURIComponent(linkTitle);
return $("<img>")
.attr("src", `api/images/${noteId}/${encodedTitle}?${Math.random()}`)
.attr("alt", linkTitle);
}
const $container = $("<span>"); const $container = $("<span>");
if (showNoteIcon) { if (showNoteIcon) {

View File

@@ -97,7 +97,7 @@ export default class IncludeNoteDialog extends BasicWidget {
const boxSize = $("input[name='include-note-box-size']:checked").val(); const boxSize = $("input[name='include-note-box-size']:checked").val();
if (note.type === 'image') { if (['image', 'canvas', 'mermaid'].includes(note.type)) {
// there's no benefit to use insert note functionlity for images, // there's no benefit to use insert note functionlity for images,
// so we'll just add an IMG tag // so we'll just add an IMG tag
this.textTypeWidget.addImage(noteId); this.textTypeWidget.addImage(noteId);

View File

@@ -274,16 +274,16 @@ export default class RevisionsDialog extends BasicWidget {
this.$content.html($table); this.$content.html($table);
} else if (revisionItem.type === 'canvas') { } else if (revisionItem.type === 'canvas') {
const sanitizedTitle = revisionItem.title.replace(/[^a-z0-9-.]/gi, ""); const encodedTitle = encodeURIComponent(revisionItem.title);
this.$content.html($("<img>") this.$content.html($("<img>")
.attr("src", `api/revisions/${revisionItem.revisionId}/image/${sanitizedTitle}?${Math.random()}`) .attr("src", `api/revisions/${revisionItem.revisionId}/image/${encodedTitle}?${Math.random()}`)
.css("max-width", "100%")); .css("max-width", "100%"));
} else if (revisionItem.type === 'mermaid') { } else if (revisionItem.type === 'mermaid') {
const sanitizedTitle = revisionItem.title.replace(/[^a-z0-9-.]/gi, ""); const encodedTitle = encodeURIComponent(revisionItem.title);
this.$content.html($("<img>") this.$content.html($("<img>")
.attr("src", `api/revisions/${revisionItem.revisionId}/image/${sanitizedTitle}?${Math.random()}`) .attr("src", `api/revisions/${revisionItem.revisionId}/image/${encodedTitle}?${Math.random()}`)
.css("max-width", "100%")); .css("max-width", "100%"));
this.$content.append($("<pre>").text(fullRevision.content)); this.$content.append($("<pre>").text(fullRevision.content));

View File

@@ -56,7 +56,6 @@ export default class HighlightsListWidget extends RightPanelWidget {
.class("icon-action"), .class("icon-action"),
new OnClickButtonWidget() new OnClickButtonWidget()
.icon("bx-x") .icon("bx-x")
.title("Close Highlights List")
.titlePlacement("left") .titlePlacement("left")
.onClick(widget => widget.triggerCommand("closeHlt")) .onClick(widget => widget.triggerCommand("closeHlt"))
.class("icon-action") .class("icon-action")

View File

@@ -258,10 +258,11 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
.append($("<h2>").text(this.note.title)) .append($("<h2>").text(this.note.title))
.append($promotedAttributes) .append($promotedAttributes)
.prop('outerHTML'), .prop('outerHTML'),
footer: ` footer: `
<script src="${assetPath}/libraries/katex/katex.min.js"></script> <script src="${assetPath}/node_modules/katex/dist/katex.min.js"></script>
<script src="${assetPath}/libraries/katex/mhchem.min.js"></script> <script src="${assetPath}/node_modules/katex/dist/contrib/mhchem.min.js"></script>
<script src="${assetPath}/libraries/katex/auto-render.min.js"></script> <script src="${assetPath}/node_modules/katex/dist/contrib/auto-render.min.js"></script>
<script> <script>
document.body.className += ' ck-content printed-content'; document.body.className += ' ck-content printed-content';
@@ -273,7 +274,7 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
`${assetPath}/libraries/codemirror/codemirror.css`, `${assetPath}/libraries/codemirror/codemirror.css`,
`${assetPath}/libraries/ckeditor/ckeditor-content.css`, `${assetPath}/libraries/ckeditor/ckeditor-content.css`,
`${assetPath}/libraries/bootstrap/css/bootstrap.min.css`, `${assetPath}/libraries/bootstrap/css/bootstrap.min.css`,
`${assetPath}/libraries/katex/katex.min.css`, `${assetPath}/node_modules/katex/dist/katex.min.css`,
`${assetPath}/stylesheets/print.css`, `${assetPath}/stylesheets/print.css`,
`${assetPath}/stylesheets/relation_map.css`, `${assetPath}/stylesheets/relation_map.css`,
`${assetPath}/stylesheets/ckeditor-theme.css` `${assetPath}/stylesheets/ckeditor-theme.css`

View File

@@ -402,11 +402,11 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
})); }));
if (notes.length === 1) { if (notes.length === 1) {
linkService.createLink(notes[0].noteId, {referenceLink: true}) linkService.createLink(notes[0].noteId, {referenceLink: true, autoConvertToImage: true})
.then($link => data.dataTransfer.setData("text/html", $link[0].outerHTML)); .then($link => data.dataTransfer.setData("text/html", $link[0].outerHTML));
} }
else { else {
Promise.all(notes.map(note => linkService.createLink(note.noteId, {referenceLink: true}))).then(links => { Promise.all(notes.map(note => linkService.createLink(note.noteId, {referenceLink: true, autoConvertToImage: true}))).then(links => {
const $list = $("<ul>").append(...links.map($link => $("<li>").append($link))); const $list = $("<ul>").append(...links.map($link => $("<li>").append($link)));
data.dataTransfer.setData("text/html", $list[0].outerHTML); data.dataTransfer.setData("text/html", $list[0].outerHTML);

View File

@@ -8,8 +8,14 @@ import options from "../../services/options.js";
import utils from "../../services/utils.js"; import utils from "../../services/utils.js";
const TPL = ` const TPL = `
<div> <div class="promoted-attributes-widget">
<style> <style>
body.mobile .promoted-attributes-widget {
/* https://github.com/zadam/trilium/issues/4468 */
flex-shrink: 0.4;
overflow: auto;
}
.promoted-attributes-container { .promoted-attributes-container {
margin: auto; margin: auto;
display: flex; display: flex;

View File

@@ -68,7 +68,6 @@ export default class TocWidget extends RightPanelWidget {
.class("icon-action"), .class("icon-action"),
new OnClickButtonWidget() new OnClickButtonWidget()
.icon("bx-x") .icon("bx-x")
.title("Close Table of Contents")
.titlePlacement("left") .titlePlacement("left")
.onClick(widget => widget.triggerCommand("closeToc")) .onClick(widget => widget.triggerCommand("closeToc"))
.class("icon-action") .class("icon-action")

View File

@@ -365,12 +365,10 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
const note = await froca.getNote(noteId); const note = await froca.getNote(noteId);
this.watchdog.editor.model.change( writer => { this.watchdog.editor.model.change( writer => {
const sanitizedTitle = note.title.replace(/[^a-z0-9-.]/gi, ""); const encodedTitle = encodeURIComponent(note.title);
const src = `api/images/${note.noteId}/${sanitizedTitle}`; const src = `api/images/${note.noteId}/${encodedTitle}`;
const imageElement = writer.createElement( 'image', { 'src': src } ); this.watchdog.editor.execute( 'insertImage', { source: src } );
this.watchdog.editor.model.insertContent(imageElement, this.watchdog.editor.model.document.selection);
} ); } );
} }

View File

@@ -153,8 +153,9 @@ function processContent(images, note, content) {
const buffer = Buffer.from(dataUrl.split(",")[1], 'base64'); const buffer = Buffer.from(dataUrl.split(",")[1], 'base64');
const attachment = imageService.saveImageToAttachment(note.noteId, buffer, filename, true); const attachment = imageService.saveImageToAttachment(note.noteId, buffer, filename, true);
const sanitizedTitle = attachment.title.replace(/[^a-z0-9-.]/gi, "");
const url = `api/attachments/${attachment.attachmentId}/image/${sanitizedTitle}`; const encodedTitle = encodeURIComponent(attachment.title);
const url = `api/attachments/${attachment.attachmentId}/image/${encodedTitle}`;
log.info(`Replacing '${imageId}' with '${url}' in note '${note.noteId}'`); log.info(`Replacing '${imageId}' with '${url}' in note '${note.noteId}'`);

View File

@@ -8,6 +8,7 @@ const cls = require('../../services/cls');
const path = require('path'); const path = require('path');
const becca = require("../../becca/becca"); const becca = require("../../becca/becca");
const blobService = require("../../services/blob"); const blobService = require("../../services/blob");
const eraseService = require("../../services/erase.js");
function getRevisionBlob(req) { function getRevisionBlob(req) {
const preview = req.query.preview === 'true'; const preview = req.query.preview === 'true';
@@ -88,11 +89,11 @@ function eraseAllRevisions(req) {
const revisionIdsToErase = sql.getColumn('SELECT revisionId FROM revisions WHERE noteId = ?', const revisionIdsToErase = sql.getColumn('SELECT revisionId FROM revisions WHERE noteId = ?',
[req.params.noteId]); [req.params.noteId]);
revisionService.eraseRevisions(revisionIdsToErase); eraseService.eraseRevisions(revisionIdsToErase);
} }
function eraseRevision(req) { function eraseRevision(req) {
revisionService.eraseRevisions([req.params.revisionId]); eraseService.eraseRevisions([req.params.revisionId]);
} }
function restoreRevision(req) { function restoreRevision(req) {

View File

@@ -1 +1 @@
module.exports = { buildDate:"2023-11-21T20:49:24+01:00", buildRevision: "e2b1421bf3d764ffe444a103c118e67d8c563673" }; module.exports = { buildDate:"2023-12-07T00:03:59+01:00", buildRevision: "2e23c521c356c2305124f5df0f474532fa5f34ce" };

View File

@@ -4,6 +4,7 @@ const becca = require("../becca/becca");
const cloningService = require("./cloning"); const cloningService = require("./cloning");
const branchService = require("./branches"); const branchService = require("./branches");
const utils = require("./utils"); const utils = require("./utils");
const eraseService = require("./erase.js");
const ACTION_HANDLERS = { const ACTION_HANDLERS = {
addLabel: (action, note) => { addLabel: (action, note) => {
@@ -18,7 +19,7 @@ const ACTION_HANDLERS = {
note.deleteNote(deleteId); note.deleteNote(deleteId);
}, },
deleteRevisions: (action, note) => { deleteRevisions: (action, note) => {
revisionService.eraseRevisions(note.getRevisions().map(rev => rev.revisionId)); eraseService.eraseRevisions(note.getRevisions().map(rev => rev.revisionId));
}, },
deleteLabel: (action, note) => { deleteLabel: (action, note) => {
for (const label of note.getOwnedLabels(action.labelName)) { for (const label of note.getOwnedLabels(action.labelName)) {

View File

@@ -467,7 +467,7 @@ class ConsistencyChecks {
WHERE blobs.blobId IS NULL`, WHERE blobs.blobId IS NULL`,
({revisionId, blobId}) => { ({revisionId, blobId}) => {
if (this.autoFix) { if (this.autoFix) {
revisionService.eraseRevisions([revisionId]); eraseService.eraseRevisions([revisionId]);
this.reloadNeeded = true; this.reloadNeeded = true;

View File

@@ -29,7 +29,7 @@ function eraseNotes(noteIdsToErase) {
const revisionIdsToErase = sql.getManyRows(`SELECT revisionId FROM revisions WHERE noteId IN (???)`, noteIdsToErase) const revisionIdsToErase = sql.getManyRows(`SELECT revisionId FROM revisions WHERE noteId IN (???)`, noteIdsToErase)
.map(row => row.revisionId); .map(row => row.revisionId);
revisionService.eraseRevisions(revisionIdsToErase); eraseRevisions(revisionIdsToErase);
log.info(`Erased notes: ${JSON.stringify(noteIdsToErase)}`); log.info(`Erased notes: ${JSON.stringify(noteIdsToErase)}`);
} }
@@ -79,6 +79,18 @@ function eraseAttachments(attachmentIdsToErase) {
log.info(`Erased attachments: ${JSON.stringify(attachmentIdsToErase)}`); log.info(`Erased attachments: ${JSON.stringify(attachmentIdsToErase)}`);
} }
function eraseRevisions(revisionIdsToErase) {
if (revisionIdsToErase.length === 0) {
return;
}
sql.executeMany(`DELETE FROM revisions WHERE revisionId IN (???)`, revisionIdsToErase);
setEntityChangesAsErased(sql.getManyRows(`SELECT * FROM entity_changes WHERE entityName = 'revisions' AND entityId IN (???)`, revisionIdsToErase));
log.info(`Removed revisions: ${JSON.stringify(revisionIdsToErase)}`);
}
function eraseUnusedBlobs() { function eraseUnusedBlobs() {
const unusedBlobIds = sql.getColumn(` const unusedBlobIds = sql.getColumn(`
SELECT blobs.blobId SELECT blobs.blobId
@@ -184,5 +196,6 @@ module.exports = {
eraseUnusedAttachmentsNow, eraseUnusedAttachmentsNow,
eraseNotesWithDeleteId, eraseNotesWithDeleteId,
eraseUnusedBlobs, eraseUnusedBlobs,
eraseAttachments eraseAttachments,
eraseRevisions
}; };

View File

@@ -303,8 +303,8 @@ function importEnex(taskContext, file, parentNote) {
const attachment = imageService.saveImageToAttachment(noteEntity.noteId, resource.content, originalName, taskContext.data.shrinkImages); const attachment = imageService.saveImageToAttachment(noteEntity.noteId, resource.content, originalName, taskContext.data.shrinkImages);
const sanitizedTitle = attachment.title.replace(/[^a-z0-9-.]/gi, ""); const encodedTitle = encodeURIComponent(attachment.title);
const url = `api/attachments/${attachment.attachmentId}/image/${sanitizedTitle}`; const url = `api/attachments/${attachment.attachmentId}/image/${encodedTitle}`;
const imageLink = `<img src="${url}">`; const imageLink = `<img src="${url}">`;
content = content.replace(mediaRegex, imageLink); content = content.replace(mediaRegex, imageLink);

View File

@@ -529,9 +529,9 @@ function downloadImages(noteId, content) {
const imageService = require('../services/image'); const imageService = require('../services/image');
const attachment = imageService.saveImageToAttachment(noteId, imageBuffer, "inline image", true, true); const attachment = imageService.saveImageToAttachment(noteId, imageBuffer, "inline image", true, true);
const sanitizedTitle = attachment.title.replace(/[^a-z0-9-.]/gi, ""); const encodedTitle = encodeURIComponent(attachment.title);
content = `${content.substr(0, imageMatch.index)}<img src="api/attachments/${attachment.attachmentId}/image/${sanitizedTitle}"${content.substr(imageMatch.index + imageMatch[0].length)}`; content = `${content.substr(0, imageMatch.index)}<img src="api/attachments/${attachment.attachmentId}/image/${encodedTitle}"${content.substr(imageMatch.index + imageMatch[0].length)}`;
} }
else if (!url.includes('api/images/') && !/api\/attachments\/.+\/image\/?.*/.test(url) else if (!url.includes('api/images/') && !/api\/attachments\/.+\/image\/?.*/.test(url)
// this is an exception for the web clipper's "imageId" // this is an exception for the web clipper's "imageId"
@@ -889,6 +889,10 @@ function scanForLinks(note, content) {
* Things which have to be executed after updating content, but asynchronously (separate transaction) * Things which have to be executed after updating content, but asynchronously (separate transaction)
*/ */
async function asyncPostProcessContent(note, content) { async function asyncPostProcessContent(note, content) {
if (note.hasStringContent() && !utils.isString(content)) {
content = content.toString();
}
scanForLinks(note, content); scanForLinks(note, content);
} }

View File

@@ -46,18 +46,6 @@ function protectRevisions(note) {
} }
} }
function eraseRevisions(revisionIdsToErase) {
if (revisionIdsToErase.length === 0) {
return;
}
log.info(`Removing revisions: ${JSON.stringify(revisionIdsToErase)}`);
sql.executeMany(`DELETE FROM revisions WHERE revisionId IN (???)`, revisionIdsToErase);
sql.executeMany(`UPDATE entity_changes SET isErased = 1, utcDateChanged = '${dateUtils.utcNowDateTime()}' WHERE entityName = 'revisions' AND entityId IN (???)`, revisionIdsToErase);
}
module.exports = { module.exports = {
protectRevisions, protectRevisions
eraseRevisions
}; };

View File

@@ -63,6 +63,8 @@ function isElectron() {
} }
function hash(text) { function hash(text) {
text = text.normalize();
return crypto.createHash('sha1').update(text).digest('base64'); return crypto.createHash('sha1').update(text).digest('base64');
} }
@@ -303,6 +305,10 @@ function toMap(list, key) {
return map; return map;
} }
function isString(x) {
return Object.prototype.toString.call(x) === "[object String]";
}
module.exports = { module.exports = {
randomSecureToken, randomSecureToken,
randomString, randomString,
@@ -335,4 +341,5 @@ module.exports = {
normalize, normalize,
hashedBlobId, hashedBlobId,
toMap, toMap,
isString
}; };