Compare commits

...

21 Commits

Author SHA1 Message Date
zadam
803b6df40c release 0.61.12 2023-11-04 00:16:19 +01:00
zadam
1ebdb0f5e1 emergency disabling of image compression since it appears to make problems in migration to 0.61 2023-11-04 00:10:54 +01:00
zadam
df5951ce46 improve sync debug info 2023-11-04 00:02:31 +01:00
zadam
d3a477b8f2 release 0.61.11 2023-11-03 11:46:53 +01:00
zadam
01093d05d7 qswitch-electron, qswitch-server npm scripts 2023-10-29 18:39:53 +01:00
zadam
a9b63111ae qstart-electron, qstart-server npm scripts 2023-10-29 16:42:30 +01:00
zadam
ed1a731950 extra hint on "noproxy directly in the setting", #3934 2023-10-24 23:15:18 +02:00
zadam
ef974ab1f5 add shell as enabled by default #4347 2023-10-24 23:00:46 +02:00
zadam
1cd391a132 mermaid 10.5.1 2023-10-22 23:57:28 +02:00
zadam
1c15527d95 added "greater than 0" and "less than 2" for ancestor depth, fixes #4343 2023-10-22 23:52:21 +02:00
zadam
7af79ec33b limit mermaid width, #4340 2023-10-21 23:24:43 +02:00
zadam
4294c043d8 enabled CKEditor HTML support 2023-10-21 23:19:14 +02:00
zadam
e5b925abf8 added extra elements to the html sanitizer 2023-10-21 18:03:06 +02:00
zadam
90c0a4a437 respect safeImport flag when sanitizing imported content 2023-10-21 17:54:07 +02:00
zadam
692f7868bc updated demo document so that canvas and mermaid have generated export attachments 2023-10-21 17:36:08 +02:00
zadam
5282af55f6 render attachment SVG when sharing mermaid 2023-10-21 17:32:07 +02:00
zadam
aefc4c6bd2 ckeditor 40 2023-10-21 17:24:47 +02:00
zadam
b39ba76505 add ability to insert mermaid diagram into text notes as image 2023-10-21 00:23:16 +02:00
zadam
9d918e7a54 fix image zooming 2023-10-20 23:44:30 +02:00
zadam
38db7f9db7 style tweaks #4338 2023-10-19 23:54:36 +02:00
zadam
5163e50e7d use mtime instead of ctime for backups #4321 2023-10-19 23:02:37 +02:00
39 changed files with 555 additions and 418 deletions

Binary file not shown.

View File

