mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
Merge branch 'beta'
# Conflicts: # package-lock.json
This commit is contained in:
commit
99d0f22403
0
bin/create-anonymization-script.js
Normal file → Executable file
0
bin/create-anonymization-script.js
Normal file → Executable file
@ -1,165 +1,47 @@
|
||||
|
||||
UPDATE etapi_tokens SET tokenHash = 'API token hash value';
|
||||
UPDATE notes SET title = 'title' WHERE noteId != 'root' AND noteId NOT LIKE '\_%' ESCAPE '\';
|
||||
UPDATE notes SET title = 'title' WHERE title NOT IN ('root', '_hidden', '_share');
|
||||
UPDATE blobs SET content = 'text' WHERE content IS NOT NULL;
|
||||
UPDATE revisions SET title = 'title';
|
||||
|
||||
UPDATE attributes SET name = 'name', value = 'value'
|
||||
WHERE type = 'label'
|
||||
AND name NOT IN ('inbox',
|
||||
'disableVersioning',
|
||||
'calendarRoot',
|
||||
'archived',
|
||||
'excludeFromExport',
|
||||
'disableInclusion',
|
||||
'appCss',
|
||||
'appTheme',
|
||||
'hidePromotedAttributes',
|
||||
'readOnly',
|
||||
'autoReadOnlyDisabled',
|
||||
'cssClass',
|
||||
'iconClass',
|
||||
'keyboardShortcut',
|
||||
'run',
|
||||
'runOnInstance',
|
||||
'runAtHour',
|
||||
'customRequestHandler',
|
||||
'customResourceProvider',
|
||||
'widget',
|
||||
'noteInfoWidgetDisabled',
|
||||
'linkMapWidgetDisabled',
|
||||
'revisionsWidgetDisabled',
|
||||
'whatLinksHereWidgetDisabled',
|
||||
'similarNotesWidgetDisabled',
|
||||
'workspace',
|
||||
'workspaceIconClass',
|
||||
'workspaceTabBackgroundColor',
|
||||
'searchHome',
|
||||
'workspaceInbox',
|
||||
'workspaceSearchHome',
|
||||
'sqlConsoleHome',
|
||||
'datePattern',
|
||||
'pageSize',
|
||||
'viewType',
|
||||
'mapRootNoteId',
|
||||
'bookmarkFolder',
|
||||
'sorted',
|
||||
'top',
|
||||
'fullContentWidth',
|
||||
'shareHiddenFromTree',
|
||||
'shareAlias',
|
||||
'shareOmitDefaultCss',
|
||||
'shareRoot',
|
||||
'internalLink',
|
||||
'imageLink',
|
||||
'relationMapLink',
|
||||
'includeMapLink',
|
||||
'runOnNoteCreation',
|
||||
'runOnNoteTitleChange',
|
||||
'runOnNoteContentChange',
|
||||
'runOnNoteChange',
|
||||
'runOnChildNoteCreation',
|
||||
'runOnAttributeCreation',
|
||||
'runOnAttributeChange',
|
||||
'template',
|
||||
'inherit',
|
||||
'widget',
|
||||
'renderNote',
|
||||
'shareCss',
|
||||
'shareJs',
|
||||
'shareFavicon',
|
||||
'executeButton',
|
||||
'keepCurrentHoisting',
|
||||
'color',
|
||||
'toc',
|
||||
'excludeFromNoteMap',
|
||||
'docName',
|
||||
'launcherType',
|
||||
'builtinWidget',
|
||||
'baseSize',
|
||||
'growthFactor'
|
||||
);
|
||||
|
||||
UPDATE attributes SET name = 'name'
|
||||
AND name NOT IN ('inbox',
|
||||
'disableVersioning',
|
||||
'calendarRoot',
|
||||
'archived',
|
||||
'excludeFromExport',
|
||||
'disableInclusion',
|
||||
'appCss',
|
||||
'appTheme',
|
||||
'hidePromotedAttributes',
|
||||
'readOnly',
|
||||
'autoReadOnlyDisabled',
|
||||
'cssClass',
|
||||
'iconClass',
|
||||
'keyboardShortcut',
|
||||
'run',
|
||||
'runOnInstance',
|
||||
'runAtHour',
|
||||
'customRequestHandler',
|
||||
'customResourceProvider',
|
||||
'widget',
|
||||
'noteInfoWidgetDisabled',
|
||||
'linkMapWidgetDisabled',
|
||||
'revisionsWidgetDisabled',
|
||||
'whatLinksHereWidgetDisabled',
|
||||
'similarNotesWidgetDisabled',
|
||||
'workspace',
|
||||
'workspaceIconClass',
|
||||
'workspaceTabBackgroundColor',
|
||||
'searchHome',
|
||||
'workspaceInbox',
|
||||
'workspaceSearchHome',
|
||||
'sqlConsoleHome',
|
||||
'datePattern',
|
||||
'pageSize',
|
||||
'viewType',
|
||||
'mapRootNoteId',
|
||||
'bookmarkFolder',
|
||||
'sorted',
|
||||
'top',
|
||||
'fullContentWidth',
|
||||
'shareHiddenFromTree',
|
||||
'shareAlias',
|
||||
'shareOmitDefaultCss',
|
||||
'shareRoot',
|
||||
'internalLink',
|
||||
'imageLink',
|
||||
'relationMapLink',
|
||||
'includeMapLink',
|
||||
'runOnNoteCreation',
|
||||
'runOnNoteTitleChange',
|
||||
'runOnNoteContentChange',
|
||||
'runOnNoteChange',
|
||||
'runOnChildNoteCreation',
|
||||
'runOnAttributeCreation',
|
||||
'runOnAttributeChange',
|
||||
'template',
|
||||
'inherit',
|
||||
'widget',
|
||||
'renderNote',
|
||||
'shareCss',
|
||||
'shareJs',
|
||||
'shareFavicon',
|
||||
'executeButton',
|
||||
'keepCurrentHoisting',
|
||||
'color',
|
||||
'toc',
|
||||
'excludeFromNoteMap',
|
||||
'docName',
|
||||
'launcherType',
|
||||
'builtinWidget',
|
||||
'baseSize',
|
||||
'growthFactor'
|
||||
);
|
||||
|
||||
UPDATE attributes SET name = 'name', value = 'value' WHERE type = 'label'
|
||||
AND name NOT IN
|
||||
('inbox', 'disableVersioning', 'calendarRoot', 'archived', 'excludeFromExport', 'disableInclusion', 'appCss',
|
||||
'appTheme', 'hidePromotedAttributes', 'readOnly', 'autoReadOnlyDisabled', 'cssClass', 'iconClass',
|
||||
'keyboardShortcut', 'run', 'runOnInstance', 'runAtHour', 'customRequestHandler', 'customResourceProvider',
|
||||
'widget', 'noteInfoWidgetDisabled', 'linkMapWidgetDisabled', 'revisionsWidgetDisabled',
|
||||
'whatLinksHereWidgetDisabled', 'similarNotesWidgetDisabled', 'workspace', 'workspaceIconClass',
|
||||
'workspaceTabBackgroundColor', 'workspaceCalendarRoot', 'workspaceTemplate', 'searchHome', 'workspaceInbox',
|
||||
'workspaceSearchHome', 'sqlConsoleHome', 'datePattern', 'pageSize', 'viewType', 'mapRootNoteId',
|
||||
'bookmarkFolder', 'sorted', 'sortDirection', 'sortFoldersFirst', 'sortNatural', 'sortLocale', 'top',
|
||||
'fullContentWidth', 'shareHiddenFromTree', 'shareOmitDefaultCss', 'shareRoot', 'shareDescription',
|
||||
'shareRaw', 'shareDisallowRobotIndexing', 'shareIndex', 'displayRelations', 'hideRelations', 'titleTemplate',
|
||||
'template', 'toc', 'color', 'keepCurrentHoisting', 'executeButton', 'executeDescription', 'newNotesOnTop',
|
||||
'clipperInbox', 'internalLink', 'imageLink', 'relationMapLink', 'includeMapLink', 'runOnNoteCreation',
|
||||
'runOnNoteTitleChange', 'runOnNoteChange', 'runOnNoteContentChange', 'runOnNoteDeletion', 'runOnBranchCreation',
|
||||
'runOnBranchDeletion', 'runOnChildNoteCreation', 'runOnAttributeCreation', 'runOnAttributeChange', 'template',
|
||||
'inherit', 'widget', 'renderNote', 'shareCss', 'shareJs', 'shareFavicon');
|
||||
UPDATE attributes SET name = 'name' WHERE type = 'relation'
|
||||
AND name NOT IN
|
||||
('inbox', 'disableVersioning', 'calendarRoot', 'archived', 'excludeFromExport', 'disableInclusion', 'appCss',
|
||||
'appTheme', 'hidePromotedAttributes', 'readOnly', 'autoReadOnlyDisabled', 'cssClass', 'iconClass',
|
||||
'keyboardShortcut', 'run', 'runOnInstance', 'runAtHour', 'customRequestHandler', 'customResourceProvider',
|
||||
'widget', 'noteInfoWidgetDisabled', 'linkMapWidgetDisabled', 'revisionsWidgetDisabled',
|
||||
'whatLinksHereWidgetDisabled', 'similarNotesWidgetDisabled', 'workspace', 'workspaceIconClass',
|
||||
'workspaceTabBackgroundColor', 'workspaceCalendarRoot', 'workspaceTemplate', 'searchHome', 'workspaceInbox',
|
||||
'workspaceSearchHome', 'sqlConsoleHome', 'datePattern', 'pageSize', 'viewType', 'mapRootNoteId',
|
||||
'bookmarkFolder', 'sorted', 'sortDirection', 'sortFoldersFirst', 'sortNatural', 'sortLocale', 'top',
|
||||
'fullContentWidth', 'shareHiddenFromTree', 'shareOmitDefaultCss', 'shareRoot', 'shareDescription',
|
||||
'shareRaw', 'shareDisallowRobotIndexing', 'shareIndex', 'displayRelations', 'hideRelations', 'titleTemplate',
|
||||
'template', 'toc', 'color', 'keepCurrentHoisting', 'executeButton', 'executeDescription', 'newNotesOnTop',
|
||||
'clipperInbox', 'internalLink', 'imageLink', 'relationMapLink', 'includeMapLink', 'runOnNoteCreation',
|
||||
'runOnNoteTitleChange', 'runOnNoteChange', 'runOnNoteContentChange', 'runOnNoteDeletion', 'runOnBranchCreation',
|
||||
'runOnBranchDeletion', 'runOnChildNoteCreation', 'runOnAttributeCreation', 'runOnAttributeChange', 'template',
|
||||
'inherit', 'widget', 'renderNote', 'shareCss', 'shareJs', 'shareFavicon');
|
||||
UPDATE branches SET prefix = 'prefix' WHERE prefix IS NOT NULL AND prefix != 'recovered';
|
||||
UPDATE options SET value = 'anonymized' WHERE name IN
|
||||
('documentId', 'documentSecret', 'encryptedDataKey',
|
||||
'passwordVerificationHash', 'passwordVerificationSalt',
|
||||
'passwordDerivedKeySalt', 'username', 'syncServerHost', 'syncProxy')
|
||||
AND value != '';
|
||||
('documentId', 'documentSecret', 'encryptedDataKey',
|
||||
'passwordVerificationHash', 'passwordVerificationSalt',
|
||||
'passwordDerivedKeySalt', 'username', 'syncServerHost', 'syncProxy')
|
||||
AND value != '';
|
||||
|
||||
VACUUM;
|
||||
|
1
db/migrations/0226__rename_noteSize_label.sql
Normal file
1
db/migrations/0226__rename_noteSize_label.sql
Normal file
@ -0,0 +1 @@
|
||||
UPDATE attributes SET value = 'contentAndAttachmentsAndRevisionsSize' WHERE name = 'orderBy' AND value = 'noteSize';
|
@ -1,3 +1,5 @@
|
||||
# Running `docker-compose up` will create/use the "trilium-data" directory in the user home
|
||||
# Run `TRILIUM_DATA_DIR=/path/of/your/choice docker-compose up` to set a different directory
|
||||
version: '2.1'
|
||||
services:
|
||||
trilium:
|
||||
@ -8,7 +10,7 @@ services:
|
||||
ports:
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- trilium:/home/node/trilium-data
|
||||
- ${TRILIUM_DATA_DIR:-~/trilium-data}:/home/node/trilium-data
|
||||
|
||||
volumes:
|
||||
trilium:
|
||||
|
2
libraries/codemirror/addon/lint/eslint.js
vendored
2
libraries/codemirror/addon/lint/eslint.js
vendored
@ -46,7 +46,7 @@
|
||||
const errors = new eslint().verify(text, {
|
||||
root: true,
|
||||
parserOptions: {
|
||||
ecmaVersion: 2022
|
||||
ecmaVersion: "latest"
|
||||
},
|
||||
extends: ['eslint:recommended', 'airbnb-base'],
|
||||
env: {
|
||||
|
13
libraries/codemirror/addon/lint/lint.js
vendored
13
libraries/codemirror/addon/lint/lint.js
vendored
@ -24,8 +24,10 @@
|
||||
|
||||
function position(e) {
|
||||
if (!tt.parentNode) return CodeMirror.off(document, "mousemove", position);
|
||||
tt.style.top = Math.max(0, e.clientY - tt.offsetHeight - 5) + "px";
|
||||
tt.style.left = (e.clientX + 5) + "px";
|
||||
var top = Math.max(0, e.clientY - tt.offsetHeight - 5);
|
||||
var left = Math.max(0, Math.min(e.clientX + 5, tt.ownerDocument.defaultView.innerWidth - tt.offsetWidth));
|
||||
tt.style.top = top + "px"
|
||||
tt.style.left = left + "px";
|
||||
}
|
||||
CodeMirror.on(document, "mousemove", position);
|
||||
position(e);
|
||||
@ -199,10 +201,6 @@
|
||||
var anns = annotations[line];
|
||||
if (!anns) continue;
|
||||
|
||||
// filter out duplicate messages
|
||||
var message = [];
|
||||
anns = anns.filter(function(item) { return message.indexOf(item.message) > -1 ? false : message.push(item.message) });
|
||||
|
||||
var maxSeverity = null;
|
||||
var tipLabel = state.hasGutter && document.createDocumentFragment();
|
||||
|
||||
@ -220,9 +218,8 @@
|
||||
__annotation: ann
|
||||
}));
|
||||
}
|
||||
// use original annotations[line] to show multiple messages
|
||||
if (state.hasGutter)
|
||||
cm.setGutterMarker(line, GUTTER_ID, makeMarker(cm, tipLabel, maxSeverity, annotations[line].length > 1,
|
||||
cm.setGutterMarker(line, GUTTER_ID, makeMarker(cm, tipLabel, maxSeverity, anns.length > 1,
|
||||
options.tooltips));
|
||||
|
||||
if (options.highlightLines)
|
||||
|
136
libraries/codemirror/addon/mode/multiplex.js
vendored
Normal file
136
libraries/codemirror/addon/mode/multiplex.js
vendored
Normal file
@ -0,0 +1,136 @@
|
||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
CodeMirror.multiplexingMode = function(outer /*, others */) {
|
||||
// Others should be {open, close, mode [, delimStyle] [, innerStyle] [, parseDelimiters]} objects
|
||||
var others = Array.prototype.slice.call(arguments, 1);
|
||||
|
||||
function indexOf(string, pattern, from, returnEnd) {
|
||||
if (typeof pattern == "string") {
|
||||
var found = string.indexOf(pattern, from);
|
||||
return returnEnd && found > -1 ? found + pattern.length : found;
|
||||
}
|
||||
var m = pattern.exec(from ? string.slice(from) : string);
|
||||
return m ? m.index + from + (returnEnd ? m[0].length : 0) : -1;
|
||||
}
|
||||
|
||||
return {
|
||||
startState: function() {
|
||||
return {
|
||||
outer: CodeMirror.startState(outer),
|
||||
innerActive: null,
|
||||
inner: null,
|
||||
startingInner: false
|
||||
};
|
||||
},
|
||||
|
||||
copyState: function(state) {
|
||||
return {
|
||||
outer: CodeMirror.copyState(outer, state.outer),
|
||||
innerActive: state.innerActive,
|
||||
inner: state.innerActive && CodeMirror.copyState(state.innerActive.mode, state.inner),
|
||||
startingInner: state.startingInner
|
||||
};
|
||||
},
|
||||
|
||||
token: function(stream, state) {
|
||||
if (!state.innerActive) {
|
||||
var cutOff = Infinity, oldContent = stream.string;
|
||||
for (var i = 0; i < others.length; ++i) {
|
||||
var other = others[i];
|
||||
var found = indexOf(oldContent, other.open, stream.pos);
|
||||
if (found == stream.pos) {
|
||||
if (!other.parseDelimiters) stream.match(other.open);
|
||||
state.startingInner = !!other.parseDelimiters
|
||||
state.innerActive = other;
|
||||
|
||||
// Get the outer indent, making sure to handle CodeMirror.Pass
|
||||
var outerIndent = 0;
|
||||
if (outer.indent) {
|
||||
var possibleOuterIndent = outer.indent(state.outer, "", "");
|
||||
if (possibleOuterIndent !== CodeMirror.Pass) outerIndent = possibleOuterIndent;
|
||||
}
|
||||
|
||||
state.inner = CodeMirror.startState(other.mode, outerIndent);
|
||||
return other.delimStyle && (other.delimStyle + " " + other.delimStyle + "-open");
|
||||
} else if (found != -1 && found < cutOff) {
|
||||
cutOff = found;
|
||||
}
|
||||
}
|
||||
if (cutOff != Infinity) stream.string = oldContent.slice(0, cutOff);
|
||||
var outerToken = outer.token(stream, state.outer);
|
||||
if (cutOff != Infinity) stream.string = oldContent;
|
||||
return outerToken;
|
||||
} else {
|
||||
var curInner = state.innerActive, oldContent = stream.string;
|
||||
if (!curInner.close && stream.sol()) {
|
||||
state.innerActive = state.inner = null;
|
||||
return this.token(stream, state);
|
||||
}
|
||||
var found = curInner.close && !state.startingInner ?
|
||||
indexOf(oldContent, curInner.close, stream.pos, curInner.parseDelimiters) : -1;
|
||||
if (found == stream.pos && !curInner.parseDelimiters) {
|
||||
stream.match(curInner.close);
|
||||
state.innerActive = state.inner = null;
|
||||
return curInner.delimStyle && (curInner.delimStyle + " " + curInner.delimStyle + "-close");
|
||||
}
|
||||
if (found > -1) stream.string = oldContent.slice(0, found);
|
||||
var innerToken = curInner.mode.token(stream, state.inner);
|
||||
if (found > -1) stream.string = oldContent;
|
||||
else if (stream.pos > stream.start) state.startingInner = false
|
||||
|
||||
if (found == stream.pos && curInner.parseDelimiters)
|
||||
state.innerActive = state.inner = null;
|
||||
|
||||
if (curInner.innerStyle) {
|
||||
if (innerToken) innerToken = innerToken + " " + curInner.innerStyle;
|
||||
else innerToken = curInner.innerStyle;
|
||||
}
|
||||
|
||||
return innerToken;
|
||||
}
|
||||
},
|
||||
|
||||
indent: function(state, textAfter, line) {
|
||||
var mode = state.innerActive ? state.innerActive.mode : outer;
|
||||
if (!mode.indent) return CodeMirror.Pass;
|
||||
return mode.indent(state.innerActive ? state.inner : state.outer, textAfter, line);
|
||||
},
|
||||
|
||||
blankLine: function(state) {
|
||||
var mode = state.innerActive ? state.innerActive.mode : outer;
|
||||
if (mode.blankLine) {
|
||||
mode.blankLine(state.innerActive ? state.inner : state.outer);
|
||||
}
|
||||
if (!state.innerActive) {
|
||||
for (var i = 0; i < others.length; ++i) {
|
||||
var other = others[i];
|
||||
if (other.open === "\n") {
|
||||
state.innerActive = other;
|
||||
state.inner = CodeMirror.startState(other.mode, mode.indent ? mode.indent(state.outer, "", "") : 0);
|
||||
}
|
||||
}
|
||||
} else if (state.innerActive.close === "\n") {
|
||||
state.innerActive = state.inner = null;
|
||||
}
|
||||
},
|
||||
|
||||
electricChars: outer.electricChars,
|
||||
|
||||
innerMode: function(state) {
|
||||
return state.inner ? {state: state.inner, mode: state.innerActive.mode} : {state: state.outer, mode: outer};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
});
|
90
libraries/codemirror/addon/mode/overlay.js
vendored
Normal file
90
libraries/codemirror/addon/mode/overlay.js
vendored
Normal file
@ -0,0 +1,90 @@
|
||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
|
||||
|
||||
// Utility function that allows modes to be combined. The mode given
|
||||
// as the base argument takes care of most of the normal mode
|
||||
// functionality, but a second (typically simple) mode is used, which
|
||||
// can override the style of text. Both modes get to parse all of the
|
||||
// text, but when both assign a non-null style to a piece of code, the
|
||||
// overlay wins, unless the combine argument was true and not overridden,
|
||||
// or state.overlay.combineTokens was true, in which case the styles are
|
||||
// combined.
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
CodeMirror.overlayMode = function(base, overlay, combine) {
|
||||
return {
|
||||
startState: function() {
|
||||
return {
|
||||
base: CodeMirror.startState(base),
|
||||
overlay: CodeMirror.startState(overlay),
|
||||
basePos: 0, baseCur: null,
|
||||
overlayPos: 0, overlayCur: null,
|
||||
streamSeen: null
|
||||
};
|
||||
},
|
||||
copyState: function(state) {
|
||||
return {
|
||||
base: CodeMirror.copyState(base, state.base),
|
||||
overlay: CodeMirror.copyState(overlay, state.overlay),
|
||||
basePos: state.basePos, baseCur: null,
|
||||
overlayPos: state.overlayPos, overlayCur: null
|
||||
};
|
||||
},
|
||||
|
||||
token: function(stream, state) {
|
||||
if (stream != state.streamSeen ||
|
||||
Math.min(state.basePos, state.overlayPos) < stream.start) {
|
||||
state.streamSeen = stream;
|
||||
state.basePos = state.overlayPos = stream.start;
|
||||
}
|
||||
|
||||
if (stream.start == state.basePos) {
|
||||
state.baseCur = base.token(stream, state.base);
|
||||
state.basePos = stream.pos;
|
||||
}
|
||||
if (stream.start == state.overlayPos) {
|
||||
stream.pos = stream.start;
|
||||
state.overlayCur = overlay.token(stream, state.overlay);
|
||||
state.overlayPos = stream.pos;
|
||||
}
|
||||
stream.pos = Math.min(state.basePos, state.overlayPos);
|
||||
|
||||
// state.overlay.combineTokens always takes precedence over combine,
|
||||
// unless set to null
|
||||
if (state.overlayCur == null) return state.baseCur;
|
||||
else if (state.baseCur != null &&
|
||||
state.overlay.combineTokens ||
|
||||
combine && state.overlay.combineTokens == null)
|
||||
return state.baseCur + " " + state.overlayCur;
|
||||
else return state.overlayCur;
|
||||
},
|
||||
|
||||
indent: base.indent && function(state, textAfter, line) {
|
||||
return base.indent(state.base, textAfter, line);
|
||||
},
|
||||
electricChars: base.electricChars,
|
||||
|
||||
innerMode: function(state) { return {state: state.base, mode: base}; },
|
||||
|
||||
blankLine: function(state) {
|
||||
var baseToken, overlayToken;
|
||||
if (base.blankLine) baseToken = base.blankLine(state.base);
|
||||
if (overlay.blankLine) overlayToken = overlay.blankLine(state.overlay);
|
||||
|
||||
return overlayToken == null ?
|
||||
baseToken :
|
||||
(combine && baseToken != null ? baseToken + " " + overlayToken : overlayToken);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
});
|
@ -8259,8 +8259,8 @@
|
||||
}
|
||||
|
||||
function disableBrowserMagic(field, spellcheck, autocorrect, autocapitalize) {
|
||||
field.setAttribute("autocorrect", autocorrect ? "" : "off");
|
||||
field.setAttribute("autocapitalize", autocapitalize ? "" : "off");
|
||||
field.setAttribute("autocorrect", autocorrect ? "on" : "off");
|
||||
field.setAttribute("autocapitalize", autocapitalize ? "on" : "off");
|
||||
field.setAttribute("spellcheck", !!spellcheck);
|
||||
}
|
||||
|
||||
@ -8275,7 +8275,6 @@
|
||||
else { te.setAttribute("wrap", "off"); }
|
||||
// If border: 0; -- iOS fails to open keyboard (issue #1287)
|
||||
if (ios) { te.style.border = "1px solid black"; }
|
||||
disableBrowserMagic(te);
|
||||
return div
|
||||
}
|
||||
|
||||
@ -8897,6 +8896,7 @@
|
||||
}
|
||||
// Old-fashioned briefly-focus-a-textarea hack
|
||||
var kludge = hiddenTextarea(), te = kludge.firstChild;
|
||||
disableBrowserMagic(te);
|
||||
cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild);
|
||||
te.value = lastCopied.text.join("\n");
|
||||
var hadFocus = activeElt(div.ownerDocument);
|
||||
@ -9461,6 +9461,8 @@
|
||||
// The semihidden textarea that is focused when the editor is
|
||||
// focused, and receives input.
|
||||
this.textarea = this.wrapper.firstChild;
|
||||
var opts = this.cm.options;
|
||||
disableBrowserMagic(this.textarea, opts.spellcheck, opts.autocorrect, opts.autocapitalize);
|
||||
};
|
||||
|
||||
TextareaInput.prototype.screenReaderLabelChanged = function (label) {
|
||||
@ -9865,7 +9867,7 @@
|
||||
|
||||
addLegacyProps(CodeMirror);
|
||||
|
||||
CodeMirror.version = "5.65.9";
|
||||
CodeMirror.version = "5.65.15";
|
||||
|
||||
return CodeMirror;
|
||||
|
||||
|
10
libraries/codemirror/mode/clike/clike.js
vendored
10
libraries/codemirror/mode/clike/clike.js
vendored
@ -218,7 +218,8 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
|
||||
},
|
||||
|
||||
indent: function(state, textAfter) {
|
||||
if (state.tokenize != tokenBase && state.tokenize != null || state.typeAtEndOfLine) return CodeMirror.Pass;
|
||||
if (state.tokenize != tokenBase && state.tokenize != null || state.typeAtEndOfLine && isTopScope(state.context))
|
||||
return CodeMirror.Pass;
|
||||
var ctx = state.context, firstChar = textAfter && textAfter.charAt(0);
|
||||
var closing = firstChar == ctx.type;
|
||||
if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev;
|
||||
@ -512,8 +513,8 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
|
||||
name: "clike",
|
||||
keywords: words("abstract as async await base break case catch checked class const continue" +
|
||||
" default delegate do else enum event explicit extern finally fixed for" +
|
||||
" foreach goto if implicit in interface internal is lock namespace new" +
|
||||
" operator out override params private protected public readonly ref return sealed" +
|
||||
" foreach goto if implicit in init interface internal is lock namespace new" +
|
||||
" operator out override params private protected public readonly record ref required return sealed" +
|
||||
" sizeof stackalloc static struct switch this throw try typeof unchecked" +
|
||||
" unsafe using virtual void volatile while add alias ascending descending dynamic from get" +
|
||||
" global group into join let orderby partial remove select set value var yield"),
|
||||
@ -522,7 +523,7 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
|
||||
" UInt64 bool byte char decimal double short int long object" +
|
||||
" sbyte float string ushort uint ulong"),
|
||||
blockKeywords: words("catch class do else finally for foreach if struct switch try while"),
|
||||
defKeywords: words("class interface namespace struct var"),
|
||||
defKeywords: words("class interface namespace record struct var"),
|
||||
typeFirstDefinitions: true,
|
||||
atoms: words("true false null"),
|
||||
hooks: {
|
||||
@ -613,6 +614,7 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
|
||||
return state.tokenize(stream, state);
|
||||
},
|
||||
"'": function(stream) {
|
||||
if (stream.match(/^(\\[^'\s]+|[^\\'])'/)) return "string-2"
|
||||
stream.eatWhile(/[\w\$_\xa1-\uffff]/);
|
||||
return "atom";
|
||||
},
|
||||
|
8
libraries/codemirror/mode/dart/dart.js
vendored
8
libraries/codemirror/mode/dart/dart.js
vendored
@ -12,10 +12,10 @@
|
||||
"use strict";
|
||||
|
||||
var keywords = ("this super static final const abstract class extends external factory " +
|
||||
"implements mixin get native set typedef with enum throw rethrow " +
|
||||
"assert break case continue default in return new deferred async await covariant " +
|
||||
"try catch finally do else for if switch while import library export " +
|
||||
"part of show hide is as extension on yield late required").split(" ");
|
||||
"implements mixin get native set typedef with enum throw rethrow assert break case " +
|
||||
"continue default in return new deferred async await covariant try catch finally " +
|
||||
"do else for if switch while import library export part of show hide is as extension " +
|
||||
"on yield late required sealed base interface when inline").split(" ");
|
||||
var blockKeywords = "try catch finally do else for if switch while".split(" ");
|
||||
var atoms = "true false null".split(" ");
|
||||
var builtins = "void bool num int double dynamic var String Null Never".split(" ");
|
||||
|
@ -779,7 +779,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
|
||||
if (type == "async" ||
|
||||
(type == "variable" &&
|
||||
(value == "static" || value == "get" || value == "set" || (isTS && isModifier(value))) &&
|
||||
cx.stream.match(/^\s+[\w$\xa1-\uffff]/, false))) {
|
||||
cx.stream.match(/^\s+#?[\w$\xa1-\uffff]/, false))) {
|
||||
cx.marked = "keyword";
|
||||
return cont(classBody);
|
||||
}
|
||||
|
2
libraries/codemirror/mode/nsis/nsis.js
vendored
2
libraries/codemirror/mode/nsis/nsis.js
vendored
@ -24,7 +24,7 @@ CodeMirror.defineSimpleMode("nsis",{
|
||||
{ regex: /`(?:[^\\`]|\\.)*`?/, token: "string" },
|
||||
|
||||
// Compile Time Commands
|
||||
{regex: /^\s*(?:\!(addincludedir|addplugindir|appendfile|cd|define|delfile|echo|error|execute|finalize|getdllversion|gettlbversion|include|insertmacro|macro|macroend|makensis|packhdr|pragma|searchparse|searchreplace|system|tempfile|undef|uninstfinalize|verbose|warning))\b/i, token: "keyword"},
|
||||
{regex: /^\s*(?:\!(addincludedir|addplugindir|appendfile|assert|cd|define|delfile|echo|error|execute|finalize|getdllversion|gettlbversion|include|insertmacro|macro|macroend|makensis|packhdr|pragma|searchparse|searchreplace|system|tempfile|undef|uninstfinalize|verbose|warning))\b/i, token: "keyword"},
|
||||
|
||||
// Conditional Compilation
|
||||
{regex: /^\s*(?:\!(if(?:n?def)?|ifmacron?def|macro))\b/i, token: "keyword", indent: true},
|
||||
|
3
libraries/codemirror/mode/pegjs/pegjs.js
vendored
3
libraries/codemirror/mode/pegjs/pegjs.js
vendored
@ -31,8 +31,6 @@ CodeMirror.defineMode("pegjs", function (config) {
|
||||
};
|
||||
},
|
||||
token: function (stream, state) {
|
||||
if (stream)
|
||||
|
||||
//check for state changes
|
||||
if (!state.inString && !state.inComment && ((stream.peek() == '"') || (stream.peek() == "'"))) {
|
||||
state.stringType = stream.peek();
|
||||
@ -43,7 +41,6 @@ CodeMirror.defineMode("pegjs", function (config) {
|
||||
state.inComment = true;
|
||||
}
|
||||
|
||||
//return state
|
||||
if (state.inString) {
|
||||
while (state.inString && !stream.eol()) {
|
||||
if (stream.peek() === state.stringType) {
|
||||
|
6
libraries/codemirror/mode/python/python.js
vendored
6
libraries/codemirror/mode/python/python.js
vendored
@ -20,7 +20,7 @@
|
||||
"def", "del", "elif", "else", "except", "finally",
|
||||
"for", "from", "global", "if", "import",
|
||||
"lambda", "pass", "raise", "return",
|
||||
"try", "while", "with", "yield", "in"];
|
||||
"try", "while", "with", "yield", "in", "False", "True"];
|
||||
var commonBuiltins = ["abs", "all", "any", "bin", "bool", "bytearray", "callable", "chr",
|
||||
"classmethod", "compile", "complex", "delattr", "dict", "dir", "divmod",
|
||||
"enumerate", "eval", "filter", "float", "format", "frozenset",
|
||||
@ -60,7 +60,7 @@
|
||||
if (py3) {
|
||||
// since http://legacy.python.org/dev/peps/pep-0465/ @ is also an operator
|
||||
var identifiers = parserConf.identifiers|| /^[_A-Za-z\u00A1-\uFFFF][_A-Za-z0-9\u00A1-\uFFFF]*/;
|
||||
myKeywords = myKeywords.concat(["nonlocal", "False", "True", "None", "async", "await"]);
|
||||
myKeywords = myKeywords.concat(["nonlocal", "None", "aiter", "anext", "async", "await", "breakpoint", "match", "case"]);
|
||||
myBuiltins = myBuiltins.concat(["ascii", "bytes", "exec", "print"]);
|
||||
var stringPrefixes = new RegExp("^(([rbuf]|(br)|(rb)|(fr)|(rf))?('{3}|\"{3}|['\"]))", "i");
|
||||
} else {
|
||||
@ -68,7 +68,7 @@
|
||||
myKeywords = myKeywords.concat(["exec", "print"]);
|
||||
myBuiltins = myBuiltins.concat(["apply", "basestring", "buffer", "cmp", "coerce", "execfile",
|
||||
"file", "intern", "long", "raw_input", "reduce", "reload",
|
||||
"unichr", "unicode", "xrange", "False", "True", "None"]);
|
||||
"unichr", "unicode", "xrange", "None"]);
|
||||
var stringPrefixes = new RegExp("^(([rubf]|(ur)|(br))?('{3}|\"{3}|['\"]))", "i");
|
||||
}
|
||||
var keywords = wordRegexp(myKeywords);
|
||||
|
23
libraries/codemirror/mode/sparql/sparql.js
vendored
23
libraries/codemirror/mode/sparql/sparql.js
vendored
@ -33,6 +33,9 @@ CodeMirror.defineMode("sparql", function(config) {
|
||||
"true", "false", "with",
|
||||
"data", "copy", "to", "move", "add", "create", "drop", "clear", "load", "into"]);
|
||||
var operatorChars = /[*+\-<>=&|\^\/!\?]/;
|
||||
var PN_CHARS = "[A-Za-z_\\-0-9]";
|
||||
var PREFIX_START = new RegExp("[A-Za-z]");
|
||||
var PREFIX_REMAINDER = new RegExp("((" + PN_CHARS + "|\\.)*(" + PN_CHARS + "))?:");
|
||||
|
||||
function tokenBase(stream, state) {
|
||||
var ch = stream.next();
|
||||
@ -71,20 +74,18 @@ CodeMirror.defineMode("sparql", function(config) {
|
||||
stream.eatWhile(/[a-z\d\-]/i);
|
||||
return "meta";
|
||||
}
|
||||
else {
|
||||
stream.eatWhile(/[_\w\d]/);
|
||||
if (stream.eat(":")) {
|
||||
else if (PREFIX_START.test(ch) && stream.match(PREFIX_REMAINDER)) {
|
||||
eatPnLocal(stream);
|
||||
return "atom";
|
||||
}
|
||||
var word = stream.current();
|
||||
if (ops.test(word))
|
||||
return "builtin";
|
||||
else if (keywords.test(word))
|
||||
return "keyword";
|
||||
else
|
||||
return "variable";
|
||||
}
|
||||
stream.eatWhile(/[_\w\d]/);
|
||||
var word = stream.current();
|
||||
if (ops.test(word))
|
||||
return "builtin";
|
||||
else if (keywords.test(word))
|
||||
return "keyword";
|
||||
else
|
||||
return "variable";
|
||||
}
|
||||
|
||||
function eatPnLocal(stream) {
|
||||
|
26
libraries/codemirror/mode/sql/sql.js
vendored
26
libraries/codemirror/mode/sql/sql.js
vendored
File diff suppressed because one or more lines are too long
2
libraries/codemirror/mode/yaml/yaml.js
vendored
2
libraries/codemirror/mode/yaml/yaml.js
vendored
@ -85,7 +85,7 @@ CodeMirror.defineMode("yaml", function() {
|
||||
}
|
||||
|
||||
/* pairs (associative arrays) -> key */
|
||||
if (!state.pair && stream.match(/^\s*(?:[,\[\]{}&*!|>'"%@`][^\s'":]|[^,\[\]{}#&*!|>'"%@`])[^#]*?(?=\s*:($|\s))/)) {
|
||||
if (!state.pair && stream.match(/^\s*(?:[,\[\]{}&*!|>'"%@`][^\s'":]|[^\s,\[\]{}#&*!|>'"%@`])[^#:]*(?=:($|\s))/)) {
|
||||
state.pair = true;
|
||||
state.keyCol = stream.indentation();
|
||||
return "atom";
|
||||
|
14
package.json
14
package.json
@ -2,7 +2,7 @@
|
||||
"name": "trilium",
|
||||
"productName": "Trilium Notes",
|
||||
"description": "Trilium Notes",
|
||||
"version": "0.61.7-beta",
|
||||
"version": "0.61.8-beta",
|
||||
"license": "AGPL-3.0-only",
|
||||
"main": "electron.js",
|
||||
"bin": {
|
||||
@ -36,7 +36,7 @@
|
||||
"@excalidraw/excalidraw": "0.16.1",
|
||||
"archiver": "5.3.1",
|
||||
"async-mutex": "0.4.0",
|
||||
"axios": "1.5.0",
|
||||
"axios": "1.5.1",
|
||||
"better-sqlite3": "8.4.0",
|
||||
"chokidar": "3.5.3",
|
||||
"cls-hooked": "4.2.2",
|
||||
@ -68,7 +68,7 @@
|
||||
"jimp": "0.22.10",
|
||||
"joplin-turndown-plugin-gfm": "1.0.12",
|
||||
"jsdom": "22.1.0",
|
||||
"marked": "9.0.3",
|
||||
"marked": "9.1.0",
|
||||
"mime-types": "2.1.35",
|
||||
"multer": "1.4.5-lts.1",
|
||||
"node-abi": "3.47.0",
|
||||
@ -78,11 +78,11 @@
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"request": "2.88.2",
|
||||
"rimraf": "5.0.1",
|
||||
"rimraf": "5.0.5",
|
||||
"safe-compare": "1.1.4",
|
||||
"sanitize-filename": "1.6.3",
|
||||
"sanitize-html": "2.11.0",
|
||||
"sax": "1.2.4",
|
||||
"sax": "1.3.0",
|
||||
"semver": "7.5.4",
|
||||
"serve-favicon": "2.5.0",
|
||||
"session-file-store": "1.5.0",
|
||||
@ -97,11 +97,11 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"cross-env": "7.0.3",
|
||||
"electron": "25.8.2",
|
||||
"electron": "25.9.0",
|
||||
"electron-builder": "24.6.4",
|
||||
"electron-packager": "17.1.2",
|
||||
"electron-rebuild": "3.2.9",
|
||||
"eslint": "8.49.0",
|
||||
"eslint": "8.50.0",
|
||||
"eslint-config-airbnb-base": "15.0.0",
|
||||
"eslint-config-prettier": "9.0.0",
|
||||
"eslint-plugin-import": "2.28.1",
|
||||
|
@ -208,6 +208,7 @@ class Becca {
|
||||
return this.etapiTokens[etapiTokenId];
|
||||
}
|
||||
|
||||
/** @returns {AbstractBeccaEntity|null} */
|
||||
getEntity(entityName, entityId) {
|
||||
if (!entityName || !entityId) {
|
||||
return null;
|
||||
|
@ -63,10 +63,10 @@ function load() {
|
||||
log.info(`Becca (note cache) load took ${Date.now() - start}ms`);
|
||||
}
|
||||
|
||||
function reload() {
|
||||
function reload(reason) {
|
||||
load();
|
||||
|
||||
require('../services/ws').reloadFrontend();
|
||||
require('../services/ws').reloadFrontend(reason || "becca reloaded");
|
||||
}
|
||||
|
||||
eventService.subscribeBeccaLoader([eventService.ENTITY_CHANGE_SYNCED], ({entityName, entityRow}) => {
|
||||
|
@ -18,31 +18,11 @@ let becca = null;
|
||||
class AbstractBeccaEntity {
|
||||
/** @protected */
|
||||
beforeSaving() {
|
||||
this.generateIdIfNecessary();
|
||||
}
|
||||
|
||||
/** @protected */
|
||||
generateIdIfNecessary() {
|
||||
if (!this[this.constructor.primaryKeyName]) {
|
||||
this[this.constructor.primaryKeyName] = utils.newEntityId();
|
||||
}
|
||||
}
|
||||
|
||||
/** @protected */
|
||||
generateHash(isDeleted = false) {
|
||||
let contentToHash = "";
|
||||
|
||||
for (const propertyName of this.constructor.hashedProperties) {
|
||||
contentToHash += `|${this[propertyName]}`;
|
||||
}
|
||||
|
||||
if (isDeleted) {
|
||||
contentToHash += "|deleted";
|
||||
}
|
||||
|
||||
return utils.hash(contentToHash).substr(0, 10);
|
||||
}
|
||||
|
||||
/** @protected */
|
||||
getUtcDateChanged() {
|
||||
return this.utcDateModified || this.utcDateCreated;
|
||||
@ -61,7 +41,7 @@ class AbstractBeccaEntity {
|
||||
}
|
||||
|
||||
/** @protected */
|
||||
putEntityChange(isDeleted = false) {
|
||||
putEntityChange(isDeleted) {
|
||||
entityChangesService.putEntityChange({
|
||||
entityName: this.constructor.entityName,
|
||||
entityId: this[this.constructor.primaryKeyName],
|
||||
@ -72,11 +52,37 @@ class AbstractBeccaEntity {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @protected
|
||||
* @returns {string}
|
||||
*/
|
||||
generateHash(isDeleted) {
|
||||
let contentToHash = "";
|
||||
|
||||
for (const propertyName of this.constructor.hashedProperties) {
|
||||
contentToHash += `|${this[propertyName]}`;
|
||||
}
|
||||
|
||||
if (isDeleted) {
|
||||
contentToHash += "|deleted";
|
||||
}
|
||||
|
||||
return utils.hash(contentToHash).substr(0, 10);
|
||||
}
|
||||
|
||||
/** @protected */
|
||||
getPojoToSave() {
|
||||
return this.getPojo();
|
||||
}
|
||||
|
||||
/**
|
||||
* @protected
|
||||
* @abstract
|
||||
*/
|
||||
getPojo() {
|
||||
throw new Error(`Unimplemented getPojo() for entity '${this.constructor.name}'`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves entity - executes SQL, but doesn't commit the transaction on its own
|
||||
*
|
||||
@ -88,9 +94,7 @@ class AbstractBeccaEntity {
|
||||
|
||||
const isNewEntity = !this[primaryKeyName];
|
||||
|
||||
if (this.beforeSaving) {
|
||||
this.beforeSaving(opts);
|
||||
}
|
||||
this.beforeSaving(opts);
|
||||
|
||||
const pojo = this.getPojoToSave();
|
||||
|
||||
@ -101,7 +105,7 @@ class AbstractBeccaEntity {
|
||||
return;
|
||||
}
|
||||
|
||||
this.putEntityChange(false);
|
||||
this.putEntityChange(!!this.isDeleted);
|
||||
|
||||
if (!cls.isEntityEventsDisabled()) {
|
||||
const eventPayload = {
|
||||
|
@ -20,8 +20,7 @@ const attachmentRoleToNoteTypeMapping = {
|
||||
class BAttachment extends AbstractBeccaEntity {
|
||||
static get entityName() { return "attachments"; }
|
||||
static get primaryKeyName() { return "attachmentId"; }
|
||||
static get hashedProperties() { return ["attachmentId", "ownerId", "role", "mime", "title", "blobId",
|
||||
"utcDateScheduledForErasureSince", "utcDateModified"]; }
|
||||
static get hashedProperties() { return ["attachmentId", "ownerId", "role", "mime", "title", "blobId", "utcDateScheduledForErasureSince"]; }
|
||||
|
||||
constructor(row) {
|
||||
super();
|
||||
|
@ -150,11 +150,17 @@ class BNote extends AbstractBeccaEntity {
|
||||
*/
|
||||
this.contentSize = null;
|
||||
/**
|
||||
* size of the content and note revision contents in bytes
|
||||
* size of the note content, attachment contents in bytes
|
||||
* @type {int|null}
|
||||
* @private
|
||||
*/
|
||||
this.noteSize = null;
|
||||
this.contentAndAttachmentsSize = null;
|
||||
/**
|
||||
* size of the note content, attachment contents and revision contents in bytes
|
||||
* @type {int|null}
|
||||
* @private
|
||||
*/
|
||||
this.contentAndAttachmentsAndRevisionsSize = null;
|
||||
/**
|
||||
* number of note revisions for this note
|
||||
* @type {int|null}
|
||||
@ -1625,16 +1631,12 @@ class BNote extends AbstractBeccaEntity {
|
||||
|
||||
revision.save(); // to generate revisionId, which is then used to save attachments
|
||||
|
||||
if (this.type === 'text') {
|
||||
for (const noteAttachment of this.getAttachments()) {
|
||||
if (noteAttachment.utcDateScheduledForErasureSince) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const revisionAttachment = noteAttachment.copy();
|
||||
revisionAttachment.ownerId = revision.revisionId;
|
||||
revisionAttachment.setContent(noteAttachment.getContent(), {forceSave: true});
|
||||
for (const noteAttachment of this.getAttachments()) {
|
||||
const revisionAttachment = noteAttachment.copy();
|
||||
revisionAttachment.ownerId = revision.revisionId;
|
||||
revisionAttachment.setContent(noteAttachment.getContent(), {forceSave: true});
|
||||
|
||||
if (this.type === 'text') {
|
||||
// content is rewritten to point to the revision attachments
|
||||
noteContent = noteContent.replaceAll(`attachments/${noteAttachment.attachmentId}`,
|
||||
`attachments/${revisionAttachment.attachmentId}`);
|
||||
|
@ -86,6 +86,29 @@ class BRevision extends AbstractBeccaEntity {
|
||||
return this._getContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {*}
|
||||
* @throws Error in case of invalid JSON */
|
||||
getJsonContent() {
|
||||
const content = this.getContent();
|
||||
|
||||
if (!content || !content.trim()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return JSON.parse(content);
|
||||
}
|
||||
|
||||
/** @returns {*|null} valid object or null if the content cannot be parsed as JSON */
|
||||
getJsonContentSafely() {
|
||||
try {
|
||||
return this.getJsonContent();
|
||||
}
|
||||
catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param content
|
||||
* @param {object} [opts]
|
||||
@ -105,6 +128,45 @@ class BRevision extends AbstractBeccaEntity {
|
||||
.map(row => new BAttachment(row));
|
||||
}
|
||||
|
||||
/** @returns {BAttachment|null} */
|
||||
getAttachmentById(attachmentId, opts = {}) {
|
||||
opts.includeContentLength = !!opts.includeContentLength;
|
||||
|
||||
const query = opts.includeContentLength
|
||||
? `SELECT attachments.*, LENGTH(blobs.content) AS contentLength
|
||||
FROM attachments
|
||||
JOIN blobs USING (blobId)
|
||||
WHERE ownerId = ? AND attachmentId = ? AND isDeleted = 0`
|
||||
: `SELECT * FROM attachments WHERE ownerId = ? AND attachmentId = ? AND isDeleted = 0`;
|
||||
|
||||
return sql.getRows(query, [this.revisionId, attachmentId])
|
||||
.map(row => new BAttachment(row))[0];
|
||||
}
|
||||
|
||||
/** @returns {BAttachment[]} */
|
||||
getAttachmentsByRole(role) {
|
||||
return sql.getRows(`
|
||||
SELECT attachments.*
|
||||
FROM attachments
|
||||
WHERE ownerId = ?
|
||||
AND role = ?
|
||||
AND isDeleted = 0
|
||||
ORDER BY position`, [this.revisionId, role])
|
||||
.map(row => new BAttachment(row));
|
||||
}
|
||||
|
||||
/** @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];
|
||||
}
|
||||
|
||||
beforeSaving() {
|
||||
super.beforeSaving();
|
||||
|
||||
|
@ -127,7 +127,8 @@ paths:
|
||||
- targetRelationCount
|
||||
- targetRelationCountIncludingLinks
|
||||
- contentSize
|
||||
- noteSize
|
||||
- contentAndAttachmentsSize
|
||||
- contentAndAttachmentsAndRevisionsSize
|
||||
- revisionCount
|
||||
- name: orderDirection
|
||||
in: query
|
||||
|
@ -32,8 +32,8 @@ export default class LauncherContextMenu {
|
||||
const isVisibleItem = parentNoteId === '_lbVisibleLaunchers';
|
||||
const isAvailableItem = parentNoteId === '_lbAvailableLaunchers';
|
||||
const isItem = isVisibleItem || isAvailableItem;
|
||||
const canBeDeleted = !note.isLaunchBarConfig();
|
||||
const canBeReset = note.isLaunchBarConfig();
|
||||
const canBeDeleted = !note.noteId.startsWith("_"); // fixed notes can't be deleted
|
||||
const canBeReset = !canBeDeleted && note.isLaunchBarConfig();;
|
||||
|
||||
return [
|
||||
(isVisibleRoot || isAvailableRoot) ? { title: 'Add a note launcher', command: 'addNoteLauncher', uiIcon: "bx bx-plus" } : null,
|
||||
|
@ -10,6 +10,8 @@ const CODE_MIRROR = {
|
||||
"libraries/codemirror/addon/lint/lint.js",
|
||||
"libraries/codemirror/addon/lint/eslint.js",
|
||||
"libraries/codemirror/addon/mode/loadmode.js",
|
||||
"libraries/codemirror/addon/mode/multiplex.js",
|
||||
"libraries/codemirror/addon/mode/overlay.js",
|
||||
"libraries/codemirror/addon/mode/simple.js",
|
||||
"libraries/codemirror/addon/search/match-highlighter.js",
|
||||
"libraries/codemirror/mode/meta.js",
|
||||
|
@ -252,11 +252,15 @@ class NoteListRenderer {
|
||||
if (pageCount < 20 || i <= 5 || pageCount - i <= 5 || Math.abs(this.page - i) <= 2) {
|
||||
lastPrinted = true;
|
||||
|
||||
const startIndex = (i - 1) * this.pageSize + 1;
|
||||
const endIndex = Math.min(this.noteIds.length, i * this.pageSize);
|
||||
|
||||
$pager.append(
|
||||
i === this.page
|
||||
? $('<span>').text(i).css('text-decoration', 'underline').css('font-weight', "bold")
|
||||
: $('<a href="javascript:">')
|
||||
.text(i)
|
||||
.attr("title", `Page of ${startIndex} - ${endIndex}`)
|
||||
.on('click', () => {
|
||||
this.page = i;
|
||||
this.renderList();
|
||||
@ -270,6 +274,9 @@ class NoteListRenderer {
|
||||
lastPrinted = false;
|
||||
}
|
||||
}
|
||||
|
||||
// no need to distinguish "note" vs "notes" since in case of one result, there's no paging at all
|
||||
$pager.append(`<span class="note-list-pager-total-count">(${this.noteIds.length} notes)</span>`);
|
||||
}
|
||||
|
||||
async renderNote(note, expand = false) {
|
||||
|
@ -45,6 +45,7 @@ const TPL = `
|
||||
<a class="dropdown-item export-note-button">Export note</a>
|
||||
<a class="dropdown-item delete-note-button">Delete note</a>
|
||||
<a data-trigger-command="printActiveNote" class="dropdown-item print-active-note-button"><kbd data-command="printActiveNote"></kbd> Print note</a>
|
||||
<a data-trigger-command="forceSaveRevision" class="dropdown-item save-revision-button"><kbd data-command="forceSaveRevision"></kbd> Save revision</a>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
|
@ -274,26 +274,11 @@ export default class RevisionsDialog extends BasicWidget {
|
||||
|
||||
this.$content.html($table);
|
||||
} else if (revisionItem.type === 'canvas') {
|
||||
/**
|
||||
* FIXME: We load a font called Virgil.wof2, which originates from excalidraw.com
|
||||
* REMOVE external dependency!!!! This is defined in the svg in defs.style
|
||||
*/
|
||||
const content = fullRevision.content;
|
||||
const sanitizedTitle = revisionItem.title.replace(/[^a-z0-9-.]/gi, "");
|
||||
|
||||
try {
|
||||
const data = JSON.parse(content)
|
||||
const svg = data.svg || "no svg present."
|
||||
|
||||
/**
|
||||
* maxWidth: 100% use full width of container but do not enlarge!
|
||||
* height:auto to ensure that height scales with width
|
||||
*/
|
||||
const $svgHtml = $(svg).css({maxWidth: "100%", height: "auto"});
|
||||
this.$content.html($('<div>').append($svgHtml));
|
||||
} catch (err) {
|
||||
console.error("error parsing fullRevision.content as JSON", fullRevision.content, err);
|
||||
this.$content.html($("<div>").text("Error parsing content. Please check console.error() for more details."));
|
||||
}
|
||||
this.$content.html($("<img>")
|
||||
.attr("src", `api/revisions/${revisionItem.revisionId}/image/${sanitizedTitle}?${Math.random()}`)
|
||||
.css("max-width", "100%"));
|
||||
} else {
|
||||
this.$content.text("Preview isn't available for this note type.");
|
||||
}
|
||||
|
@ -14,7 +14,8 @@ const TPL = `
|
||||
<option value="dateCreated">Date created</option>
|
||||
<option value="dateModified">Date of last modification</option>
|
||||
<option value="contentSize">Note content size</option>
|
||||
<option value="noteSize">Note content size including revisions</option>
|
||||
<option value="contentAndAttachmentsSize">Note content size including attachments</option>
|
||||
<option value="contentAndAttachmentsAndRevisionsSize">Note content size including attachments and revisions</option>
|
||||
<option value="revisionCount">Number of revisions</option>
|
||||
<option value="childrenCount">Number of children notes</option>
|
||||
<option value="parentCount">Number of clones</option>
|
||||
|
@ -5,14 +5,27 @@ const becca = require('../../becca/becca');
|
||||
const RESOURCE_DIR = require('../../services/resource_dir').RESOURCE_DIR;
|
||||
const fs = require('fs');
|
||||
|
||||
function returnImage(req, res) {
|
||||
function returnImageFromNote(req, res) {
|
||||
const image = becca.getNote(req.params.noteId);
|
||||
|
||||
return returnImageInt(image, res);
|
||||
}
|
||||
|
||||
function returnImageFromRevision(req, res) {
|
||||
const image = becca.getRevision(req.params.revisionId);
|
||||
|
||||
return returnImageInt(image, res);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {BNote|BRevision} image
|
||||
* @param res
|
||||
*/
|
||||
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"].includes(image.type)) {
|
||||
return res.sendStatus(400);
|
||||
}
|
||||
|
||||
@ -84,7 +97,8 @@ function updateImage(req) {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
returnImage,
|
||||
returnImageFromNote,
|
||||
returnImageFromRevision,
|
||||
returnAttachedImage,
|
||||
updateImage
|
||||
};
|
||||
|
@ -181,6 +181,8 @@ function register(app) {
|
||||
apiRoute(GET, '/api/revisions/:revisionId/blob', revisionsApiRoute.getRevisionBlob);
|
||||
apiRoute(DEL, '/api/revisions/:revisionId', revisionsApiRoute.eraseRevision);
|
||||
apiRoute(PST, '/api/revisions/:revisionId/restore', revisionsApiRoute.restoreRevision);
|
||||
route(GET, '/api/revisions/:revisionId/image/:filename', [auth.checkApiAuthOrElectron], imageRoute.returnImageFromRevision);
|
||||
|
||||
route(GET, '/api/revisions/:revisionId/download', [auth.checkApiAuthOrElectron], revisionsApiRoute.downloadRevision);
|
||||
|
||||
|
||||
@ -200,7 +202,7 @@ function register(app) {
|
||||
apiRoute(GET, '/api/attribute-values/:attributeName', attributesRoute.getValuesForAttribute);
|
||||
|
||||
// :filename is not used by trilium, but instead used for "save as" to assign a human-readable filename
|
||||
route(GET, '/api/images/:noteId/:filename', [auth.checkApiAuthOrElectron], imageRoute.returnImage);
|
||||
route(GET, '/api/images/:noteId/:filename', [auth.checkApiAuthOrElectron], imageRoute.returnImageFromNote);
|
||||
route(PUT, '/api/images/:noteId', [auth.checkApiAuthOrElectron, uploadMiddlewareWithErrorHandling, csrfMiddleware], imageRoute.updateImage, apiResultHandler);
|
||||
|
||||
apiRoute(GET, '/api/options', optionsApiRoute.getOptions);
|
||||
|
@ -7,8 +7,9 @@ const sql = require("./sql");
|
||||
|
||||
function getFullAnonymizationScript() {
|
||||
// we want to delete all non-builtin attributes because they can contain sensitive names and values
|
||||
// on the other hand builtin/system attrs should not contain any sensitive info
|
||||
// on the other hand builtin/system attrs should not contain any sensitive info
|
||||
const builtinAttrNames = BUILTIN_ATTRIBUTES
|
||||
.filter(attr => !["shareCredentials", "shareAlias"].includes(attr.name))
|
||||
.map(attr => `'${attr.name}'`).join(', ');
|
||||
|
||||
const anonymizeScript = `
|
||||
|
@ -4,7 +4,7 @@ const build = require('./build');
|
||||
const packageJson = require('../../package');
|
||||
const {TRILIUM_DATA_DIR} = require('./data_dir');
|
||||
|
||||
const APP_DB_VERSION = 225;
|
||||
const APP_DB_VERSION = 226;
|
||||
const SYNC_VERSION = 31;
|
||||
const CLIPPER_PROTOCOL_VERSION = "1.0";
|
||||
|
||||
|
@ -1 +1 @@
|
||||
module.exports = { buildDate:"2023-09-21T23:38:18+02:00", buildRevision: "79e5e3b65ff613cdb81e2afaa832037ccf06d7b8" };
|
||||
module.exports = { buildDate:"2023-09-29T00:54:45+02:00", buildRevision: "e5555beea9a1638fefa218118e0596f4cfc1f4d0" };
|
||||
|
@ -763,7 +763,7 @@ class ConsistencyChecks {
|
||||
}
|
||||
|
||||
if (this.reloadNeeded) {
|
||||
require("../becca/becca_loader").reload();
|
||||
require("../becca/becca_loader").reload("consistency checks need becca reload");
|
||||
}
|
||||
|
||||
return !this.unrecoveredConsistencyErrors;
|
||||
|
@ -148,12 +148,13 @@ function fillEntityChanges(entityName, entityPrimaryKey, condition = '') {
|
||||
const entity = becca.getEntity(entityName, entityId);
|
||||
|
||||
if (entity) {
|
||||
ec.hash = entity.generateHash() || "|deleted";
|
||||
ec.hash = entity.generateHash();
|
||||
ec.utcDateChanged = entity.getUtcDateChanged() || dateUtils.utcNowDateTime();
|
||||
ec.isSynced = entityName !== 'options' || !!entity.isSynced;
|
||||
} else {
|
||||
// entity might be null (not present in becca) when it's deleted
|
||||
// FIXME: hacky, not sure if it might cause some problems
|
||||
// this will produce different hash value than when entity is being deleted since then
|
||||
// all normal hashed attributes are being used. Sync should recover from that, though.
|
||||
ec.hash = "deleted";
|
||||
ec.utcDateChanged = dateUtils.utcNowDateTime();
|
||||
ec.isSynced = true; // deletable (the ones with isDeleted) entities are synced
|
||||
|
@ -327,6 +327,11 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
||||
content = content.replace(/<\/body>.*<\/html>/gis, "");
|
||||
|
||||
content = content.replace(/src="([^"]*)"/g, (match, url) => {
|
||||
if (url.startsWith("data:image")) {
|
||||
// inline images are parsed and saved into attachments in the note service
|
||||
return match;
|
||||
}
|
||||
|
||||
try {
|
||||
url = decodeURIComponent(url).trim();
|
||||
} catch (e) {
|
||||
@ -456,7 +461,7 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
||||
position: attachmentMeta.position
|
||||
});
|
||||
|
||||
attachment.setContent(content);
|
||||
attachment.setContent(content, { forceSave: true });
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -60,7 +60,7 @@ function checkProtectedSessionExpiration() {
|
||||
|
||||
log.info("Expiring protected session");
|
||||
|
||||
require('./ws').reloadFrontend();
|
||||
require('./ws').reloadFrontend("leaving protected session");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -56,9 +56,9 @@ class OrderByAndLimitExp extends Expression {
|
||||
if (!valA && !valB) {
|
||||
// the attribute value is empty/zero in both notes so continue to the next order definition
|
||||
continue;
|
||||
} else if (!valB || valA < valB) {
|
||||
} else if (valA < valB) {
|
||||
return smaller;
|
||||
} else if (!valA || valA > valB) {
|
||||
} else if (valA > valB) {
|
||||
return larger;
|
||||
}
|
||||
// else the values are equal and continue to next order definition
|
||||
|
@ -31,7 +31,8 @@ const PROP_MAPPING = {
|
||||
"targetrelationcount": "targetRelationCount",
|
||||
"targetrelationcountincludinglinks": "targetRelationCountIncludingLinks",
|
||||
"contentsize": "contentSize",
|
||||
"notesize": "noteSize",
|
||||
"contentandattachmentssize": "contentAndAttachmentsSize",
|
||||
"contentandattachmentsandrevisionssize": "contentAndAttachmentsAndRevisionsSize",
|
||||
"revisioncount": "revisionCount"
|
||||
};
|
||||
|
||||
@ -48,7 +49,7 @@ class PropertyComparisonExp extends Expression {
|
||||
this.comparedValue = comparedValue; // for DEBUG mode
|
||||
this.comparator = buildComparator(operator, comparedValue);
|
||||
|
||||
if (['contentsize', 'notesize', 'revisioncount'].includes(this.propertyName)) {
|
||||
if (['contentsize', 'contentandattachmentssize', 'contentandattachmentsandrevisionssize', 'revisioncount'].includes(this.propertyName)) {
|
||||
searchContext.dbLoadNeeded = true;
|
||||
}
|
||||
}
|
||||
|
@ -92,47 +92,107 @@ function searchFromRelation(note, relationName) {
|
||||
function loadNeededInfoFromDatabase() {
|
||||
const sql = require('../../sql');
|
||||
|
||||
for (const noteId in becca.notes) {
|
||||
becca.notes[noteId].contentSize = 0;
|
||||
becca.notes[noteId].noteSize = 0;
|
||||
becca.notes[noteId].revisionCount = 0;
|
||||
}
|
||||
/**
|
||||
* This complex structure is needed to calculate total occupied space by a note. Several object instances
|
||||
* (note, revisions, attachments) can point to a single blobId, and thus the blob size should count towards the total
|
||||
* only once.
|
||||
*
|
||||
* @var {Object.<string, Object.<string, int>>} - noteId => { blobId => blobSize }
|
||||
*/
|
||||
const noteBlobs = {};
|
||||
|
||||
const noteContentLengths = sql.getRows(`
|
||||
SELECT
|
||||
noteId,
|
||||
blobId,
|
||||
LENGTH(content) AS length
|
||||
FROM notes
|
||||
JOIN blobs USING(blobId)
|
||||
WHERE notes.isDeleted = 0`);
|
||||
|
||||
for (const {noteId, length} of noteContentLengths) {
|
||||
for (const {noteId, blobId, length} of noteContentLengths) {
|
||||
if (!(noteId in becca.notes)) {
|
||||
log.error(`Note ${noteId} not found in becca.`);
|
||||
log.error(`Note '${noteId}' not found in becca.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
becca.notes[noteId].contentSize = length;
|
||||
becca.notes[noteId].noteSize = length;
|
||||
becca.notes[noteId].revisionCount = 0;
|
||||
|
||||
noteBlobs[noteId] = { [blobId]: length };
|
||||
}
|
||||
|
||||
const revisionContentLengths = sql.getRows(`
|
||||
SELECT
|
||||
noteId,
|
||||
LENGTH(content) AS length
|
||||
FROM notes
|
||||
JOIN revisions USING(noteId)
|
||||
JOIN blobs USING(blobId)
|
||||
WHERE notes.isDeleted = 0`);
|
||||
const attachmentContentLengths = sql.getRows(`
|
||||
SELECT
|
||||
ownerId AS noteId,
|
||||
attachments.blobId,
|
||||
LENGTH(content) AS length
|
||||
FROM attachments
|
||||
JOIN notes ON attachments.ownerId = notes.noteId
|
||||
JOIN blobs ON attachments.blobId = blobs.blobId
|
||||
WHERE attachments.isDeleted = 0
|
||||
AND notes.isDeleted = 0`);
|
||||
|
||||
for (const {noteId, length} of revisionContentLengths) {
|
||||
for (const {noteId, blobId, length} of attachmentContentLengths) {
|
||||
if (!(noteId in becca.notes)) {
|
||||
log.error(`Note ${noteId} not found in becca.`);
|
||||
log.error(`Note '${noteId}' not found in becca.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
becca.notes[noteId].noteSize += length;
|
||||
becca.notes[noteId].revisionCount++;
|
||||
if (!(noteId in noteBlobs)) {
|
||||
log.error(`Did not find a '${noteId}' in the noteBlobs.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
noteBlobs[noteId][blobId] = length;
|
||||
}
|
||||
|
||||
for (const noteId in noteBlobs) {
|
||||
becca.notes[noteId].contentAndAttachmentsSize = Object.values(noteBlobs[noteId]).reduce((acc, size) => acc + size, 0);
|
||||
}
|
||||
|
||||
const revisionContentLengths = sql.getRows(`
|
||||
SELECT
|
||||
noteId,
|
||||
revisions.blobId,
|
||||
LENGTH(content) AS length,
|
||||
1 AS isNoteRevision
|
||||
FROM notes
|
||||
JOIN revisions USING(noteId)
|
||||
JOIN blobs ON revisions.blobId = blobs.blobId
|
||||
WHERE notes.isDeleted = 0
|
||||
UNION ALL
|
||||
SELECT
|
||||
noteId,
|
||||
revisions.blobId,
|
||||
LENGTH(content) AS length,
|
||||
0 AS isNoteRevision -- it's attachment not counting towards revision count
|
||||
FROM notes
|
||||
JOIN revisions USING(noteId)
|
||||
JOIN attachments ON attachments.ownerId = revisions.revisionId
|
||||
JOIN blobs ON attachments.blobId = blobs.blobId
|
||||
WHERE notes.isDeleted = 0`);
|
||||
|
||||
for (const {noteId, blobId, length, isNoteRevision} of revisionContentLengths) {
|
||||
if (!(noteId in becca.notes)) {
|
||||
log.error(`Note '${noteId}' not found in becca.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!(noteId in noteBlobs)) {
|
||||
log.error(`Did not find a '${noteId}' in the noteBlobs.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
noteBlobs[noteId][blobId] = length;
|
||||
|
||||
if (isNoteRevision) {
|
||||
becca.notes[noteId].revisionCount++;
|
||||
}
|
||||
}
|
||||
|
||||
for (const noteId in noteBlobs) {
|
||||
becca.notes[noteId].contentAndAttachmentsAndRevisionsSize = Object.values(noteBlobs[noteId]).reduce((acc, size) => acc + size, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,8 @@ const PROP_MAPPING = {
|
||||
"targetrelationcount": "targetRelationCount",
|
||||
"targetrelationcountincludinglinks": "targetRelationCountIncludingLinks",
|
||||
"contentsize": "contentSize",
|
||||
"notesize": "noteSize",
|
||||
"contentandattachmentssize": "contentAndAttachmentsSize",
|
||||
"contentandattachmentsandrevisionssize": "contentAndAttachmentsAndRevisionsSize",
|
||||
"revisioncount": "revisionCount"
|
||||
};
|
||||
|
||||
@ -42,7 +43,7 @@ class ValueExtractor {
|
||||
this.propertyPath = ['note', 'relations', this.propertyPath[0].substr(1), ...this.propertyPath.slice(1, this.propertyPath.length)];
|
||||
}
|
||||
|
||||
if (['contentsize', 'notesize', 'revisioncount'].includes(this.propertyPath[this.propertyPath.length - 1])) {
|
||||
if (['contentsize', 'contentandattachmentssize', 'contentandattachmentsandrevisionssize', 'revisioncount'].includes(this.propertyPath[this.propertyPath.length - 1])) {
|
||||
searchContext.dbLoadNeeded = true;
|
||||
}
|
||||
}
|
||||
|
@ -151,9 +151,14 @@ async function pullChanges(syncContext) {
|
||||
if (entityChanges.length === 0) {
|
||||
break;
|
||||
} else {
|
||||
const sizeInKb = Math.round(JSON.stringify(resp).length / 1024);
|
||||
try { // https://github.com/zadam/trilium/issues/4310
|
||||
const sizeInKb = Math.round(JSON.stringify(resp).length / 1024);
|
||||
|
||||
log.info(`Sync ${logMarkerId}: Pulled ${entityChanges.length} changes in ${sizeInKb} KB, starting at entityChangeId=${lastSyncedPull} in ${pulledDate - startDate}ms and applied them in ${Date.now() - pulledDate}ms, ${outstandingPullCount} outstanding pulls`);
|
||||
log.info(`Sync ${logMarkerId}: Pulled ${entityChanges.length} changes in ${sizeInKb} KB, starting at entityChangeId=${lastSyncedPull} in ${pulledDate - startDate}ms and applied them in ${Date.now() - pulledDate}ms, ${outstandingPullCount} outstanding pulls`);
|
||||
}
|
||||
catch (e) {
|
||||
log.error(`Error occurred ${e.message} ${e.stack}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,7 +106,7 @@ function updateNormalEntity(remoteEC, remoteEntityRow, instanceId, updateContext
|
||||
updateContext.updated[remoteEC.entityName] = updateContext.updated[remoteEC.entityName] || [];
|
||||
updateContext.updated[remoteEC.entityName].push(remoteEC.entityId);
|
||||
|
||||
if (!localEC || localEC.utcDateChanged < remoteEC.utcDateChanged) {
|
||||
if (!localEC || localEC.utcDateChanged < remoteEC.utcDateChanged || localEC.hash !== remoteEC.hash) {
|
||||
entityChangesService.putEntityChangeWithInstanceId(remoteEC, instanceId);
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,7 @@ const env = require('./env');
|
||||
if (env.isDev()) {
|
||||
const chokidar = require('chokidar');
|
||||
const debounce = require('debounce');
|
||||
const debouncedReloadFrontend = debounce(reloadFrontend, 200);
|
||||
const debouncedReloadFrontend = debounce(() => reloadFrontend("source code change"), 200);
|
||||
chokidar
|
||||
.watch('src/public')
|
||||
.on('add', debouncedReloadFrontend)
|
||||
@ -230,8 +230,8 @@ function syncFailed() {
|
||||
sendMessageToAllClients({ type: 'sync-failed', lastSyncedPush });
|
||||
}
|
||||
|
||||
function reloadFrontend() {
|
||||
sendMessageToAllClients({ type: 'reload-frontend' });
|
||||
function reloadFrontend(reason) {
|
||||
sendMessageToAllClients({ type: 'reload-frontend', reason });
|
||||
}
|
||||
|
||||
function setLastSyncedPush(entityChangeId) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user