Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f4266754d8 | |||
| dc288fb18c | |||
| 26dfa1ffdb | |||
| 153de63f4d | |||
| a89629b3de | |||
| eec850c11f | |||
| e8d63b5647 | |||
| bd8b83898f | |||
| 97109efb6c | |||
| 3e89855aa3 | |||
| 3b148eb6f8 | |||
| 4e8d1dac67 | |||
| 7779fd1dfe | |||
| 960d7dede3 | |||
| 224fbdc8cd | |||
| 8561201abc | |||
| 3c1a809276 | |||
| 4b101baf00 | |||
| 782127dd91 | |||
| 4fc8bace94 | |||
| 47a22f6e8d | |||
| 17d7ff3ff1 | |||
| 3582013a33 | |||
| 95bbdb3b6b | |||
| 8a57960c6e | |||
| 5f4a84d967 | |||
| 099e90ed64 | |||
| 3d324b954d | |||
| 443f389d73 | |||
| 08edc521e4 | |||
| f54f6d09b0 | |||
| 3219441fdf | |||
| 1b0a2b41da | |||
| 582429e762 | |||
| 238df0fb40 | |||
| 89356918f1 | |||
| 263b65997c | |||
| 2b757bfccd | |||
| c78ca4c9db | |||
| a0395e9866 | |||
| 89aa4fbc73 | |||
| 74a7802088 | |||
| a89b6711d1 | |||
| b2549b2834 | |||
| 959c4cbe64 | |||
| d03d3603d2 | |||
| e1c2573778 | |||
| 2af2b45062 | |||
| eabe4775bd | |||
| da9b321aa0 | |||
| c18d8d2d1b |
BIN
Binary file not shown.
@@ -0,0 +1,4 @@
|
||||
UPDATE sync SET isSynced = 1 WHERE entityName != 'options' OR (
|
||||
entityName = 'options'
|
||||
AND 1 = (SELECT isSynced FROM options WHERE name = sync.entityId)
|
||||
)
|
||||
@@ -1,23 +1,53 @@
|
||||
/*
|
||||
* !!!!!!! This stylesheet is heavily modified compared to the original for similarity with in-editor look !!!!!!!
|
||||
* This is used for printing and tar HTML export
|
||||
/* !!!!!! TRILIUM CUSTOM CHANGES !!!!!! */
|
||||
|
||||
* CKEditor 5 (v17.0.0) content styles.
|
||||
* Generated on Fri, 13 Mar 2020 13:27:10 GMT.
|
||||
.ck-widget__type-around { /* gets rid of triangles: https://github.com/zadam/trilium/issues/1129 */
|
||||
display: none;
|
||||
}
|
||||
|
||||
/*
|
||||
* CKEditor 5 (v21.0.0) content styles.
|
||||
* Generated on Wed, 29 Jul 2020 12:14:43 GMT.
|
||||
* For more information, check out https://ckeditor.com/docs/ckeditor5/latest/builds/guides/integration/content-styles.html
|
||||
*/
|
||||
|
||||
:root {
|
||||
--ck-highlight-marker-blue: #72cdfd;
|
||||
--ck-highlight-marker-green: #63f963;
|
||||
--ck-highlight-marker-pink: #fc7999;
|
||||
--ck-highlight-marker-yellow: #fdfd77;
|
||||
--ck-highlight-pen-green: #118800;
|
||||
--ck-highlight-pen-red: #e91313;
|
||||
--ck-color-mention-background: hsla(341, 100%, 30%, 0.1);
|
||||
--ck-color-mention-text: hsl(341, 100%, 30%);
|
||||
--ck-highlight-marker-blue: hsl(201, 97%, 72%);
|
||||
--ck-highlight-marker-green: hsl(120, 93%, 68%);
|
||||
--ck-highlight-marker-pink: hsl(345, 96%, 73%);
|
||||
--ck-highlight-marker-yellow: hsl(60, 97%, 73%);
|
||||
--ck-highlight-pen-green: hsl(112, 100%, 27%);
|
||||
--ck-highlight-pen-red: hsl(0, 85%, 49%);
|
||||
--ck-image-style-spacing: 1.5em;
|
||||
--ck-todo-list-checkmark-size: 16px;
|
||||
}
|
||||
|
||||
/* ckeditor5-image/theme/image.css */
|
||||
.ck-content .image {
|
||||
display: table;
|
||||
clear: both;
|
||||
text-align: center;
|
||||
margin: 1em auto;
|
||||
}
|
||||
/* ckeditor5-image/theme/image.css */
|
||||
.ck-content .image img {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
max-width: 100%;
|
||||
min-width: 50px;
|
||||
}
|
||||
/* ckeditor5-image/theme/imagecaption.css */
|
||||
.ck-content .image > figcaption {
|
||||
display: table-caption;
|
||||
caption-side: bottom;
|
||||
word-break: break-word;
|
||||
color: hsl(0, 0%, 20%);
|
||||
background-color: hsl(0, 0%, 97%);
|
||||
padding: .6em;
|
||||
font-size: .75em;
|
||||
outline-offset: -1px;
|
||||
}
|
||||
/* ckeditor5-image/theme/imageresize.css */
|
||||
.ck-content .image.image_resized {
|
||||
max-width: 100%;
|
||||
@@ -32,37 +62,11 @@
|
||||
.ck-content .image.image_resized > figcaption {
|
||||
display: block;
|
||||
}
|
||||
/* ckeditor5-basic-styles/theme/code.css */
|
||||
.ck-content code {
|
||||
background-color: hsla(0, 0%, 78%, 0.3);
|
||||
padding: .15em;
|
||||
border-radius: 2px;
|
||||
}
|
||||
/* ckeditor5-image/theme/image.css */
|
||||
.ck-content .image {
|
||||
display: table;
|
||||
clear: both;
|
||||
text-align: center;
|
||||
margin: 1em auto;
|
||||
}
|
||||
/* ckeditor5-image/theme/image.css */
|
||||
.ck-content .image > img {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
max-width: 100%;
|
||||
min-width: 50px;
|
||||
}
|
||||
/* ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-style-side,
|
||||
.ck-content .image-style-align-left,
|
||||
.ck-content .image-style-align-center,
|
||||
.ck-content .image-style-align-right {
|
||||
max-width: 50%;
|
||||
}
|
||||
/* ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-style-side {
|
||||
float: right;
|
||||
margin-left: var(--ck-image-style-spacing);
|
||||
max-width: 50%;
|
||||
}
|
||||
/* ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-style-align-left {
|
||||
@@ -79,42 +83,6 @@
|
||||
float: right;
|
||||
margin-left: var(--ck-image-style-spacing);
|
||||
}
|
||||
/* ckeditor5-page-break/theme/pagebreak.css */
|
||||
.ck-content .page-break {
|
||||
position: relative;
|
||||
clear: both;
|
||||
padding: 5px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
/* ckeditor5-page-break/theme/pagebreak.css */
|
||||
.ck-content .page-break::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
border-bottom: 2px dashed hsl(0, 0%, 77%);
|
||||
width: 100%;
|
||||
}
|
||||
/* ckeditor5-page-break/theme/pagebreak.css */
|
||||
.ck-content .page-break__label {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding: .3em .6em;
|
||||
display: block;
|
||||
text-transform: uppercase;
|
||||
border: 1px solid hsl(0, 0%, 77%);
|
||||
border-radius: 2px;
|
||||
font-family: Helvetica, Arial, Tahoma, Verdana, Sans-Serif;
|
||||
font-size: 0.75em;
|
||||
font-weight: bold;
|
||||
color: hsl(0, 0%, 20%);
|
||||
background: #fff;
|
||||
box-shadow: 2px 2px 1px hsla(0, 0%, 0%, 0.15);
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
/* ckeditor5-block-quote/theme/blockquote.css */
|
||||
.ck-content blockquote {
|
||||
overflow: hidden;
|
||||
@@ -130,38 +98,6 @@
|
||||
border-left: 0;
|
||||
border-right: solid 5px hsl(0, 0%, 80%);
|
||||
}
|
||||
/* ckeditor5-media-embed/theme/mediaembed.css */
|
||||
.ck-content .media {
|
||||
clear: both;
|
||||
margin: 1em 0;
|
||||
display: block;
|
||||
min-width: 15em;
|
||||
}
|
||||
/* ckeditor5-table/theme/table.css */
|
||||
.ck-content .table {
|
||||
margin: 1em auto;
|
||||
display: table;
|
||||
}
|
||||
/* ckeditor5-table/theme/table.css */
|
||||
.ck-content .table table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px double hsl(0, 0%, 70%);
|
||||
}
|
||||
/* ckeditor5-table/theme/table.css */
|
||||
.ck-content .table table td,
|
||||
.ck-content .table table th {
|
||||
min-width: 2em;
|
||||
padding: .4em;
|
||||
border-color: hsl(0, 0%, 75%);
|
||||
}
|
||||
/* ckeditor5-table/theme/table.css */
|
||||
.ck-content .table table th {
|
||||
font-weight: bold;
|
||||
background: hsla(0, 0%, 0%, 5%);
|
||||
}
|
||||
/* ckeditor5-list/theme/todolist.css */
|
||||
.ck-content .todo-list {
|
||||
list-style: none;
|
||||
@@ -229,16 +165,12 @@
|
||||
.ck-content .todo-list .todo-list__label .todo-list__label__description {
|
||||
vertical-align: middle;
|
||||
}
|
||||
/* ckeditor5-image/theme/imagecaption.css */
|
||||
.ck-content .image > figcaption {
|
||||
display: table-caption;
|
||||
caption-side: bottom;
|
||||
word-break: break-word;
|
||||
color: hsl(0, 0%, 20%);
|
||||
background-color: hsl(0, 0%, 97%);
|
||||
padding: .6em;
|
||||
font-size: .75em;
|
||||
outline-offset: -1px;
|
||||
/* ckeditor5-horizontal-line/theme/horizontalline.css */
|
||||
.ck-content hr {
|
||||
margin: 15px 0;
|
||||
height: 4px;
|
||||
background: hsl(0, 0%, 87%);
|
||||
border: 0;
|
||||
}
|
||||
/* ckeditor5-highlight/theme/highlight.css */
|
||||
.ck-content .marker-yellow {
|
||||
@@ -266,17 +198,108 @@
|
||||
color: var(--ck-highlight-pen-green);
|
||||
background-color: transparent;
|
||||
}
|
||||
/* ckeditor5-horizontal-line/theme/horizontalline.css */
|
||||
.ck-content hr {
|
||||
border-width: 1px 0 0;
|
||||
border-style: solid;
|
||||
border-color: hsl(0, 0%, 37%);
|
||||
margin: 0;
|
||||
/* ckeditor5-font/theme/fontsize.css */
|
||||
.ck-content .text-tiny {
|
||||
font-size: .7em;
|
||||
}
|
||||
/* ckeditor5-font/theme/fontsize.css */
|
||||
.ck-content .text-small {
|
||||
font-size: .85em;
|
||||
}
|
||||
/* ckeditor5-font/theme/fontsize.css */
|
||||
.ck-content .text-big {
|
||||
font-size: 1.4em;
|
||||
}
|
||||
/* ckeditor5-font/theme/fontsize.css */
|
||||
.ck-content .text-huge {
|
||||
font-size: 1.8em;
|
||||
}
|
||||
/* ckeditor5-basic-styles/theme/code.css */
|
||||
.ck-content code {
|
||||
background-color: hsla(0, 0%, 78%, 0.3);
|
||||
padding: .15em;
|
||||
border-radius: 2px;
|
||||
}
|
||||
/* ckeditor5-table/theme/table.css */
|
||||
.ck-content .table {
|
||||
margin: 1em auto;
|
||||
display: table;
|
||||
}
|
||||
/* ckeditor5-table/theme/table.css */
|
||||
.ck-content .table table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px double hsl(0, 0%, 70%);
|
||||
}
|
||||
/* ckeditor5-table/theme/table.css */
|
||||
.ck-content .table table td,
|
||||
.ck-content .table table th {
|
||||
min-width: 2em;
|
||||
padding: .4em;
|
||||
border: 1px solid hsl(0, 0%, 75%);
|
||||
}
|
||||
/* ckeditor5-table/theme/table.css */
|
||||
.ck-content .table table th {
|
||||
font-weight: bold;
|
||||
background: hsla(0, 0%, 0%, 5%);
|
||||
}
|
||||
/* ckeditor5-table/theme/table.css */
|
||||
.ck-content[dir="rtl"] .table th {
|
||||
text-align: right;
|
||||
}
|
||||
/* ckeditor5-table/theme/table.css */
|
||||
.ck-content[dir="ltr"] .table th {
|
||||
text-align: left;
|
||||
}
|
||||
/* ckeditor5-page-break/theme/pagebreak.css */
|
||||
.ck-content .page-break {
|
||||
position: relative;
|
||||
clear: both;
|
||||
padding: 5px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
/* ckeditor5-page-break/theme/pagebreak.css */
|
||||
.ck-content .page-break::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
border-bottom: 2px dashed hsl(0, 0%, 77%);
|
||||
width: 100%;
|
||||
}
|
||||
/* ckeditor5-page-break/theme/pagebreak.css */
|
||||
.ck-content .page-break__label {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding: .3em .6em;
|
||||
display: block;
|
||||
text-transform: uppercase;
|
||||
border: 1px solid hsl(0, 0%, 77%);
|
||||
border-radius: 2px;
|
||||
font-family: Helvetica, Arial, Tahoma, Verdana, Sans-Serif;
|
||||
font-size: 0.75em;
|
||||
font-weight: bold;
|
||||
color: hsl(0, 0%, 20%);
|
||||
background: hsl(0, 0%, 100%);
|
||||
box-shadow: 2px 2px 1px hsla(0, 0%, 0%, 0.15);
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
/* ckeditor5-media-embed/theme/mediaembed.css */
|
||||
.ck-content .media {
|
||||
clear: both;
|
||||
margin: 1em 0;
|
||||
display: block;
|
||||
min-width: 15em;
|
||||
}
|
||||
/* ckeditor5-code-block/theme/codeblock.css */
|
||||
.ck-content pre {
|
||||
padding: 1em;
|
||||
color: #353535;
|
||||
color: hsl(0, 0%, 20.8%);
|
||||
background: hsla(0, 0%, 78%, 0.3);
|
||||
border: 1px solid hsl(0, 0%, 77%);
|
||||
border-radius: 2px;
|
||||
@@ -293,6 +316,11 @@
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
/* ckeditor5-mention/theme/mention.css */
|
||||
.ck-content .mention {
|
||||
background: var(--ck-color-mention-background);
|
||||
color: var(--ck-color-mention-text);
|
||||
}
|
||||
@media print {
|
||||
/* ckeditor5-page-break/theme/pagebreak.css */
|
||||
.ck-content .page-break {
|
||||
@@ -302,4 +330,4 @@
|
||||
.ck-content .page-break::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
-1
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
Generated
+7
-7
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "trilium",
|
||||
"version": "0.42.7",
|
||||
"version": "0.43.3",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -3345,9 +3345,9 @@
|
||||
}
|
||||
},
|
||||
"electron": {
|
||||
"version": "9.0.4",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-9.0.4.tgz",
|
||||
"integrity": "sha512-QzkeZNAiNB7KxcdoQKSoaiVT/GQdB4Vt0/ZZOuU8tIKABAsni2I7ztiAbUzxcsnQsqEBSfChuPuDQ5A4VbbzPg==",
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-9.0.5.tgz",
|
||||
"integrity": "sha512-bnL9H48LuQ250DML8xUscsKiuSu+xv5umXbpBXYJ0BfvYVmFfNbG3jCfhrsH7aP6UcQKVxOG1R/oQExd0EFneQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@electron/get": "^1.0.1",
|
||||
@@ -7923,9 +7923,9 @@
|
||||
"integrity": "sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q=="
|
||||
},
|
||||
"node-abi": {
|
||||
"version": "2.16.0",
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.16.0.tgz",
|
||||
"integrity": "sha512-+sa0XNlWDA6T+bDLmkCUYn6W5k5W6BPRL6mqzSCs6H/xUgtl4D5x2fORKDzopKiU6wsyn/+wXlRXwXeSp+mtoA==",
|
||||
"version": "2.18.0",
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.18.0.tgz",
|
||||
"integrity": "sha512-yi05ZoiuNNEbyT/xXfSySZE+yVnQW6fxPZuFbLyS1s6b5Kw3HzV2PHOM4XR+nsjzkHxByK+2Wg+yCQbe35l8dw==",
|
||||
"requires": {
|
||||
"semver": "^5.4.1"
|
||||
},
|
||||
|
||||
+3
-3
@@ -2,7 +2,7 @@
|
||||
"name": "trilium",
|
||||
"productName": "Trilium Notes",
|
||||
"description": "Trilium Notes",
|
||||
"version": "0.43.0-beta",
|
||||
"version": "0.43.4",
|
||||
"license": "AGPL-3.0-only",
|
||||
"main": "electron.js",
|
||||
"bin": {
|
||||
@@ -54,7 +54,7 @@
|
||||
"jimp": "0.10.3",
|
||||
"mime-types": "2.1.27",
|
||||
"multer": "1.4.2",
|
||||
"node-abi": "2.16.0",
|
||||
"node-abi": "2.18.0",
|
||||
"open": "7.0.3",
|
||||
"portscanner": "2.2.0",
|
||||
"rand-token": "1.0.1",
|
||||
@@ -78,7 +78,7 @@
|
||||
"yazl": "^2.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"electron": "9.0.4",
|
||||
"electron": "9.0.5",
|
||||
"electron-builder": "22.6.0",
|
||||
"electron-packager": "14.2.1",
|
||||
"electron-rebuild": "1.10.1",
|
||||
|
||||
@@ -17,7 +17,6 @@ export async function showDialog(widget) {
|
||||
|
||||
$addLinkTitleSettings.toggle(!textTypeWidget.hasSelection());
|
||||
|
||||
updateTitleFormGroupVisibility();
|
||||
$addLinkTitleSettings.find('input[type=radio]').on('change', updateTitleFormGroupVisibility);
|
||||
|
||||
// with selection hyper link is implied
|
||||
@@ -28,6 +27,8 @@ export async function showDialog(widget) {
|
||||
$addLinkTitleSettings.find("input[value='reference-link']").prop("checked", true);
|
||||
}
|
||||
|
||||
updateTitleFormGroupVisibility();
|
||||
|
||||
utils.openDialog($dialog);
|
||||
|
||||
$autoComplete.val('').trigger('focus');
|
||||
@@ -89,4 +90,4 @@ $form.on('submit', () => {
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -52,7 +52,17 @@ class Attribute {
|
||||
* 3. attribute is owned by some note's ancestor and is inheritable
|
||||
*/
|
||||
isAffecting(affectedNote) {
|
||||
if (!affectedNote) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const attrNote = this.getNote();
|
||||
|
||||
if (!attrNote) {
|
||||
// the note (owner of the attribute) is not even loaded into the cache so it should not affect anything else
|
||||
return false;
|
||||
}
|
||||
|
||||
const owningNotes = [affectedNote, ...affectedNote.getTemplateNotes()];
|
||||
|
||||
for (const owningNote of owningNotes) {
|
||||
|
||||
@@ -24,6 +24,7 @@ import NoteRevisionsWidget from "../widgets/collapsible_widgets/note_revisions.j
|
||||
import SimilarNotesWidget from "../widgets/collapsible_widgets/similar_notes.js";
|
||||
import WhatLinksHereWidget from "../widgets/collapsible_widgets/what_links_here.js";
|
||||
import SidePaneToggles from "../widgets/side_pane_toggles.js";
|
||||
import EditedNotesWidget from "../widgets/collapsible_widgets/edited_notes.js";
|
||||
|
||||
const RIGHT_PANE_CSS = `
|
||||
<style>
|
||||
@@ -143,6 +144,7 @@ export default class DesktopMainWindowLayout {
|
||||
.hideInZenMode()
|
||||
.child(new NoteInfoWidget())
|
||||
.child(new TabCachingWidget(() => new CalendarWidget()))
|
||||
.child(new TabCachingWidget(() => new EditedNotesWidget()))
|
||||
.child(new TabCachingWidget(() => new AttributesWidget()))
|
||||
.child(new TabCachingWidget(() => new LinkMapWidget()))
|
||||
.child(new TabCachingWidget(() => new NoteRevisionsWidget()))
|
||||
|
||||
@@ -83,6 +83,8 @@ function setupGlobs() {
|
||||
|
||||
$("body").on("click", "a.external", function () {
|
||||
window.open($(this).attr("href"), '_blank');
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -164,6 +164,7 @@ $(document).on('mousedown', '.note-detail-text a.reference-link', goToLink);
|
||||
$(document).on('mousedown', '.note-detail-readonly-text a.reference-link', goToLink);
|
||||
$(document).on('mousedown', '.note-detail-readonly-text a', goToLink);
|
||||
$(document).on('mousedown', 'a.ck-link-actions__preview', goToLink);
|
||||
$(document).on('click', 'section.include-note a', goToLink);
|
||||
$(document).on('click', 'a.ck-link-actions__preview', e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
@@ -175,6 +176,7 @@ $(document).on('contextmenu', '.note-detail-readonly-text a', linkContextMenu);
|
||||
$(document).on('contextmenu', "a[data-action='note']", linkContextMenu);
|
||||
$(document).on('contextmenu', ".note-detail-render a", linkContextMenu);
|
||||
$(document).on('contextmenu', ".note-paths-widget a", linkContextMenu);
|
||||
$(document).on('contextmenu', "section.include-note a", linkContextMenu);
|
||||
|
||||
export default {
|
||||
getNotePathFromUrl,
|
||||
|
||||
@@ -107,8 +107,8 @@ export default class LoadResults {
|
||||
* notably changes in note itself should not have any effect on attributes
|
||||
*/
|
||||
hasAttributeRelatedChanges() {
|
||||
return this.branches.length === 0
|
||||
&& this.attributes.length === 0;
|
||||
return this.branches.length > 0
|
||||
|| this.attributes.length > 0;
|
||||
}
|
||||
|
||||
isEmpty() {
|
||||
|
||||
@@ -24,7 +24,7 @@ async function getRenderedContent(note) {
|
||||
.attr("src", `api/images/${note.noteId}/${note.title}`)
|
||||
.css("max-width", "100%");
|
||||
}
|
||||
else if (type === 'file') {
|
||||
else if (type === 'file' || type === 'pdf') {
|
||||
function getFileUrl() {
|
||||
return utils.getUrlForDownload("api/notes/" + note.noteId + "/download");
|
||||
}
|
||||
@@ -47,19 +47,21 @@ async function getRenderedContent(note) {
|
||||
// open doesn't work for protected notes since it works through browser which isn't in protected session
|
||||
$openButton.toggle(!note.isProtected);
|
||||
|
||||
$rendered = $('<div>');
|
||||
$rendered = $('<div style="display: flex; flex-direction: column; height: 100%;">');
|
||||
|
||||
if (note.mime === 'application/pdf' && utils.isElectron()) {
|
||||
const $pdfPreview = $('<iframe class="pdf-preview" style="width: 100%; height: 100%; flex-grow: 100;"></iframe>');
|
||||
if (type === 'pdf') {
|
||||
const $pdfPreview = $('<iframe class="pdf-preview" style="width: 100%; flex-grow: 100;"></iframe>');
|
||||
$pdfPreview.attr("src", utils.getUrlForDownload("api/notes/" + note.noteId + "/open"));
|
||||
|
||||
$rendered.append($pdfPreview);
|
||||
}
|
||||
|
||||
$rendered
|
||||
.append($downloadButton)
|
||||
.append(' ')
|
||||
.append($openButton);
|
||||
$rendered.append(
|
||||
$("<div>")
|
||||
.append($downloadButton)
|
||||
.append(' ')
|
||||
.append($openButton)
|
||||
);
|
||||
}
|
||||
else if (type === 'render') {
|
||||
$rendered = $('<div>');
|
||||
@@ -90,6 +92,10 @@ async function getRenderedContent(note) {
|
||||
function getRenderingType(note) {
|
||||
let type = note.type;
|
||||
|
||||
if (type === 'file' && note.mime === 'application/pdf') {
|
||||
type = 'pdf';
|
||||
}
|
||||
|
||||
if (note.isProtected) {
|
||||
if (protectedSessionHolder.isProtectedSessionAvailable()) {
|
||||
protectedSessionHolder.touchProtectedSession();
|
||||
@@ -104,4 +110,4 @@ function getRenderingType(note) {
|
||||
|
||||
export default {
|
||||
getRenderedContent
|
||||
};
|
||||
};
|
||||
|
||||
@@ -273,6 +273,12 @@ class TreeCache {
|
||||
async getBranchId(parentNoteId, childNoteId) {
|
||||
const child = await this.getNote(childNoteId);
|
||||
|
||||
if (!child) {
|
||||
console.error(`Could not find branchId for parent=${parentNoteId}, child=${childNoteId} since child does not exist`);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return child.parentToBranch[parentNoteId];
|
||||
}
|
||||
|
||||
@@ -282,6 +288,13 @@ class TreeCache {
|
||||
async getNoteComplement(noteId) {
|
||||
if (!this.noteComplementPromises[noteId]) {
|
||||
this.noteComplementPromises[noteId] = server.get('notes/' + noteId).then(row => new NoteComplement(row));
|
||||
|
||||
// we don't want to keep large payloads forever in memory so we clean that up quite quickly
|
||||
// this cache is more meant to share the data between different components within one business transaction (e.g. loading of the note into the tab context and all the components)
|
||||
// this is also a work around for missing invalidation after change
|
||||
this.noteComplementPromises[noteId].then(
|
||||
() => setTimeout(() => this.noteComplementPromises[noteId] = null, 1000)
|
||||
);
|
||||
}
|
||||
|
||||
return await this.noteComplementPromises[noteId];
|
||||
|
||||
@@ -64,8 +64,19 @@ function assertArguments() {
|
||||
}
|
||||
}
|
||||
|
||||
const entityMap = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
'/': '/',
|
||||
'`': '`',
|
||||
'=': '='
|
||||
};
|
||||
|
||||
function escapeHtml(str) {
|
||||
return $('<div/>').text(str).html();
|
||||
return str.replace(/[&<>"'`=\/]/g, s => entityMap[s]);
|
||||
}
|
||||
|
||||
async function stopWatch(what, func) {
|
||||
@@ -317,6 +328,9 @@ function dynamicRequire(moduleName) {
|
||||
}
|
||||
|
||||
function timeLimit(promise, limitMs) {
|
||||
// better stack trace if created outside of promise
|
||||
const error = new Error('Process exceeded time limit ' + limitMs);
|
||||
|
||||
return new Promise((res, rej) => {
|
||||
let resolved = false;
|
||||
|
||||
@@ -328,7 +342,7 @@ function timeLimit(promise, limitMs) {
|
||||
|
||||
setTimeout(() => {
|
||||
if (!resolved) {
|
||||
rej(new Error('Process exceeded time limit ' + limitMs));
|
||||
rej(error);
|
||||
}
|
||||
}, limitMs);
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import CollapsibleWidget from "../collapsible_widget.js";
|
||||
import treeCache from "../../services/tree_cache.js";
|
||||
|
||||
let linkMapContainerIdCtr = 1;
|
||||
|
||||
@@ -89,5 +90,19 @@ export default class LinkMapWidget extends CollapsibleWidget {
|
||||
if (loadResults.getAttributes().find(attr => attr.type === 'relation' && (attr.noteId === this.noteId || attr.value === this.noteId))) {
|
||||
this.noteSwitched();
|
||||
}
|
||||
|
||||
const changedNoteIds = loadResults.getNoteIds();
|
||||
|
||||
if (changedNoteIds.length > 0) {
|
||||
const $linkMapContainer = this.$body.find('.link-map-container');
|
||||
|
||||
for (const noteId of changedNoteIds) {
|
||||
const note = treeCache.notes[noteId];
|
||||
|
||||
if (note) {
|
||||
$linkMapContainer.find(`a[data-note-path="${noteId}"]`).text(note.title);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,6 +79,7 @@ const TPL = `
|
||||
<span class="slider checked"></span>
|
||||
</span>
|
||||
</div>
|
||||
<a data-trigger-command="findInText" class="dropdown-item">Search in note <kbd data-command="findInText"></a>
|
||||
<a data-trigger-command="showNoteRevisions" class="dropdown-item show-note-revisions-button">Revisions</a>
|
||||
<a data-trigger-command="showAttributes" class="dropdown-item show-attributes-button"><kbd data-command="showAttributes"></kbd> Attributes</a>
|
||||
<a data-trigger-command="showLinkMap" class="dropdown-item show-link-map-button"><kbd data-command="showLinkMap"></kbd> Link map</a>
|
||||
@@ -140,4 +141,4 @@ export default class NoteActionsWidget extends TabAwareWidget {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,4 +331,9 @@ export default class NoteDetailWidget extends TabAwareWidget {
|
||||
saveSelection: true
|
||||
});
|
||||
}
|
||||
|
||||
// used by cutToNote in CKEditor build
|
||||
async saveNoteDetailNowCommand() {
|
||||
await this.spacedUpdate.updateNowIfNecessary();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ const TPL = `
|
||||
.tree {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.refresh-search-button {
|
||||
@@ -249,9 +250,39 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
|
||||
this.initialized = this.initFancyTree();
|
||||
|
||||
this.setupNoteTitleTooltip();
|
||||
|
||||
return this.$widget;
|
||||
}
|
||||
|
||||
setupNoteTitleTooltip() {
|
||||
// the following will dynamically set tree item's tooltip if the whole item's text is not currently visible
|
||||
// if the whole text is visible then no tooltip is show since that's unnecessarily distracting
|
||||
// see https://github.com/zadam/trilium/pull/1120 for discussion
|
||||
|
||||
// code inspired by https://gist.github.com/jtsternberg/c272d7de5b967cec2d3d
|
||||
const isEnclosing = ($container, $sub) => {
|
||||
const conOffset = $container.offset();
|
||||
const conDistanceFromTop = conOffset.top + $container.outerHeight(true);
|
||||
const conDistanceFromLeft = conOffset.left + $container.outerWidth(true);
|
||||
|
||||
const subOffset = $sub.offset();
|
||||
const subDistanceFromTop = subOffset.top + $sub.outerHeight(true);
|
||||
const subDistanceFromLeft = subOffset.left + $sub.outerWidth(true);
|
||||
|
||||
return conDistanceFromTop > subDistanceFromTop
|
||||
&& conOffset.top < subOffset.top
|
||||
&& conDistanceFromLeft > subDistanceFromLeft
|
||||
&& conOffset.left < subOffset.left;
|
||||
};
|
||||
|
||||
this.$tree.on("mouseenter", "span.fancytree-title", e => {
|
||||
e.currentTarget.title = isEnclosing(this.$tree, $(e.currentTarget))
|
||||
? ""
|
||||
: e.currentTarget.innerText;
|
||||
});
|
||||
}
|
||||
|
||||
get hideArchivedNotes() {
|
||||
return options.is("hideArchivedNotes_" + this.treeName);
|
||||
}
|
||||
@@ -273,10 +304,13 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
|
||||
this.$tree.fancytree({
|
||||
titlesTabbable: true,
|
||||
autoScroll: true,
|
||||
keyboard: false, // we takover keyboard handling in the hotkeys plugin
|
||||
extensions: utils.isMobile() ? ["dnd5", "clones"] : ["hotkeys", "dnd5", "clones"],
|
||||
source: treeData,
|
||||
scrollOfs: {
|
||||
top: 100,
|
||||
bottom: 100
|
||||
},
|
||||
scrollParent: this.$tree,
|
||||
minExpandLevel: 2, // root can't be collapsed
|
||||
click: (event, data) => {
|
||||
@@ -747,7 +781,7 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
utils.assertArguments(notePath);
|
||||
|
||||
const hoistedNoteId = hoistedNoteService.getHoistedNoteId();
|
||||
/** @var {FancytreeNode} */
|
||||
/** @const {FancytreeNode} */
|
||||
let parentNode = null;
|
||||
|
||||
const runPath = await treeService.getRunPath(notePath, logErrors);
|
||||
@@ -832,13 +866,14 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
const branch = treeCache.getBranch(node.data.branchId);
|
||||
|
||||
const isFolder = this.isFolder(note);
|
||||
const title = (branch.prefix ? (branch.prefix + " - ") : "") + note.title;
|
||||
|
||||
node.data.isProtected = note.isProtected;
|
||||
node.data.noteType = note.type;
|
||||
node.folder = isFolder;
|
||||
node.icon = this.getIcon(note, isFolder);
|
||||
node.extraClasses = this.getExtraClasses(note);
|
||||
node.title = (branch.prefix ? (branch.prefix + " - ") : "") + note.title;
|
||||
node.title = utils.escapeHtml(title);
|
||||
|
||||
if (node.isExpanded() !== branch.isExpanded) {
|
||||
node.setExpanded(branch.isExpanded, {noEvents: true});
|
||||
|
||||
@@ -20,8 +20,8 @@ const TPL = `
|
||||
}
|
||||
</style>
|
||||
|
||||
<button class="hide-left-pane-button btn btn-sm icon-button bx bx-chevrons-left" title="Show sidebar"></button>
|
||||
<button class="show-left-pane-button btn btn-sm icon-button bx bx-chevrons-right" title="Hide sidebar"></button>
|
||||
<button class="hide-left-pane-button btn btn-sm icon-button bx bx-chevrons-left" title="Hide sidebar"></button>
|
||||
<button class="show-left-pane-button btn btn-sm icon-button bx bx-chevrons-right" title="Show sidebar"></button>
|
||||
|
||||
<button class="hide-right-pane-button btn btn-sm icon-button bx bx-chevrons-right" title="Hide sidebar"></button>
|
||||
<button class="show-right-pane-button btn btn-sm icon-button bx bx-chevrons-left" title="Show sidebar"></button>
|
||||
|
||||
@@ -36,10 +36,10 @@ export default class AbstractTextTypeWidget extends TypeWidget {
|
||||
.append($link)
|
||||
);
|
||||
|
||||
const {renderedContent} = await noteContentRenderer.getRenderedContent(note);
|
||||
const {renderedContent, type} = await noteContentRenderer.getRenderedContent(note);
|
||||
|
||||
$el.append(
|
||||
$('<div class="include-note-content">')
|
||||
$(`<div class="include-note-content type-${type}">`)
|
||||
.append(renderedContent)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -121,10 +121,10 @@ export default class FileTypeWidget extends TypeWidget {
|
||||
this.$pdfPreview.attr('src', '').empty().hide();
|
||||
|
||||
if (noteComplement.content) {
|
||||
this.$previewContent.show();
|
||||
this.$previewContent.show().scrollTop(0);
|
||||
this.$previewContent.text(noteComplement.content);
|
||||
}
|
||||
else if (note.mime === 'application/pdf' && utils.isElectron()) {
|
||||
else if (note.mime === 'application/pdf') {
|
||||
this.$pdfPreview.show();
|
||||
this.$pdfPreview.attr("src", utils.getUrlForDownload("api/notes/" + this.noteId + "/open"));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "Trilium",
|
||||
"short_name": "Trilium",
|
||||
"theme_color": "DarkGray",
|
||||
"background_color": "DarkGray",
|
||||
"display": "standalone",
|
||||
"scope": "/",
|
||||
"start_url": "/",
|
||||
"icons": [{
|
||||
"src": "images/app-icons/ios/apple-touch-icon.png",
|
||||
"sizes": "180x180",
|
||||
"type": "image/png"
|
||||
}]
|
||||
}
|
||||
@@ -693,11 +693,23 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.include-note.box-size-small .include-note-content.type-pdf {
|
||||
height: 10em; /* PDF is rendered in iframe and must be sized absolutely */
|
||||
}
|
||||
|
||||
.include-note.box-size-medium .include-note-content {
|
||||
max-height: 20em;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.include-note.box-size-medium .include-note-content.type-pdf {
|
||||
height: 20em; /* PDF is rendered in iframe and must be sized absolutely */
|
||||
}
|
||||
|
||||
.include-note.box-size-full .include-note-content.type-pdf {
|
||||
height: 50em; /* PDF is rendered in iframe and it's not possible to put full height so at least a large height */
|
||||
}
|
||||
|
||||
.alert-warning {
|
||||
color: var(--main-text-color) !important;
|
||||
background-color: var(--accented-background-color) !important;
|
||||
|
||||
@@ -65,7 +65,11 @@ async function addClipping(req) {
|
||||
}
|
||||
|
||||
async function createNote(req) {
|
||||
const {title, content, pageUrl, images, clipType} = req.body;
|
||||
let {title, content, pageUrl, images, clipType} = req.body;
|
||||
|
||||
if (!title || !title.trim()) {
|
||||
title = "Clipped note from " + pageUrl;
|
||||
}
|
||||
|
||||
log.info(`Creating clipped note from ${pageUrl}`);
|
||||
|
||||
@@ -155,4 +159,4 @@ module.exports = {
|
||||
addClipping,
|
||||
openNote,
|
||||
handshake
|
||||
};
|
||||
};
|
||||
|
||||
@@ -49,7 +49,7 @@ async function loginSync(req) {
|
||||
|
||||
return {
|
||||
sourceId: sourceIdService.getCurrentSourceId(),
|
||||
maxSyncId: await sql.getValue("SELECT MAX(id) FROM sync WHERE isSynced = 1")
|
||||
maxSyncId: await sql.getValue("SELECT COALESCE(MAX(id), 0) FROM sync WHERE isSynced = 1")
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+10
-3
@@ -17,8 +17,9 @@ async function getNote(req) {
|
||||
if (note.isStringNote()) {
|
||||
note.content = await note.getContent();
|
||||
|
||||
if (note.type === 'file') {
|
||||
note.content = note.content.substr(0, 10000);
|
||||
if (note.type === 'file' && note.content.length > 10000) {
|
||||
note.content = note.content.substr(0, 10000)
|
||||
+ `\r\n\r\n... and ${note.content.length - 10000} more characters.`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,10 +165,16 @@ async function changeTitle(req) {
|
||||
return [400, `Note ${noteId} is not available for change`];
|
||||
}
|
||||
|
||||
const noteTitleChanged = note.title !== title;
|
||||
|
||||
note.title = title;
|
||||
|
||||
await note.save();
|
||||
|
||||
if (noteTitleChanged) {
|
||||
await noteService.triggerNoteTitleChanged(note);
|
||||
}
|
||||
|
||||
return note;
|
||||
}
|
||||
|
||||
@@ -189,4 +196,4 @@ module.exports = {
|
||||
getRelationMap,
|
||||
changeTitle,
|
||||
duplicateNote
|
||||
};
|
||||
};
|
||||
|
||||
@@ -50,7 +50,7 @@ async function getStats() {
|
||||
async function checkSync() {
|
||||
return {
|
||||
entityHashes: await contentHashService.getEntityHashes(),
|
||||
maxSyncId: await sql.getValue('SELECT MAX(id) FROM sync WHERE isSynced = 1')
|
||||
maxSyncId: await sql.getValue('SELECT COALESCE(MAX(id), 0) FROM sync WHERE isSynced = 1')
|
||||
};
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ async function getChanged(req) {
|
||||
|
||||
const ret = {
|
||||
syncs: await syncService.getSyncRecords(syncs),
|
||||
maxSyncId: await sql.getValue('SELECT MAX(id) FROM sync WHERE isSynced = 1')
|
||||
maxSyncId: await sql.getValue('SELECT COALESCE(MAX(id), 0) FROM sync WHERE isSynced = 1')
|
||||
};
|
||||
|
||||
if (ret.syncs.length > 0) {
|
||||
|
||||
+62
-53
@@ -2,67 +2,76 @@ const repository = require('../services/repository');
|
||||
const log = require('../services/log');
|
||||
const fileUploadService = require('./api/files.js');
|
||||
const scriptService = require('../services/script');
|
||||
const cls = require('../services/cls');
|
||||
|
||||
async function handleRequest(req, res) {
|
||||
// express puts content after first slash into 0 index element
|
||||
|
||||
const path = req.params.path + req.params[0];
|
||||
|
||||
const attrs = await repository.getEntities("SELECT * FROM attributes WHERE isDeleted = 0 AND type = 'label' AND name IN ('customRequestHandler', 'customResourceProvider')");
|
||||
|
||||
for (const attr of attrs) {
|
||||
const regex = new RegExp(attr.value);
|
||||
let match;
|
||||
|
||||
try {
|
||||
match = path.match(regex);
|
||||
}
|
||||
catch (e) {
|
||||
log.error(`Testing path for label ${attr.attributeId}, regex=${attr.value} failed with error ` + e.stack);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!match) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (attr.name === 'customRequestHandler') {
|
||||
const note = await attr.getNote();
|
||||
|
||||
log.info(`Handling custom request "${path}" with note ${note.noteId}`);
|
||||
|
||||
try {
|
||||
await scriptService.executeNote(note, {
|
||||
pathParams: match.slice(1),
|
||||
req,
|
||||
res
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
log.error(`Custom handler ${note.noteId} failed with ${e.message}`);
|
||||
|
||||
res.status(500).send(e.message);
|
||||
}
|
||||
}
|
||||
else if (attr.name === 'customResourceProvider') {
|
||||
await fileUploadService.downloadNoteFile(attr.noteId, res);
|
||||
}
|
||||
else {
|
||||
throw new Error("Unrecognized attribute name " + attr.name);
|
||||
}
|
||||
|
||||
return; // only first handler is executed
|
||||
}
|
||||
|
||||
const message = `No handler matched for custom ${path} request.`;
|
||||
|
||||
log.info(message);
|
||||
res.status(404).send(message);
|
||||
}
|
||||
|
||||
function register(router) {
|
||||
// explicitly no CSRF middleware since it's meant to allow integration from external services
|
||||
|
||||
router.all('/custom/:path*', async (req, res, next) => {
|
||||
// express puts content after first slash into 0 index element
|
||||
const path = req.params.path + req.params[0];
|
||||
cls.namespace.bindEmitter(req);
|
||||
cls.namespace.bindEmitter(res);
|
||||
|
||||
const attrs = await repository.getEntities("SELECT * FROM attributes WHERE isDeleted = 0 AND type = 'label' AND name IN ('customRequestHandler', 'customResourceProvider')");
|
||||
|
||||
for (const attr of attrs) {
|
||||
const regex = new RegExp(attr.value);
|
||||
let match;
|
||||
|
||||
try {
|
||||
match = path.match(regex);
|
||||
}
|
||||
catch (e) {
|
||||
log.error(`Testing path for label ${attr.attributeId}, regex=${attr.value} failed with error ` + e.stack);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!match) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (attr.name === 'customRequestHandler') {
|
||||
const note = await attr.getNote();
|
||||
|
||||
log.info(`Handling custom request "${path}" with note ${note.noteId}`);
|
||||
|
||||
try {
|
||||
await scriptService.executeNote(note, {
|
||||
pathParams: match.slice(1),
|
||||
req,
|
||||
res
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
log.error(`Custom handler ${note.noteId} failed with ${e.message}`);
|
||||
|
||||
res.status(500).send(e.message);
|
||||
}
|
||||
}
|
||||
else if (attr.name === 'customResourceProvider') {
|
||||
await fileUploadService.downloadNoteFile(attr.noteId, res);
|
||||
}
|
||||
else {
|
||||
throw new Error("Unrecognized attribute name " + attr.name);
|
||||
}
|
||||
|
||||
return; // only first handler is executed
|
||||
}
|
||||
|
||||
const message = `No handler matched for custom ${path} request.`;
|
||||
|
||||
log.info(message);
|
||||
res.status(404).send(message);
|
||||
cls.init(() => handleRequest(req, res));
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
register
|
||||
};
|
||||
};
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ async function index(req, res) {
|
||||
treeFontSize: parseInt(options.treeFontSize),
|
||||
detailFontSize: parseInt(options.detailFontSize),
|
||||
sourceId: await sourceIdService.generateSourceId(),
|
||||
maxSyncIdAtLoad: await sql.getValue("SELECT MAX(id) FROM sync"),
|
||||
maxSyncIdAtLoad: await sql.getValue("SELECT COALESCE(MAX(id), 0) FROM sync"),
|
||||
instanceName: config.General ? config.General.instanceName : null,
|
||||
appCssNoteIds: await getAppCssNoteIds(),
|
||||
isDev: env.isDev(),
|
||||
|
||||
@@ -4,7 +4,7 @@ const build = require('./build');
|
||||
const packageJson = require('../../package');
|
||||
const {TRILIUM_DATA_DIR} = require('./data_dir');
|
||||
|
||||
const APP_DB_VERSION = 158;
|
||||
const APP_DB_VERSION = 159;
|
||||
const SYNC_VERSION = 14;
|
||||
const CLIPPER_PROTOCOL_VERSION = "1.0";
|
||||
|
||||
@@ -16,4 +16,4 @@ module.exports = {
|
||||
buildRevision: build.buildRevision,
|
||||
dataDirectory: TRILIUM_DATA_DIR,
|
||||
clipperProtocolVersion: CLIPPER_PROTOCOL_VERSION
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1 +1 @@
|
||||
module.exports = { buildDate:"2020-06-15T23:26:12+02:00", buildRevision: "9791dab97d9e86c4b02ca593198caffd1b72bbfb" };
|
||||
module.exports = { buildDate:"2020-08-27T23:58:58+02:00", buildRevision: "dc288fb18c7622f6e08c574888dc0e8c90e544c2" };
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
const html = require('html');
|
||||
const repository = require('../repository');
|
||||
const dateUtils = require('../date_utils');
|
||||
const zip = require('tar-stream');
|
||||
const path = require('path');
|
||||
const mimeTypes = require('mime-types');
|
||||
const mdService = require('./md');
|
||||
@@ -444,4 +443,4 @@ ${content}
|
||||
|
||||
module.exports = {
|
||||
exportToZip
|
||||
};
|
||||
};
|
||||
|
||||
@@ -68,6 +68,9 @@ eventService.subscribe(eventService.ENTITY_CREATED, async ({ entityName, entity
|
||||
|
||||
await note.setContent(await targetNote.getContent());
|
||||
}
|
||||
else if (entity.type === 'label' && entity.name === 'sorted') {
|
||||
await treeService.sortNotesAlphabetically(entity.noteId);
|
||||
}
|
||||
}
|
||||
else if (entityName === 'notes') {
|
||||
await runAttachedRelations(entity, 'runOnNoteCreation', entity);
|
||||
@@ -135,4 +138,4 @@ eventService.subscribe(eventService.ENTITY_DELETED, async ({ entityName, entity
|
||||
targetNote.invalidateAttributeCache();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+20
-2
@@ -98,7 +98,17 @@ async function saveImage(parentNoteId, uploadBuffer, originalName, shrinkImageSw
|
||||
|
||||
async function shrinkImage(buffer, originalName) {
|
||||
// we do resizing with max (100) quality which will be trimmed during optimization step next
|
||||
const resizedImage = await resize(buffer, 100);
|
||||
let resizedImage;
|
||||
|
||||
try {
|
||||
resizedImage = await resize(buffer, 100);
|
||||
}
|
||||
catch (e) {
|
||||
log.error("Failed to resize image '" + originalName + "'\nStack: " + e.stack);
|
||||
|
||||
resizedImage = buffer;
|
||||
}
|
||||
|
||||
let finalImageBuffer;
|
||||
|
||||
const jpegQuality = await optionService.getOptionInt('imageJpegQuality');
|
||||
@@ -107,7 +117,15 @@ async function shrinkImage(buffer, originalName) {
|
||||
finalImageBuffer = await optimize(resizedImage, jpegQuality);
|
||||
} catch (e) {
|
||||
log.error("Failed to optimize image '" + originalName + "'\nStack: " + e.stack);
|
||||
finalImageBuffer = await resize(buffer, jpegQuality);
|
||||
|
||||
try {
|
||||
finalImageBuffer = await resize(buffer, jpegQuality);
|
||||
}
|
||||
catch (e) {
|
||||
log.error("Failed to resize image '" + originalName + "'\nStack: " + e.stack);
|
||||
|
||||
finalImageBuffer = buffer;
|
||||
}
|
||||
}
|
||||
|
||||
// if resizing & shrinking did not help with size then save the original
|
||||
|
||||
@@ -24,7 +24,7 @@ async function importSingleFile(taskContext, file, parentNote) {
|
||||
return await importCodeNote(taskContext, file, parentNote);
|
||||
}
|
||||
|
||||
if (["image/jpeg", "image/gif", "image/png", "image/webp"].includes(mime)) {
|
||||
if (mime.startsWith("image/")) {
|
||||
return await importImage(file, parentNote, taskContext);
|
||||
}
|
||||
|
||||
@@ -166,4 +166,4 @@ function getFileNameWithoutExtension(filePath) {
|
||||
|
||||
module.exports = {
|
||||
importSingleFile
|
||||
};
|
||||
};
|
||||
|
||||
@@ -327,7 +327,7 @@ const DEFAULT_KEYBOARD_ACTIONS = [
|
||||
{
|
||||
actionName: "printActiveNote",
|
||||
defaultShortcuts: [],
|
||||
scope: "note-detail"
|
||||
scope: "window"
|
||||
},
|
||||
{
|
||||
actionName: "runActiveNote",
|
||||
|
||||
+20
-11
@@ -279,20 +279,30 @@ const downloadImagePromises = {};
|
||||
function replaceUrl(content, url, imageNote) {
|
||||
const quotedUrl = utils.quoteRegex(url);
|
||||
|
||||
return content.replace(new RegExp(`\\s+src=[\"']${quotedUrl}[\"']`, "g"), ` src="api/images/${imageNote.noteId}/${imageNote.title}"`);
|
||||
return content.replace(new RegExp(`\\s+src=[\"']${quotedUrl}[\"']`, "ig"), ` src="api/images/${imageNote.noteId}/${imageNote.title}"`);
|
||||
}
|
||||
|
||||
async function downloadImages(noteId, content) {
|
||||
const re = /<img[^>]*?\ssrc=['"]([^'">]+)['"]/ig;
|
||||
let match;
|
||||
const imageRe = /<img[^>]*?\ssrc=['"]([^'">]+)['"]/ig;
|
||||
let imageMatch;
|
||||
|
||||
const origContent = content;
|
||||
while (imageMatch = imageRe.exec(content)) {
|
||||
const url = imageMatch[1];
|
||||
const inlineImageMatch = /^data:image\/[a-z]+;base64,/.exec(url);
|
||||
|
||||
while (match = re.exec(origContent)) {
|
||||
const url = match[1];
|
||||
if (inlineImageMatch) {
|
||||
const imageBase64 = url.substr(inlineImageMatch[0].length);
|
||||
const imageBuffer = Buffer.from(imageBase64, 'base64');
|
||||
|
||||
if (!url.includes('api/images/')
|
||||
// this is and exception for the web clipper's "imageId"
|
||||
const imageService = require('../services/image');
|
||||
const {note} = await imageService.saveImage(noteId, imageBuffer, "inline image", true);
|
||||
|
||||
content = content.substr(0, imageMatch.index)
|
||||
+ `<img src="api/images/${note.noteId}/${note.title}"`
|
||||
+ content.substr(imageMatch.index + imageMatch[0].length);
|
||||
}
|
||||
else if (!url.includes('api/images/')
|
||||
// this is an exception for the web clipper's "imageId"
|
||||
&& (url.length !== 20 || url.toLowerCase().startsWith('http'))) {
|
||||
|
||||
if (url in imageUrlToNoteIdMapping) {
|
||||
@@ -303,7 +313,6 @@ async function downloadImages(noteId, content) {
|
||||
}
|
||||
else {
|
||||
content = replaceUrl(content, url, imageNote);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -315,7 +324,6 @@ async function downloadImages(noteId, content) {
|
||||
imageUrlToNoteIdMapping[url] = existingImage.noteId;
|
||||
|
||||
content = replaceUrl(content, url, existingImage);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -778,5 +786,6 @@ module.exports = {
|
||||
protectNoteRecursively,
|
||||
scanForLinks,
|
||||
duplicateNote,
|
||||
getUndeletedParentBranches
|
||||
getUndeletedParentBranches,
|
||||
triggerNoteTitleChanged
|
||||
};
|
||||
|
||||
@@ -51,7 +51,7 @@ async function initNotSyncedOptions(initialized, startNotePath = 'root', opts =
|
||||
await optionService.createOption('theme', opts.theme || 'white', false);
|
||||
|
||||
await optionService.createOption('syncServerHost', opts.syncServerHost || '', false);
|
||||
await optionService.createOption('syncServerTimeout', '5000', false);
|
||||
await optionService.createOption('syncServerTimeout', '60000', false);
|
||||
await optionService.createOption('syncProxy', opts.syncProxy || '', false);
|
||||
}
|
||||
|
||||
@@ -116,4 +116,4 @@ module.exports = {
|
||||
initSyncedOptions,
|
||||
initNotSyncedOptions,
|
||||
initStartupOptions
|
||||
};
|
||||
};
|
||||
|
||||
@@ -84,10 +84,11 @@ function exec(opts) {
|
||||
}
|
||||
|
||||
async function getImage(imageUrl) {
|
||||
const proxyConf = await syncOptions.getSyncProxy();
|
||||
const opts = {
|
||||
method: 'GET',
|
||||
url: imageUrl,
|
||||
proxy: await syncOptions.getSyncProxy()
|
||||
proxy: proxyConf !== "noproxy" ? proxyConf : null
|
||||
};
|
||||
|
||||
const client = getClient(opts);
|
||||
|
||||
@@ -123,7 +123,7 @@ async function doLogin() {
|
||||
}
|
||||
|
||||
async function pullSync(syncContext) {
|
||||
let appliedPulls = 0;
|
||||
let atLeastOnePullApplied = false;
|
||||
|
||||
while (true) {
|
||||
const lastSyncedPull = await getLastSyncedPull();
|
||||
@@ -132,6 +132,9 @@ async function pullSync(syncContext) {
|
||||
const startDate = Date.now();
|
||||
|
||||
const resp = await syncRequest(syncContext, 'GET', changesUri);
|
||||
|
||||
const pulledDate = Date.now();
|
||||
|
||||
stats.outstandingPulls = resp.maxSyncId - lastSyncedPull;
|
||||
|
||||
if (stats.outstandingPulls < 0) {
|
||||
@@ -147,10 +150,10 @@ async function pullSync(syncContext) {
|
||||
await sql.transactional(async () => {
|
||||
for (const {sync, entity} of rows) {
|
||||
if (!sourceIdService.isLocalSourceId(sync.sourceId)) {
|
||||
if (appliedPulls === 0 && sync.entity !== 'recent_notes') { // send only for first
|
||||
if (!atLeastOnePullApplied && sync.entity !== 'recent_notes') { // send only for first
|
||||
ws.syncPullInProgress();
|
||||
|
||||
appliedPulls++;
|
||||
atLeastOnePullApplied = true;
|
||||
}
|
||||
|
||||
await syncUpdateService.updateEntity(sync, entity, syncContext.sourceId);
|
||||
@@ -162,10 +165,10 @@ async function pullSync(syncContext) {
|
||||
await setLastSyncedPull(rows[rows.length - 1].sync.id);
|
||||
});
|
||||
|
||||
log.info(`Pulled and updated ${rows.length} changes from ${changesUri} in ${Date.now() - startDate}ms`);
|
||||
log.info(`Pulled ${rows.length} changes starting at syncId=${lastSyncedPull} in ${pulledDate - startDate}ms and applied them in ${Date.now() - pulledDate}ms, ${stats.outstandingPulls} outstanding pulls`);
|
||||
}
|
||||
|
||||
if (appliedPulls > 0) {
|
||||
if (atLeastOnePullApplied) {
|
||||
ws.syncPullFinished();
|
||||
}
|
||||
|
||||
@@ -365,7 +368,7 @@ async function updatePushStats() {
|
||||
}
|
||||
|
||||
async function getMaxSyncId() {
|
||||
return await sql.getValue('SELECT MAX(id) FROM sync');
|
||||
return await sql.getValue('SELECT COALESCE(MAX(id), 0) FROM sync');
|
||||
}
|
||||
|
||||
sqlInit.dbReady.then(async () => {
|
||||
|
||||
@@ -82,7 +82,8 @@ async function fillSyncRows(entityName, entityPrimaryKey, condition = '') {
|
||||
entityName: entityName,
|
||||
entityId: entityId,
|
||||
sourceId: "SYNC_FILL",
|
||||
utcSyncDate: dateUtils.utcNowDateTime()
|
||||
utcSyncDate: dateUtils.utcNowDateTime(),
|
||||
isSynced: true
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -127,4 +128,4 @@ module.exports = {
|
||||
fillAllSyncRows,
|
||||
addEntitySyncsForSector,
|
||||
getMaxSyncId: () => maxSyncId
|
||||
};
|
||||
};
|
||||
|
||||
@@ -159,7 +159,11 @@ function getContentDisposition(filename) {
|
||||
return `file; filename="${sanitizedFilename}"; filename*=UTF-8''${sanitizedFilename}`;
|
||||
}
|
||||
|
||||
const STRING_MIME_TYPES = ["application/x-javascript", "image/svg+xml"];
|
||||
const STRING_MIME_TYPES = [
|
||||
"application/javascript",
|
||||
"application/x-javascript",
|
||||
"image/svg+xml"
|
||||
];
|
||||
|
||||
function isStringNote(type, mime) {
|
||||
return ["text", "code", "relation-map", "search"].includes(type)
|
||||
@@ -218,6 +222,9 @@ function formatDownloadTitle(filename, type, mime) {
|
||||
}
|
||||
|
||||
function timeLimit(promise, limitMs) {
|
||||
// better stack trace if created outside of promise
|
||||
const error = new Error('Process exceeded time limit ' + limitMs);
|
||||
|
||||
return new Promise((res, rej) => {
|
||||
let resolved = false;
|
||||
|
||||
@@ -229,7 +236,7 @@ function timeLimit(promise, limitMs) {
|
||||
|
||||
setTimeout(() => {
|
||||
if (!resolved) {
|
||||
rej(new Error('Process exceeded time limit ' + limitMs));
|
||||
rej(error);
|
||||
}
|
||||
}, limitMs);
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<title>Trilium Notes</title>
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="images/app-icons/ios/apple-touch-icon.png">
|
||||
<link rel="manifest" href="manifest.webmanifest">
|
||||
|
||||
<style>
|
||||
.lds-roller {
|
||||
@@ -140,4 +140,4 @@
|
||||
<link rel="stylesheet" type="text/css" href="libraries/boxicons/css/boxicons.min.css">
|
||||
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
+3
-2
@@ -193,10 +193,11 @@
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
const glob = {
|
||||
window.glob = {
|
||||
sourceId: ''
|
||||
};
|
||||
const syncInProgress = <%= syncInProgress ? 'true' : 'false' %>;
|
||||
|
||||
window.syncInProgress = <%= syncInProgress ? 'true' : 'false' %>;
|
||||
</script>
|
||||
|
||||
<!-- Required for correct loading of scripts in Electron -->
|
||||
|
||||
Reference in New Issue
Block a user