@@ -3,8 +3,12 @@ module.exports = () => {
const becca = require("../../src/becca/becca");
const cls = require("../../src/services/cls");
const log = require("../../src/services/log");
const sql = require("../../src/services/sql");
cls.init(() => {
// emergency disabling of image compression since it appears to make problems in migration to 0.61
sql.execute(`UPDATE options SET value = 'false' WHERE name = 'compressImages'`);
beccaLoader.load();
for (const note of Object.values(becca.notes)) {

View File

@@ -0,0 +1,2 @@
-- emergency disabling of image compression since it appears to make problems in migration to 0.61
UPDATE options SET value = 'false' WHERE name = 'compressImages';

View File

@@ -5,8 +5,8 @@
}
/*
* CKEditor 5 (v39.0.2) content styles.
* Generated on Wed, 06 Sep 2023 07:32:15 GMT.
* CKEditor 5 (v40.0.0) content styles.
* Generated on Thu, 19 Oct 2023 13:45:23 GMT.
* For more information, check out https://ckeditor.com/docs/ckeditor5/latest/installation/advanced/content-styles.html
*/
@@ -42,6 +42,18 @@
overflow-wrap: break-word;
position: relative;
}
/* @ckeditor/ckeditor5-table/theme/tablecaption.css */
.ck-content .table > figcaption {
display: table-caption;
caption-side: top;
word-break: break-word;
text-align: center;
color: var(--ck-color-selector-caption-text);
background-color: var(--ck-color-selector-caption-background);
padding: .6em;
font-size: .75em;
outline-offset: -1px;
}
/* @ckeditor/ckeditor5-table/theme/table.css */
.ck-content .table {
margin: 0.9em auto;
@@ -75,18 +87,6 @@
.ck-content[dir="ltr"] .table th {
text-align: left;
}
/* @ckeditor/ckeditor5-table/theme/tablecaption.css */
.ck-content .table > figcaption {
display: table-caption;
caption-side: top;
word-break: break-word;
text-align: center;
color: var(--ck-color-selector-caption-text);
background-color: var(--ck-color-selector-caption-background);
padding: .6em;
font-size: .75em;
outline-offset: -1px;
}
/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */
.ck-content .page-break {
position: relative;
@@ -136,6 +136,7 @@
}
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-content .todo-list li {
position: relative;
margin-bottom: 5px;
}
/* @ckeditor/ckeditor5-list/theme/todolist.css */
@@ -157,6 +158,13 @@
margin-left: 0;
}
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-content[dir=rtl] .todo-list .todo-list__label > input {
left: 0;
margin-right: 0;
right: -25px;
margin-left: -15px;
}
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-content .todo-list .todo-list__label > input::before {
display: block;
position: absolute;
@@ -166,7 +174,7 @@
height: 100%;
border: 1px solid hsl(0, 0%, 20%);
border-radius: 2px;
transition: 250ms ease-in-out box-shadow, 250ms ease-in-out background, 250ms ease-in-out border;
transition: 250ms ease-in-out box-shadow;
}
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-content .todo-list .todo-list__label > input::after {
@@ -197,19 +205,80 @@
.ck-content .todo-list .todo-list__label .todo-list__label__description {
vertical-align: middle;
}
/* @ckeditor/ckeditor5-image/theme/imageresize.css */
.ck-content .image.image_resized {
max-width: 100%;
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-content .todo-list .todo-list__label.todo-list__label_without-description input[type=checkbox] {
position: absolute;
}
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-editor__editable.ck-content .todo-list .todo-list__label > input,
.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input {
cursor: pointer;
}
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-editor__editable.ck-content .todo-list .todo-list__label > input:hover::before, .ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input:hover::before {
box-shadow: 0 0 0 5px hsla(0, 0%, 0%, 0.1);
}
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input {
-webkit-appearance: none;
display: inline-block;
position: relative;
width: var(--ck-todo-list-checkmark-size);
height: var(--ck-todo-list-checkmark-size);
vertical-align: middle;
border: 0;
left: -25px;
margin-right: -15px;
right: 0;
margin-left: 0;
}
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-editor__editable.ck-content[dir=rtl] .todo-list .todo-list__label > span[contenteditable=false] > input {
left: 0;
margin-right: 0;
right: -25px;
margin-left: -15px;
}
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input::before {
display: block;
position: absolute;
box-sizing: border-box;
}
/* @ckeditor/ckeditor5-image/theme/imageresize.css */
.ck-content .image.image_resized img {
content: '';
width: 100%;
height: 100%;
border: 1px solid hsl(0, 0%, 20%);
border-radius: 2px;
transition: 250ms ease-in-out box-shadow;
}
/* @ckeditor/ckeditor5-image/theme/imageresize.css */
.ck-content .image.image_resized > figcaption {
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input::after {
display: block;
position: absolute;
box-sizing: content-box;
pointer-events: none;
content: '';
left: calc( var(--ck-todo-list-checkmark-size) / 3 );
top: calc( var(--ck-todo-list-checkmark-size) / 5.3 );
width: calc( var(--ck-todo-list-checkmark-size) / 5.3 );
height: calc( var(--ck-todo-list-checkmark-size) / 2.6 );
border-style: solid;
border-color: transparent;
border-width: 0 calc( var(--ck-todo-list-checkmark-size) / 8 ) calc( var(--ck-todo-list-checkmark-size) / 8 ) 0;
transform: rotate(45deg);
}
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input[checked]::before {
background: hsl(126, 64%, 41%);
border-color: hsl(126, 64%, 41%);
}
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input[checked]::after {
border-color: hsl(0, 0%, 100%);
}
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-editor__editable.ck-content .todo-list .todo-list__label.todo-list__label_without-description input[type=checkbox] {
position: absolute;
}
/* @ckeditor/ckeditor5-image/theme/image.css */
.ck-content .image {
@@ -225,6 +294,7 @@
margin: 0 auto;
max-width: 100%;
min-width: 100%;
height: auto;
}
/* @ckeditor/ckeditor5-image/theme/image.css */
.ck-content .image-inline {
@@ -259,6 +329,50 @@
font-size: .75em;
outline-offset: -1px;
}
/* @ckeditor/ckeditor5-image/theme/imageresize.css */
.ck-content img.image_resized {
height: auto;
}
/* @ckeditor/ckeditor5-image/theme/imageresize.css */
.ck-content .image.image_resized {
max-width: 100%;
display: block;
box-sizing: border-box;
}
/* @ckeditor/ckeditor5-image/theme/imageresize.css */
.ck-content .image.image_resized img {
width: 100%;
}
/* @ckeditor/ckeditor5-image/theme/imageresize.css */
.ck-content .image.image_resized > figcaption {
display: block;
}
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
.ck-content .marker-yellow {
background-color: var(--ck-highlight-marker-yellow);
}
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
.ck-content .marker-green {
background-color: var(--ck-highlight-marker-green);
}
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
.ck-content .marker-pink {
background-color: var(--ck-highlight-marker-pink);
}
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
.ck-content .marker-blue {
background-color: var(--ck-highlight-marker-blue);
}
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
.ck-content .pen-red {
color: var(--ck-highlight-pen-red);
background-color: transparent;
}
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
.ck-content .pen-green {
color: var(--ck-highlight-pen-green);
background-color: transparent;
}
/* @ckeditor/ckeditor5-list/theme/list.css */
.ck-content ol {
list-style-type: decimal;
@@ -295,32 +409,6 @@
.ck-content ul ul ul ul {
list-style-type: square;
}
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
.ck-content .marker-yellow {
background-color: var(--ck-highlight-marker-yellow);
}
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
.ck-content .marker-green {
background-color: var(--ck-highlight-marker-green);
}
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
.ck-content .marker-pink {
background-color: var(--ck-highlight-marker-pink);
}
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
.ck-content .marker-blue {
background-color: var(--ck-highlight-marker-blue);
}
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
.ck-content .pen-red {
color: var(--ck-highlight-pen-red);
background-color: transparent;
}
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
.ck-content .pen-green {
color: var(--ck-highlight-pen-green);
background-color: transparent;
}
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-style-block-align-left,
.ck-content .image-style-block-align-right {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "trilium",
"version": "0.61.9-beta",
"version": "0.61.10-beta",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "trilium",
"version": "0.61.9-beta",
"version": "0.61.10-beta",
"hasInstallScript": true,
"license": "AGPL-3.0-only",
"dependencies": {

View File

@@ -2,7 +2,7 @@
"name": "trilium",
"productName": "Trilium Notes",
"description": "Trilium Notes",
"version": "0.61.10-beta",
"version": "0.61.12",
"license": "AGPL-3.0-only",
"main": "electron.js",
"bin": {
@@ -15,12 +15,16 @@
"scripts": {
"start-server": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon ./src/www",
"start-server-no-dir": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon ./src/www",
"qstart-server": "npm run qswitch-server && TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon ./src/www",
"start-electron": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev electron --inspect=5858 .",
"start-electron-no-dir": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 electron --inspect=5858 .",
"qstart-electron": "npm run qswitch-electron && TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev electron --inspect=5858 .",
"switch-server": "rm -rf ./node_modules/better-sqlite3 && npm install",
"switch-electron": "./node_modules/.bin/electron-rebuild",
"qswitch-server": "rm -rf ./node_modules/better-sqlite3/bin ; mkdir -p ./node_modules/better-sqlite3/build ; cp ./bin/better-sqlite3/linux-server-better_sqlite3.node ./node_modules/better-sqlite3/build/better_sqlite3.node",
"qswitch-electron": "rm -rf ./node_modules/better-sqlite3/bin ; mkdir -p ./node_modules/better-sqlite3/build ; cp ./bin/better-sqlite3/linux-desktop-better_sqlite3.node ./node_modules/better-sqlite3/build/better_sqlite3.node",
"build-backend-docs": "rm -rf ./docs/backend_api && ./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/backend_api src/becca/entities/*.js src/services/backend_script_api.js src/services/sql.js",
"build-frontend-docs": "rm -rf ./docs/frontend_api && ./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/frontend_api src/public/app/entities/*.js src/public/app/services/frontend_script_api.js src/public/app/widgets/right_panel_widget.js",
"build-frontend-docs": "rm -rf ./docs/frontend_api && ./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/frontend_api src/public/app/entities/*.js src/public/app/services/frontend_script_api.js src/public/app/widgets/basic_widget.js src/public/app/widgets/note_context_aware_widget.js src/public/app/widgets/right_panel_widget.js",
"build-docs": "npm run build-backend-docs && npm run build-frontend-docs",
"webpack": "webpack -c webpack.config.js",
"test-jasmine": "jasmine",

5
spec/etapi/notes.spec.js Normal file
View File

@@ -0,0 +1,5 @@
describe("Notes", () => {
it("zzz", () => {
});
});

View File

@@ -1635,15 +1635,24 @@ class BNote extends AbstractBeccaEntity {
}
/**
* @param {string} matchBy - choose by which property we detect if to update an existing attachment.
* Supported values are either 'attachmentId' (default) or 'title'
* @returns {BAttachment}
*/
saveAttachment({attachmentId, role, mime, title, content, position}) {
saveAttachment({attachmentId, role, mime, title, content, position}, matchBy = 'attachmentId') {
if (!['attachmentId', 'title'].includes(matchBy)) {
throw new Error(`Unsupported value '${matchBy}' for matchBy param, has to be either 'attachmentId' or 'title'.`);
}
let attachment;
if (attachmentId) {
if (matchBy === 'title') {
attachment = this.getAttachmentByTitle(title);
} else if (matchBy === 'attachmentId' && attachmentId) {
attachment = this.becca.getAttachmentOrThrow(attachmentId);
} else {
attachment = new BAttachment({
}
attachment = attachment || new BAttachment({
ownerId: this.noteId,
title,
role,
@@ -1651,7 +1660,6 @@ class BNote extends AbstractBeccaEntity {
isProtected: this.isProtected,
position
});
}
content = content || "";
attachment.setContent(content, {forceSave: true});

View File

@@ -78,7 +78,7 @@ import HideFloatingButtonsButton from "../widgets/floating_buttons/hide_floating
import ScriptExecutorWidget from "../widgets/ribbon_widgets/script_executor.js";
import MovePaneButton from "../widgets/buttons/move_pane_button.js";
import UploadAttachmentsDialog from "../widgets/dialogs/upload_attachments.js";
import CanvasPropertiesWidget from "../widgets/ribbon_widgets/canvas_properties.js";
import CopyImageReferenceButton from "../widgets/floating_buttons/copy_image_reference_button.js";
export default class DesktopLayout {
constructor(customWidgets) {
@@ -145,7 +145,6 @@ export default class DesktopLayout {
.ribbon(new NotePropertiesWidget())
.ribbon(new FilePropertiesWidget())
.ribbon(new ImagePropertiesWidget())
.ribbon(new CanvasPropertiesWidget())
.ribbon(new BasicPropertiesWidget())
.ribbon(new OwnedAttributeListWidget())
.ribbon(new InheritedAttributesWidget())
@@ -162,6 +161,7 @@ export default class DesktopLayout {
.child(new EditButton())
.child(new CodeButtonsWidget())
.child(new RelationMapButtons())
.child(new CopyImageReferenceButton())
.child(new MermaidExportButton())
.child(new BacklinksWidget())
.child(new HideFloatingButtonsButton())

View File

@@ -18,7 +18,7 @@ const TPL = `
.global-menu-button {
background-image: url("${window.glob.assetPath}/images/icon-black.svg");
background-repeat: no-repeat;
background-position: 50% 80%;
background-position: 40% 50%;
background-size: 45px;
width: 100%;
height: 100%;

View File

@@ -88,7 +88,7 @@ const TPL = `
display: none;
border-bottom: 1px solid var(--main-border-color);
margin-left: 10px;
margin-right: 10px;
margin-right: 5px; /* needs to have this value so that the bottom border is the same width as the top one */
}
.ribbon-body.active {

View File

@@ -242,7 +242,7 @@ export default class RevisionsDialog extends BasicWidget {
renderMathInElement(this.$content[0], {trust: true});
}
} else if (revisionItem.type === 'code' || revisionItem.type === 'mermaid') {
} else if (revisionItem.type === 'code') {
this.$content.html($("<pre>").text(fullRevision.content));
} else if (revisionItem.type === 'image') {
this.$content.html($("<img>")
@@ -279,6 +279,14 @@ export default class RevisionsDialog extends BasicWidget {
this.$content.html($("<img>")
.attr("src", `api/revisions/${revisionItem.revisionId}/image/${sanitizedTitle}?${Math.random()}`)
.css("max-width", "100%"));
} else if (revisionItem.type === 'mermaid') {
const sanitizedTitle = revisionItem.title.replace(/[^a-z0-9-.]/gi, "");
this.$content.html($("<img>")
.attr("src", `api/revisions/${revisionItem.revisionId}/image/${sanitizedTitle}?${Math.random()}`)
.css("max-width", "100%"));
this.$content.append($("<pre>").text(fullRevision.content));
} else {
this.$content.text("Preview isn't available for this note type.");
}

View File

@@ -0,0 +1,40 @@
import NoteContextAwareWidget from "../note_context_aware_widget.js";
import utils from "../../services/utils.js";
import imageService from "../../services/image.js";
const TPL = `
<button type="button"
class="copy-image-reference-button"
title="Copy image reference to the clipboard, can be pasted into a text note.">
<span class="bx bx-copy"></span>
<div class="hidden-image-copy"></div>
</button>`;
export default class CopyImageReferenceButton extends NoteContextAwareWidget {
isEnabled() {
return super.isEnabled()
&& ['mermaid', 'canvas'].includes(this.note?.type)
&& this.note.isContentAvailable()
&& this.noteContext?.viewScope.viewMode === 'default';
}
doRender() {
super.doRender();
this.$widget = $(TPL);
this.$hiddenImageCopy = this.$widget.find(".hidden-image-copy");
this.$widget.on('click', () => {
this.$hiddenImageCopy.empty().append(
$("<img>")
.attr("src", utils.createImageSrcUrl(this.note))
);
imageService.copyImageReferenceToClipboard(this.$hiddenImageCopy);
this.$hiddenImageCopy.empty();
});
this.contentSized();
}
}

View File

@@ -1,5 +1,6 @@
import libraryLoader from "../services/library_loader.js";
import NoteContextAwareWidget from "./note_context_aware_widget.js";
import server from "../services/server.js";
const TPL = `<div class="mermaid-widget">
<style>
@@ -18,6 +19,10 @@ const TPL = `<div class="mermaid-widget">
height: 100%;
text-align: center;
}
.mermaid-render svg {
width: 95%; /* https://github.com/zadam/trilium/issues/4340 */
}
</style>
<div class="mermaid-error alert alert-warning">
@@ -77,6 +82,20 @@ export default class MermaidWidget extends NoteContextAwareWidget {
try {
const svg = await this.renderSvg();
if (this.dirtyAttachment) {
const payload = {
role: 'image',
title: 'mermaid-export.svg',
mime: 'image/svg+xml',
content: svg,
position: 0
};
server.post(`notes/${this.noteId}/attachments?matchBy=title`, payload).then(() => {
this.dirtyAttachment = false;
});
}
this.$display.html(svg);
await wheelZoomLoaded;
@@ -85,8 +104,8 @@ export default class MermaidWidget extends NoteContextAwareWidget {
WZoom.create(`#mermaid-render-${idCounter}`, {
type: 'html',
maxScale: 10,
speed: 20,
maxScale: 50,
speed: 1.3,
zoomOnClick: false
});
} catch (e) {
@@ -107,6 +126,8 @@ export default class MermaidWidget extends NoteContextAwareWidget {
async entitiesReloadedEvent({loadResults}) {
if (loadResults.isNoteContentReloaded(this.noteId)) {
this.dirtyAttachment = true;
await this.refresh();
}
}

View File

@@ -17,7 +17,8 @@ const TPL = `
.basic-properties-widget > * {
margin-right: 30px;
margin-top: 12px;
margin-top: 9px;
margin-bottom: 2px;
}
.note-type-container, .editability-select-container {

View File

@@ -1,56 +0,0 @@
import NoteContextAwareWidget from "../note_context_aware_widget.js";
import utils from "../../services/utils.js";
import imageService from "../../services/image.js";
const TPL = `
<div class="image-properties">
<div style="display: flex; justify-content: space-evenly; margin: 10px;">
<button class="canvas-copy-reference-to-clipboard btn btn-sm btn-primary"
title="Pasting this reference into a text note will insert the canvas note as image"
type="button">Copy reference to clipboard</button>
</div>
<div class="hidden-canvas-copy"></div>
</div>`;
export default class CanvasPropertiesWidget extends NoteContextAwareWidget {
get name() {
return "canvasProperties";
}
get toggleCommand() {
return "toggleRibbonTabCanvasProperties";
}
isEnabled() {
return this.note && this.note.type === 'canvas';
}
getTitle() {
return {
show: this.isEnabled(),
activate: false,
title: 'Canvas',
icon: 'bx bx-pen'
};
}
doRender() {
this.$widget = $(TPL);
this.contentSized();
this.$hiddenCanvasCopy = this.$widget.find('.hidden-canvas-copy');
this.$copyReferenceToClipboardButton = this.$widget.find(".canvas-copy-reference-to-clipboard");
this.$copyReferenceToClipboardButton.on('click', () => {
this.$hiddenCanvasCopy.empty().append(
$("<img>")
.attr("src", utils.createImageSrcUrl(this.note))
);
imageService.copyImageReferenceToClipboard(this.$hiddenCanvasCopy);
this.$hiddenCanvasCopy.empty();
});
}
}

View File

@@ -14,7 +14,7 @@ const TPL = `
color: var(--muted-text-color);
max-height: 200px;
overflow: auto;
padding: 12px 12px 11px 12px;
padding: 14px 12px 13px 12px;
}
</style>

View File

@@ -8,7 +8,8 @@ const TPL = `
.attribute-list {
margin-left: 7px;
margin-right: 7px;
margin-top: 3px;
margin-top: 5px;
margin-bottom: 2px;
position: relative;
}

View File

@@ -23,6 +23,7 @@ const TPL = `
<option value="eq7">is exactly 7</option>
<option value="eq8">is exactly 8</option>
<option value="eq9">is exactly 9</option>
<option value="gt0">is greater than 0</option>
<option value="gt1">is greater than 1</option>
<option value="gt2">is greater than 2</option>
<option value="gt3">is greater than 3</option>
@@ -32,6 +33,7 @@ const TPL = `
<option value="gt7">is greater than 7</option>
<option value="gt8">is greater than 8</option>
<option value="gt9">is greater than 9</option>
<option value="lt2">is less than 2</option>
<option value="lt3">is less than 3</option>
<option value="lt4">is less than 4</option>
<option value="lt5">is less than 5</option>

View File

@@ -22,6 +22,7 @@ const Draggabilly = window.Draggabilly;
const TAB_CONTAINER_MIN_WIDTH = 24;
const TAB_CONTAINER_MAX_WIDTH = 240;
const TAB_CONTAINER_LEFT_PADDING = 5;
const NEW_TAB_WIDTH = 32;
const MIN_FILLER_WIDTH = 50;
const MARGIN_WIDTH = 5;
@@ -330,7 +331,7 @@ export default class TabRowWidget extends BasicWidget {
getTabPositions() {
const tabPositions = [];
let position = 0;
let position = TAB_CONTAINER_LEFT_PADDING;
this.tabWidths.forEach(width => {
tabPositions.push(position);
position += width + MARGIN_WIDTH;

View File

@@ -49,8 +49,8 @@ class ImageTypeWidget extends TypeWidget {
libraryLoader.requireLibrary(libraryLoader.WHEEL_ZOOM).then(() => {
WZoom.create(`#${this.$imageView.attr("id")}`, {
maxScale: 10,
speed: 20,
maxScale: 50,
speed: 1.3,
zoomOnClick: false
});
});

View File

@@ -84,11 +84,11 @@ export default class BackupOptions extends OptionsWidget {
this.$existingBackupList.empty();
if (!backupFiles.length) {
backupFiles = [{filePath: "no backup yet", ctime: ''}];
backupFiles = [{filePath: "no backup yet", mtime: ''}];
}
for (const {filePath, ctime} of backupFiles) {
this.$existingBackupList.append($("<li>").text(`${filePath} ${ctime ? ` - ${ctime}` : ''}`));
for (const {filePath, mtime} of backupFiles) {
this.$existingBackupList.append($("<li>").text(`${filePath} ${mtime ? ` - ${mtime}` : ''}`));
}
});
}

View File

@@ -21,7 +21,8 @@ const TPL = `
<label>Sync proxy server (optional)</label>
<input class="sync-proxy form-control" placeholder="https://<host>:<port>">
<p><strong>Note:</strong> If you leave the proxy setting blank, the system proxy will be used (applies to desktop/electron build only)</p>
<p><strong>Note:</strong> If you leave the proxy setting blank, the system proxy will be used (applies to desktop/electron build only).</p>
<p>Another special value is <code>noproxy</code> which forces ignoring even the system proxy and respectes <code>NODE_TLS_REJECT_UNAUTHORIZED</code>.</p>
</div>
<div style="display: flex; justify-content: space-between;">

View File

@@ -32,9 +32,10 @@ function getAllAttachments(req) {
function saveAttachment(req) {
const {noteId} = req.params;
const {attachmentId, role, mime, title, content} = req.body;
const {matchBy} = req.query;
const note = becca.getNoteOrThrow(noteId);
note.saveAttachment({attachmentId, role, mime, title, content});
note.saveAttachment({attachmentId, role, mime, title, content}, matchBy);
}
function uploadAttachment(req) {

View File

@@ -25,17 +25,24 @@ function returnImageInt(image, res) {
if (!image) {
res.set('Content-Type', 'image/png');
return res.send(fs.readFileSync(`${RESOURCE_DIR}/db/image-deleted.png`));
} else if (!["image", "canvas"].includes(image.type)) {
} else if (!["image", "canvas", "mermaid"].includes(image.type)) {
return res.sendStatus(400);
}
/**
* special "image" type. the canvas is actually type application/json
* to avoid bitrot and enable usage as referenced image the svg is included.
*/
if (image.type === 'canvas') {
renderSvgAttachment(image, res, 'canvas-export.svg');
} else if (image.type === 'mermaid') {
renderSvgAttachment(image, res, 'mermaid-export.svg');
} else {
res.set('Content-Type', image.mime);
res.set("Cache-Control", "no-cache, no-store, must-revalidate");
res.send(image.getContent());
}
}
function renderSvgAttachment(image, res, attachmentName) {
let svgString = '<svg/>'
const attachment = image.getAttachmentByTitle('canvas-export.svg');
const attachment = image.getAttachmentByTitle(attachmentName);
if (attachment) {
svgString = attachment.getContent();
@@ -52,13 +59,9 @@ function returnImageInt(image, res) {
res.set('Content-Type', "image/svg+xml");
res.set("Cache-Control", "no-cache, no-store, must-revalidate");
res.send(svg);
} else {
res.set('Content-Type', image.mime);
res.set("Cache-Control", "no-cache, no-store, must-revalidate");
res.send(image.getContent());
}
}
function returnAttachedImage(req, res) {
const attachment = becca.getAttachment(req.params.attachmentId);

View File

@@ -4,7 +4,7 @@ const build = require('./build');
const packageJson = require('../../package');
const {TRILIUM_DATA_DIR} = require('./data_dir');
const APP_DB_VERSION = 226;
const APP_DB_VERSION = 227;
const SYNC_VERSION = 31;
const CLIPPER_PROTOCOL_VERSION = "1.0";

View File

@@ -21,7 +21,7 @@ function getExistingBackups() {
const filePath = path.resolve(dataDir.BACKUP_DIR, fileName);
const stat = fs.statSync(filePath)
return {fileName, filePath, ctime: stat.ctime};
return {fileName, filePath, mtime: stat.mtime};
});
}

View File

@@ -1 +1 @@
module.exports = { buildDate:"2023-10-19T09:33:51+02:00", buildRevision: "b01fe5ead9268784fb133a8cfa53670927ba0e3b" };
module.exports = { buildDate:"2023-11-04T00:16:19+01:00", buildRevision: "1ebdb0f5e1a5cc1b7f8c36af5e1f750141ab062b" };

View File

@@ -28,22 +28,14 @@ function sanitize(dirtyHtml) {
allowedTags: [
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol',
'li', 'b', 'i', 'strong', 'em', 'strike', 's', 'del', 'abbr', 'code', 'hr', 'br', 'div',
'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre', 'section', 'img',
'figure', 'figcaption', 'span', 'label', 'input',
'table', 'thead', 'caption', 'tbody', 'tfoot', 'tr', 'th', 'td', 'pre', 'section', 'img',
'figure', 'figcaption', 'span', 'label', 'input', 'details', 'summary', 'address', 'aside', 'footer',
'header', 'hgroup', 'main', 'nav', 'dl', 'dt', 'menu', 'bdi', 'bdo', 'dfn', 'kbd', 'mark', 'q', 'time',
'var', 'wbr', 'area', 'map', 'track', 'video', 'audio', 'picture', 'del', 'ins',
'en-media' // for ENEX import
],
allowedAttributes: {
'a': [ 'href', 'class' ],
'img': [ 'src' ],
'section': [ 'class', 'data-note-id' ],
'figure': [ 'class' ],
'span': [ 'class', 'style' ],
'label': [ 'class' ],
'input': [ 'class', 'type', 'disabled' ],
'code': [ 'class' ],
'ul': [ 'class' ],
'table': [ 'class' ],
'en-media': [ 'hash' ]
'*': [ 'class', 'style', 'title', 'src', 'href', 'hash', 'disabled', 'align', 'alt', 'center', 'data-*' ]
},
allowedSchemes: [
'http', 'https', 'ftp', 'ftps', 'mailto', 'data', 'evernote', 'file', 'facetime', 'irc', 'gemini', 'git',

View File

@@ -121,7 +121,11 @@ function importMarkdown(taskContext, file, parentNote) {
const title = utils.getNoteTitle(file.originalname, taskContext.data.replaceUnderscoresWithSpaces);
const markdownContent = file.buffer.toString("utf-8");
const htmlContent = markdownService.renderToHtml(markdownContent, title);
let htmlContent = markdownService.renderToHtml(markdownContent, title);
if (taskContext.data.safeImport) {
htmlContent = htmlSanitizer.sanitize(htmlContent);
}
const {note} = noteService.createNewNote({
parentNoteId: parentNote.noteId,
@@ -141,7 +145,10 @@ function importHtml(taskContext, file, parentNote) {
const title = utils.getNoteTitle(file.originalname, taskContext.data.replaceUnderscoresWithSpaces);
let content = file.buffer.toString("utf-8");
if (taskContext.data.safeImport) {
content = htmlSanitizer.sanitize(content);
}
content = importUtils.handleH1(content, title);
const {note} = noteService.createNewNote({

View File

@@ -321,7 +321,9 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
}
});
if (taskContext.data.safeImport) {
content = htmlSanitizer.sanitize(content);
}
content = content.replace(/<html.*<body[^>]*>/gis, "");
content = content.replace(/<\/body>.*<\/html>/gis, "");

View File

@@ -63,7 +63,7 @@ const defaultOptions = [
{ name: 'autoFixConsistencyIssues', value: 'true', isSynced: false },
{ name: 'vimKeymapEnabled', value: 'false', isSynced: false },
{ name: 'codeLineWrapEnabled', value: 'true', 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","text/x-sh"]', isSynced: true },
{ name: 'leftPaneWidth', value: '25', isSynced: false },
{ name: 'leftPaneVisible', value: 'true', isSynced: false },
{ name: 'rightPaneWidth', value: '25', isSynced: false },

View File

@@ -292,7 +292,9 @@ async function syncRequest(syncContext, method, requestPath, body) {
return response;
}
function getEntityChangeRow(entityName, entityId) {
function getEntityChangeRow(entityChange) {
const {entityName, entityId} = entityChange;
if (entityName === 'note_reordering') {
return sql.getMap("SELECT branchId, notePosition FROM branches WHERE parentNoteId = ? AND isDeleted = 0", [entityId]);
}
@@ -300,13 +302,13 @@ function getEntityChangeRow(entityName, entityId) {
const primaryKey = entityConstructor.getEntityFromEntityName(entityName).primaryKeyName;
if (!primaryKey) {
throw new Error(`Unknown entity '${entityName}'`);
throw new Error(`Unknown entity for entity change ${JSON.stringify(entityChange)}`);
}
const entityRow = sql.getRow(`SELECT * FROM ${entityName} WHERE ${primaryKey} = ?`, [entityId]);
if (!entityRow) {
throw new Error(`Entity ${entityName} '${entityId}' not found.`);
throw new Error(`Cannot find entity for entity change ${JSON.stringify(entityChange)}`);
}
if (entityName === 'blobs' && entityRow.content !== null) {
@@ -332,7 +334,7 @@ function getEntityChangeRecords(entityChanges) {
continue;
}
const entity = getEntityChangeRow(entityChange.entityName, entityChange.entityId);
const entity = getEntityChangeRow(entityChange);
const record = { entityChange, entity };

View File

@@ -24,7 +24,7 @@ function getContent(note) {
} else if (note.type === 'code') {
renderCode(result);
} else if (note.type === 'mermaid') {
renderMermaid(result);
renderMermaid(result, note);
} else if (note.type === 'image' || note.type === 'canvas') {
renderImage(result, note);
} else if (note.type === 'file') {
@@ -126,15 +126,14 @@ function renderCode(result) {
}
}
function renderMermaid(result) {
function renderMermaid(result, note) {
result.content = `
<div class="mermaid">${escapeHtml(result.content)}</div>
<img src="api/images/${note.noteId}/${note.escapedTitle}?${note.utcDateModified}">
<hr>
<details>
<summary>Chart source</summary>
<pre>${escapeHtml(result.content)}</pre>
</details>`
result.header += `<script src="../../${assetPath}/libraries/mermaid.min.js"></script>`;
}
function renderImage(result, note) {

View File

@@ -105,6 +105,27 @@ function checkNoteAccess(noteId, req, res) {
return false;
}
function renderImageAttachment(image, res, attachmentName) {
let svgString = '<svg/>'
const attachment = image.getAttachmentByTitle(attachmentName);
if (attachment) {
svgString = attachment.getContent();
} else {
// backwards compatibility, before attachments, the SVG was stored in the main note content as a separate key
const contentSvg = image.getJsonContentSafely()?.svg;
if (contentSvg) {
svgString = contentSvg;
}
}
const svg = svgString
res.set('Content-Type', "image/svg+xml");
res.set("Cache-Control", "no-cache, no-store, must-revalidate");
res.send(svg);
}
function register(router) {
function renderNote(note, req, res) {
if (!note) {
@@ -209,37 +230,18 @@ function register(router) {
return;
}
if (!["image", "canvas"].includes(image.type)) {
return res.status(400)
.json({ message: "Requested note is not a shareable image" });
} else if (image.type === "canvas") {
/**
* special "image" type. the canvas is actually type application/json
* to avoid bitrot and enable usage as referenced image the svg is included.
*/
let svgString = '<svg/>'
const attachment = image.getAttachmentByTitle('canvas-export.svg');
if (attachment) {
svgString = attachment.getContent();
} else {
// backwards compatibility, before attachments, the SVG was stored in the main note content as a separate key
const contentSvg = image.getJsonContentSafely()?.svg;
if (contentSvg) {
svgString = contentSvg;
}
}
const svg = svgString
res.set('Content-Type', "image/svg+xml");
res.set("Cache-Control", "no-cache, no-store, must-revalidate");
res.send(svg);
} else {
if (image.type === 'image') {
// normal image
res.set('Content-Type', image.mime);
addNoIndexHeader(image, res);
res.send(image.getContent());
} else if (image.type === "canvas") {
renderImageAttachment(image, res, 'canvas-export.svg');
} else if (image.type === 'mermaid') {
renderImageAttachment(image, res, 'mermaid-export.svg');
} else {
return res.status(400)
.json({ message: "Requested note is not a shareable image" });
}
});