Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 999cf0d9e3 | |||
| 0afc41060a | |||
| 0507c46ae9 | |||
| 6515dd7b23 | |||
| 5a38bf78e1 | |||
| 4bdb40904f | |||
| 7fc1eb5262 | |||
| 531e9d4aff | |||
| e0d4766c2d | |||
| ab4e83771c | |||
| 803b6df40c | |||
| 1ebdb0f5e1 | |||
| df5951ce46 | |||
| d3a477b8f2 | |||
| 01093d05d7 | |||
| a9b63111ae | |||
| ed1a731950 | |||
| ef974ab1f5 | |||
| 1cd391a132 | |||
| 1c15527d95 | |||
| 7af79ec33b | |||
| 4294c043d8 | |||
| e5b925abf8 | |||
| 90c0a4a437 | |||
| 692f7868bc | |||
| 5282af55f6 | |||
| aefc4c6bd2 | |||
| b39ba76505 | |||
| 9d918e7a54 | |||
| 38db7f9db7 | |||
| 5163e50e7d |
BIN
Binary file not shown.
@@ -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)) {
|
||||
|
||||
@@ -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';
|
||||
@@ -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 {
|
||||
|
||||
Vendored
+2
-2
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Vendored
+197
-198
File diff suppressed because one or more lines are too long
Generated
+16
-16
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "trilium",
|
||||
"version": "0.61.9-beta",
|
||||
"version": "0.61.13",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "trilium",
|
||||
"version": "0.61.9-beta",
|
||||
"version": "0.61.13",
|
||||
"hasInstallScript": true,
|
||||
"license": "AGPL-3.0-only",
|
||||
"dependencies": {
|
||||
@@ -47,7 +47,7 @@
|
||||
"jimp": "0.22.10",
|
||||
"joplin-turndown-plugin-gfm": "1.0.12",
|
||||
"jsdom": "22.1.0",
|
||||
"marked": "9.1.2",
|
||||
"marked": "9.1.6",
|
||||
"mime-types": "2.1.35",
|
||||
"multer": "1.4.5-lts.1",
|
||||
"node-abi": "3.50.0",
|
||||
@@ -79,7 +79,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"cross-env": "7.0.3",
|
||||
"electron": "25.9.2",
|
||||
"electron": "25.9.5",
|
||||
"electron-builder": "24.6.4",
|
||||
"electron-packager": "17.1.2",
|
||||
"electron-rebuild": "3.2.9",
|
||||
@@ -4276,9 +4276,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/electron": {
|
||||
"version": "25.9.2",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-25.9.2.tgz",
|
||||
"integrity": "sha512-hVBN5rsrL99BKNHvzMeYy2PkAmewuIobu4U3o3EzVz4MDoLmMfW4yTH5GZ4RbJrpokoEky5IzGtRR/ggPzL6Fw==",
|
||||
"version": "25.9.5",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-25.9.5.tgz",
|
||||
"integrity": "sha512-gM7GXUSd3JVRcYbBnNOtZeNnE5MCJjtZTT8QyIxJvpQ0Dh9dz3hTuEL62dOwnMFW/l47ACQ6es/8qi01P4QGZA==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@electron/get": "^2.0.0",
|
||||
@@ -9037,9 +9037,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/marked": {
|
||||
"version": "9.1.2",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-9.1.2.tgz",
|
||||
"integrity": "sha512-qoKMJqK0w6vkLk8+KnKZAH6neUZSNaQqVZ/h2yZ9S7CbLuFHyS2viB0jnqcWF9UKjwsAbMrQtnQhdmdvOVOw9w==",
|
||||
"version": "9.1.6",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-9.1.6.tgz",
|
||||
"integrity": "sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q==",
|
||||
"bin": {
|
||||
"marked": "bin/marked.js"
|
||||
},
|
||||
@@ -16633,9 +16633,9 @@
|
||||
}
|
||||
},
|
||||
"electron": {
|
||||
"version": "25.9.2",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-25.9.2.tgz",
|
||||
"integrity": "sha512-hVBN5rsrL99BKNHvzMeYy2PkAmewuIobu4U3o3EzVz4MDoLmMfW4yTH5GZ4RbJrpokoEky5IzGtRR/ggPzL6Fw==",
|
||||
"version": "25.9.5",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-25.9.5.tgz",
|
||||
"integrity": "sha512-gM7GXUSd3JVRcYbBnNOtZeNnE5MCJjtZTT8QyIxJvpQ0Dh9dz3hTuEL62dOwnMFW/l47ACQ6es/8qi01P4QGZA==",
|
||||
"requires": {
|
||||
"@electron/get": "^2.0.0",
|
||||
"@types/node": "^18.11.18",
|
||||
@@ -20139,9 +20139,9 @@
|
||||
"requires": {}
|
||||
},
|
||||
"marked": {
|
||||
"version": "9.1.2",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-9.1.2.tgz",
|
||||
"integrity": "sha512-qoKMJqK0w6vkLk8+KnKZAH6neUZSNaQqVZ/h2yZ9S7CbLuFHyS2viB0jnqcWF9UKjwsAbMrQtnQhdmdvOVOw9w=="
|
||||
"version": "9.1.6",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-9.1.6.tgz",
|
||||
"integrity": "sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q=="
|
||||
},
|
||||
"matcher": {
|
||||
"version": "3.0.0",
|
||||
|
||||
+8
-4
@@ -2,7 +2,7 @@
|
||||
"name": "trilium",
|
||||
"productName": "Trilium Notes",
|
||||
"description": "Trilium Notes",
|
||||
"version": "0.61.10-beta",
|
||||
"version": "0.61.14",
|
||||
"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",
|
||||
@@ -68,7 +72,7 @@
|
||||
"jimp": "0.22.10",
|
||||
"joplin-turndown-plugin-gfm": "1.0.12",
|
||||
"jsdom": "22.1.0",
|
||||
"marked": "9.1.2",
|
||||
"marked": "9.1.6",
|
||||
"mime-types": "2.1.35",
|
||||
"multer": "1.4.5-lts.1",
|
||||
"node-abi": "3.50.0",
|
||||
@@ -97,7 +101,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"cross-env": "7.0.3",
|
||||
"electron": "25.9.2",
|
||||
"electron": "25.9.5",
|
||||
"electron-builder": "24.6.4",
|
||||
"electron-packager": "17.1.2",
|
||||
"electron-rebuild": "3.2.9",
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
describe("Notes", () => {
|
||||
it("zzz", () => {
|
||||
|
||||
});
|
||||
});
|
||||
+21
-19
@@ -1156,14 +1156,8 @@ class BNote extends AbstractBeccaEntity {
|
||||
|
||||
/** @returns {BAttachment} */
|
||||
getAttachmentByTitle(title) {
|
||||
return sql.getRows(`
|
||||
SELECT attachments.*
|
||||
FROM attachments
|
||||
WHERE ownerId = ?
|
||||
AND title = ?
|
||||
AND isDeleted = 0
|
||||
ORDER BY position`, [this.noteId, title])
|
||||
.map(row => new BAttachment(row))[0];
|
||||
// cannot use SQL to filter by title since it can be encrypted
|
||||
return this.getAttachments().filter(attachment => attachment.title === title)[0];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1635,24 +1629,32 @@ 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({
|
||||
ownerId: this.noteId,
|
||||
title,
|
||||
role,
|
||||
mime,
|
||||
isProtected: this.isProtected,
|
||||
position
|
||||
});
|
||||
}
|
||||
|
||||
attachment = attachment || new BAttachment({
|
||||
ownerId: this.noteId,
|
||||
title,
|
||||
role,
|
||||
mime,
|
||||
isProtected: this.isProtected,
|
||||
position
|
||||
});
|
||||
|
||||
content = content || "";
|
||||
attachment.setContent(content, {forceSave: true});
|
||||
|
||||
|
||||
@@ -157,14 +157,8 @@ class BRevision extends AbstractBeccaEntity {
|
||||
|
||||
/** @returns {BAttachment} */
|
||||
getAttachmentByTitle(title) {
|
||||
return sql.getRows(`
|
||||
SELECT attachments.*
|
||||
FROM attachments
|
||||
WHERE ownerId = ?
|
||||
AND title = ?
|
||||
AND isDeleted = 0
|
||||
ORDER BY position`, [this.revisionId, title])
|
||||
.map(row => new BAttachment(row))[0];
|
||||
// cannot use SQL to filter by title since it can be encrypted
|
||||
return this.getAttachments().filter(attachment => attachment.title === title)[0];
|
||||
}
|
||||
|
||||
beforeSaving() {
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -268,7 +268,9 @@ function linkContextMenu(e) {
|
||||
}
|
||||
|
||||
async function loadReferenceLinkTitle($el, href = null) {
|
||||
href = href || $el.find("a").attr("href");
|
||||
const $link = $el[0].tagName === 'A' ? $el : $el.find("a");
|
||||
|
||||
href = href || $link.attr("href");
|
||||
if (!href) {
|
||||
console.warn("Empty URL for parsing: " + $el[0].outerHTML);
|
||||
return;
|
||||
@@ -286,7 +288,7 @@ async function loadReferenceLinkTitle($el, href = null) {
|
||||
|
||||
if (note) {
|
||||
const icon = await getLinkIcon(noteId, viewScope.viewMode);
|
||||
|
||||
k
|
||||
$el.prepend($("<span>").addClass(icon));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,7 +172,7 @@ if (utils.isElectron()) {
|
||||
await reportError(arg.method, arg.url, arg.statusCode, arg.body);
|
||||
}
|
||||
|
||||
idToRequestMap[arg.requestId].reject();
|
||||
idToRequestMap[arg.requestId].reject(new Error(`Server responded with ${arg.statusCode}`));
|
||||
}
|
||||
|
||||
delete idToRequestMap[arg.requestId];
|
||||
|
||||
@@ -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%;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
@@ -617,7 +618,7 @@ export default class TabRowWidget extends BasicWidget {
|
||||
|
||||
// update tab id for the new main context
|
||||
this.getTabById(oldMainNtxId).attr("data-ntx-id", newMainNtxId);
|
||||
this.updateTabById(newMainNtxId);
|
||||
this.updateTabById(newMainNtxId);
|
||||
}
|
||||
|
||||
contextsReopenedEvent({mainNtxId, tabPosition}) {
|
||||
|
||||
@@ -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
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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}` : ''}`));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;">
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+26
-23
@@ -25,33 +25,14 @@ 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') {
|
||||
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);
|
||||
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");
|
||||
@@ -59,6 +40,28 @@ function returnImageInt(image, res) {
|
||||
}
|
||||
}
|
||||
|
||||
function renderSvgAttachment(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 returnAttachedImage(req, res) {
|
||||
const attachment = becca.getAttachment(req.params.attachmentId);
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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};
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
module.exports = { buildDate:"2023-10-19T09:33:51+02:00", buildRevision: "b01fe5ead9268784fb133a8cfa53670927ba0e3b" };
|
||||
module.exports = { buildDate:"2023-11-16T00:21:34+01:00", buildRevision: "0afc41060a935a46642a082128bea5002c2410e1" };
|
||||
|
||||
@@ -75,15 +75,14 @@ async function exportToZip(taskContext, branch, format, res, setHeaders = true)
|
||||
* @return {string}
|
||||
*/
|
||||
function getDataFileName(type, mime, baseFileName, existingFileNames) {
|
||||
let fileName = baseFileName;
|
||||
let fileName = baseFileName.trim();
|
||||
if (fileName.length > 30) {
|
||||
fileName = fileName.substr(0, 30).trim();
|
||||
}
|
||||
|
||||
let existingExtension = path.extname(fileName).toLowerCase();
|
||||
let newExtension;
|
||||
|
||||
if (fileName.length > 30) {
|
||||
fileName = fileName.substr(0, 30);
|
||||
}
|
||||
|
||||
// the following two are handled specifically since we always want to have these extensions no matter the automatic detection
|
||||
// and/or existing detected extensions in the note name
|
||||
if (type === 'text' && format === 'markdown') {
|
||||
@@ -108,7 +107,7 @@ async function exportToZip(taskContext, branch, format, res, setHeaders = true)
|
||||
}
|
||||
}
|
||||
|
||||
// if the note is already named with extension (e.g. "jquery"), then it's silly to append the exact same extension again
|
||||
// if the note is already named with the extension (e.g. "image.jpg"), then it's silly to append the exact same extension again
|
||||
if (newExtension && existingExtension !== `.${newExtension.toLowerCase()}`) {
|
||||
fileName += `.${newExtension}`;
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
+10
-2
@@ -148,8 +148,16 @@ function saveImageToAttachment(noteId, uploadBuffer, originalName, shrinkImageSw
|
||||
title: fileName
|
||||
});
|
||||
|
||||
const noteService = require("../services/notes");
|
||||
noteService.asyncPostProcessContent(note, note.getContent()); // to mark an unused attachment for deletion
|
||||
// TODO: this is a quick-fix solution of a recursive bug - this is called from asyncPostProcessContent()
|
||||
// find some async way to do this - perhaps some global timeout with a Set of noteIds needing one more
|
||||
// run of asyncPostProcessContent
|
||||
setTimeout(() => {
|
||||
sql.transactional(() => {
|
||||
const note = becca.getNoteOrThrow(noteId);
|
||||
const noteService = require("../services/notes");
|
||||
noteService.asyncPostProcessContent(note, note.getContent()); // to mark an unused attachment for deletion
|
||||
});
|
||||
}, 5000);
|
||||
|
||||
// resizing images asynchronously since JIMP does not support sync operation
|
||||
processImage(uploadBuffer, originalName, shrinkImageSwitch).then(({buffer, imageFormat}) => {
|
||||
|
||||
@@ -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");
|
||||
|
||||
content = htmlSanitizer.sanitize(content);
|
||||
if (taskContext.data.safeImport) {
|
||||
content = htmlSanitizer.sanitize(content);
|
||||
}
|
||||
|
||||
content = importUtils.handleH1(content, title);
|
||||
|
||||
const {note} = noteService.createNewNote({
|
||||
|
||||
@@ -321,7 +321,9 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
||||
}
|
||||
});
|
||||
|
||||
content = htmlSanitizer.sanitize(content);
|
||||
if (taskContext.data.safeImport) {
|
||||
content = htmlSanitizer.sanitize(content);
|
||||
}
|
||||
|
||||
content = content.replace(/<html.*<body[^>]*>/gis, "");
|
||||
content = content.replace(/<\/body>.*<\/html>/gis, "");
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -10,6 +10,8 @@ class AttributeExistsExp extends Expression {
|
||||
|
||||
this.attributeType = attributeType;
|
||||
this.attributeName = attributeName;
|
||||
// template attr is used as a marker for templates, but it's not meant to be inherited
|
||||
this.isTemplateLabel = this.attributeType === 'label' && (this.attributeName === 'template' || this.attributeName === 'workspacetemplate');
|
||||
this.prefixMatch = prefixMatch;
|
||||
}
|
||||
|
||||
@@ -23,12 +25,10 @@ class AttributeExistsExp extends Expression {
|
||||
for (const attr of attrs) {
|
||||
const note = attr.note;
|
||||
|
||||
if (attr.isInheritable) {
|
||||
if (attr.isInheritable && !this.isTemplateLabel) {
|
||||
resultNoteSet.addAll(note.getSubtreeNotesIncludingTemplated());
|
||||
}
|
||||
else if (note.isInherited() &&
|
||||
// template attr is used as a marker for templates, but it's not meant to be inherited
|
||||
!(this.attributeType === 'label' && (this.attributeName === 'template' || this.attributeName === 'workspacetemplate'))) {
|
||||
else if (note.isInherited() && !this.isTemplateLabel) {
|
||||
resultNoteSet.addAll(note.getInheritingNotes());
|
||||
}
|
||||
else {
|
||||
|
||||
+10
-4
@@ -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,14 @@ 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.`);
|
||||
log.error(`Cannot find entity for entity change ${JSON.stringify(entityChange)}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (entityName === 'blobs' && entityRow.content !== null) {
|
||||
@@ -332,7 +335,10 @@ function getEntityChangeRecords(entityChanges) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const entity = getEntityChangeRow(entityChange.entityName, entityChange.entityId);
|
||||
const entity = getEntityChangeRow(entityChange);
|
||||
if (!entity) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const record = { entityChange, entity };
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+29
-27
@@ -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" });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user