mirror of
https://github.com/zadam/trilium.git
synced 2025-06-06 09:58:32 +02:00
Merge branch 'beta'
# Conflicts: # docs/backend_api/BAttachment.html # docs/backend_api/BRevision.html # docs/backend_api/BackendScriptApi.html # docs/backend_api/becca_entities_battachment.js.html # docs/backend_api/becca_entities_bblob.js.html # docs/backend_api/becca_entities_brevision.js.html # docs/frontend_api/FNote.html # docs/frontend_api/FrontendScriptApi.html # docs/frontend_api/entities_fattachment.js.html # docs/frontend_api/entities_fblob.js.html # docs/frontend_api/services_frontend_script_api.js.html # package-lock.json # src/public/app/services/frontend_script_api.js
This commit is contained in:
commit
90fc4b8293
@ -1,11 +1,13 @@
|
||||
//https://prettier.io/docs/en/options.html
|
||||
//https://prettier.io/docs/en/options.html
|
||||
module.exports = {
|
||||
semi: true,
|
||||
trailingComma: 'es5',
|
||||
trailingComma: 'none',
|
||||
singleQuote: true,
|
||||
printWidth: 120,
|
||||
printWidth: 100,
|
||||
tabWidth: 4,
|
||||
// useTabs: false,
|
||||
// bracketSpacing: true,
|
||||
useTabs: false,
|
||||
quoteProps: "as-needed",
|
||||
bracketSpacing: true,
|
||||
arrowParens: "avoid"
|
||||
// htmlWhitespaceSensitivity: 'ignore',
|
||||
};
|
||||
|
@ -1 +0,0 @@
|
||||
module.exports = () => console.log("NOOP, moved to migration 0189");
|
@ -1,4 +0,0 @@
|
||||
-- black theme has been removed, dark is closest replacement
|
||||
UPDATE options SET value = 'dark' WHERE name = 'theme' AND value = 'black';
|
||||
|
||||
UPDATE options SET value = 'light' WHERE name = 'theme' AND value = 'white';
|
@ -1,2 +0,0 @@
|
||||
ALTER TABLE branches DROP COLUMN utcDateCreated;
|
||||
ALTER TABLE options DROP COLUMN utcDateCreated;
|
@ -1,33 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS "mig_entity_changes" (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`entityName` TEXT NOT NULL,
|
||||
`entityId` TEXT NOT NULL,
|
||||
`hash` TEXT NOT NULL,
|
||||
`isErased` INT NOT NULL,
|
||||
`changeId` TEXT NOT NULL,
|
||||
`sourceId` TEXT NOT NULL,
|
||||
`isSynced` INTEGER NOT NULL,
|
||||
`utcDateChanged` TEXT NOT NULL
|
||||
);
|
||||
|
||||
INSERT INTO mig_entity_changes (id, entityName, entityId, hash, isErased, changeId, sourceId, isSynced, utcDateChanged)
|
||||
SELECT id, entityName, entityId, hash, isErased, '', sourceId, isSynced, utcDateChanged FROM entity_changes;
|
||||
|
||||
-- delete duplicates https://github.com/zadam/trilium/issues/2534
|
||||
DELETE FROM mig_entity_changes WHERE isErased = 0 AND id IN (
|
||||
SELECT id FROM mig_entity_changes ec
|
||||
WHERE (
|
||||
SELECT COUNT(*) FROM mig_entity_changes
|
||||
WHERE ec.entityName = mig_entity_changes.entityName
|
||||
AND ec.entityId = mig_entity_changes.entityId
|
||||
) > 1
|
||||
);
|
||||
|
||||
DROP TABLE entity_changes;
|
||||
|
||||
ALTER TABLE mig_entity_changes RENAME TO entity_changes;
|
||||
|
||||
CREATE UNIQUE INDEX `IDX_entityChanges_entityName_entityId` ON "entity_changes" (
|
||||
`entityName`,
|
||||
`entityId`
|
||||
);
|
@ -1,8 +0,0 @@
|
||||
UPDATE branches SET branchId = 'hidden' where branchId = (
|
||||
SELECT branchId FROM branches
|
||||
WHERE parentNoteId = 'root'
|
||||
AND noteId = 'hidden'
|
||||
AND isDeleted = 0
|
||||
ORDER BY utcDateModified
|
||||
LIMIT 1
|
||||
);
|
@ -1 +0,0 @@
|
||||
DELETE FROM options WHERE name = 'username';
|
@ -1,15 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS "etapi_tokens"
|
||||
(
|
||||
etapiTokenId TEXT PRIMARY KEY NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
tokenHash TEXT NOT NULL,
|
||||
utcDateCreated TEXT NOT NULL,
|
||||
utcDateModified TEXT NOT NULL,
|
||||
isDeleted INT NOT NULL DEFAULT 0);
|
||||
|
||||
INSERT INTO etapi_tokens (etapiTokenId, name, tokenHash, utcDateCreated, utcDateModified, isDeleted)
|
||||
SELECT apiTokenId, 'Trilium Sender', token, utcDateCreated, utcDateCreated, isDeleted FROM api_tokens;
|
||||
|
||||
DROP TABLE api_tokens;
|
||||
|
||||
UPDATE entity_changes SET entityName = 'etapi_tokens' WHERE entityName = 'api_tokens';
|
@ -1,10 +0,0 @@
|
||||
module.exports = () => {
|
||||
const sql = require('../../src/services/sql');
|
||||
const crypto = require('crypto');
|
||||
|
||||
for (const {etapiTokenId, token} of sql.getRows("SELECT etapiTokenId, tokenHash AS token FROM etapi_tokens")) {
|
||||
const tokenHash = crypto.createHash('sha256').update(token).digest('base64');
|
||||
|
||||
sql.execute(`UPDATE etapi_tokens SET tokenHash = ? WHERE etapiTokenId = ?`, [tokenHash, etapiTokenId]);
|
||||
}
|
||||
};
|
@ -1,20 +0,0 @@
|
||||
DROP TABLE entity_changes;
|
||||
-- not preserving the data because of https://github.com/zadam/trilium/issues/3447
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "entity_changes" (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`entityName` TEXT NOT NULL,
|
||||
`entityId` TEXT NOT NULL,
|
||||
`hash` TEXT NOT NULL,
|
||||
`isErased` INT NOT NULL,
|
||||
`changeId` TEXT NOT NULL,
|
||||
`componentId` TEXT NOT NULL,
|
||||
`instanceId` TEXT NOT NULL,
|
||||
`isSynced` INTEGER NOT NULL,
|
||||
`utcDateChanged` TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX `IDX_entityChanges_entityName_entityId` ON "entity_changes" (
|
||||
`entityName`,
|
||||
`entityId`
|
||||
);
|
@ -1 +0,0 @@
|
||||
CREATE INDEX `IDX_entity_changes_changeId` ON `entity_changes` (`changeId`);
|
@ -1,15 +0,0 @@
|
||||
const becca = require('../../src/becca/becca');
|
||||
const beccaLoader = require('../../src/becca/becca_loader');
|
||||
const cls = require('../../src/services/cls');
|
||||
|
||||
module.exports = () => {
|
||||
cls.init(() => {
|
||||
beccaLoader.load();
|
||||
|
||||
for (const note of Object.values(becca.notes)) {
|
||||
if (note.hasLabel('calendarRoot')) {
|
||||
note.addLabel('excludeFromNoteMap', "", true);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
@ -1,2 +0,0 @@
|
||||
-- removing potential remnants of recent notes in entity changes, see https://github.com/zadam/trilium/issues/2842
|
||||
DELETE FROM entity_changes WHERE entityName = 'recent_notes';
|
@ -1,2 +0,0 @@
|
||||
UPDATE attributes SET value = replace(value, 'setLabelValue', 'updateLabelValue') WHERE name = 'action' AND type = 'label';
|
||||
UPDATE attributes SET value = replace(value, 'setRelationTarget', 'updateRelationTarget') WHERE name = 'action' AND type = 'label';
|
@ -1 +0,0 @@
|
||||
module.exports = () => console.log("NOOP, increased because of protected notes IV change");
|
@ -1,6 +0,0 @@
|
||||
UPDATE branches SET branchId = '_hidden__search' WHERE parentNoteId = 'hidden' AND noteId = 'search' AND isDeleted = 0;
|
||||
UPDATE branches SET branchId = 'root__globalNoteMap' WHERE parentNoteId = 'singles' AND noteId = 'globalnotemap' AND isDeleted = 0;
|
||||
UPDATE branches SET branchId = '_hidden__sqlConsole' WHERE parentNoteId = 'hidden' AND noteId = 'sqlconsole' AND isDeleted = 0;
|
||||
UPDATE branches SET branchId = 'root__hidden' WHERE parentNoteId = 'root' AND noteId = 'hidden' AND isDeleted = 0;
|
||||
UPDATE branches SET branchId = '_hidden__bulkAction' WHERE parentNoteId = 'hidden' AND noteId = 'bulkaction' AND isDeleted = 0;
|
||||
UPDATE branches SET branchId = '_hidden__share' WHERE parentNoteId = 'root' AND noteId = 'share' AND isDeleted = 0;
|
@ -1,53 +0,0 @@
|
||||
UPDATE notes SET noteId = '_globalNoteMap', title = 'Note Map' WHERE noteId = 'globalnotemap';
|
||||
UPDATE note_contents SET noteId = '_globalNoteMap' WHERE noteId = 'globalnotemap';
|
||||
UPDATE note_revisions SET noteId = '_globalNoteMap' WHERE noteId = 'globalnotemap';
|
||||
UPDATE branches SET noteId = '_globalNoteMap' WHERE noteId = 'globalnotemap';
|
||||
UPDATE branches SET parentNoteId = '_globalNoteMap' WHERE parentNoteId = 'globalnotemap';
|
||||
UPDATE attributes SET noteId = '_globalNoteMap' WHERE noteId = 'globalnotemap';
|
||||
UPDATE attributes SET value = '_globalNoteMap' WHERE type = 'relation' AND value = 'globalnotemap';
|
||||
UPDATE entity_changes SET entityId = '_globalNoteMap' WHERE entityId = 'globalnotemap';
|
||||
|
||||
UPDATE notes SET noteId = '_bulkAction', title = 'Bulk Action' WHERE noteId = 'bulkaction';
|
||||
UPDATE note_contents SET noteId = '_bulkAction' WHERE noteId = 'bulkaction';
|
||||
UPDATE note_revisions SET noteId = '_bulkAction' WHERE noteId = 'bulkaction';
|
||||
UPDATE branches SET parentNoteId = '_bulkAction' WHERE parentNoteId = 'bulkaction';
|
||||
UPDATE branches SET noteId = '_bulkAction' WHERE noteId = 'bulkaction';
|
||||
UPDATE attributes SET noteId = '_bulkAction' WHERE noteId = 'bulkaction';
|
||||
UPDATE attributes SET value = '_bulkAction' WHERE type = 'relation' AND value = 'bulkaction';
|
||||
UPDATE entity_changes SET entityId = '_bulkAction' WHERE entityId = 'bulkaction';
|
||||
|
||||
UPDATE notes SET noteId = '_sqlConsole', title = 'SQL Console History' WHERE noteId = 'sqlconsole';
|
||||
UPDATE note_contents SET noteId = '_sqlConsole' WHERE noteId = 'sqlconsole';
|
||||
UPDATE note_revisions SET noteId = '_sqlConsole' WHERE noteId = 'sqlconsole';
|
||||
UPDATE branches SET noteId = '_sqlConsole' WHERE noteId = 'sqlconsole';
|
||||
UPDATE branches SET parentNoteId = '_sqlConsole' WHERE parentNoteId = 'sqlconsole';
|
||||
UPDATE attributes SET noteId = '_sqlConsole' WHERE noteId = 'sqlconsole';
|
||||
UPDATE attributes SET value = '_sqlConsole' WHERE type = 'relation' AND value = 'sqlconsole';
|
||||
UPDATE entity_changes SET entityId = '_sqlConsole' WHERE entityId = 'sqlconsole';
|
||||
|
||||
UPDATE notes SET noteId = '_hidden', title = 'Hidden Notes' WHERE noteId = 'hidden';
|
||||
UPDATE note_contents SET noteId = '_hidden' WHERE noteId = 'hidden';
|
||||
UPDATE note_revisions SET noteId = '_hidden' WHERE noteId = 'hidden';
|
||||
UPDATE branches SET noteId = '_hidden', prefix = NULL WHERE noteId = 'hidden';
|
||||
UPDATE branches SET parentNoteId = '_hidden' WHERE parentNoteId = 'hidden';
|
||||
UPDATE attributes SET noteId = '_hidden' WHERE noteId = 'hidden';
|
||||
UPDATE attributes SET value = '_hidden' WHERE type = 'relation' AND value = 'hidden';
|
||||
UPDATE entity_changes SET entityId = '_hidden' WHERE entityId = 'hidden';
|
||||
|
||||
UPDATE notes SET noteId = '_search', title = 'Search History' WHERE noteId = 'search';
|
||||
UPDATE note_contents SET noteId = '_search' WHERE noteId = 'search';
|
||||
UPDATE note_revisions SET noteId = '_search' WHERE noteId = 'search';
|
||||
UPDATE branches SET noteId = '_search' WHERE noteId = 'search';
|
||||
UPDATE branches SET parentNoteId = '_search' WHERE parentNoteId = 'search';
|
||||
UPDATE attributes SET noteId = '_search' WHERE noteId = 'search';
|
||||
UPDATE attributes SET value = '_search' WHERE type = 'relation' AND value = 'search';
|
||||
UPDATE entity_changes SET entityId = '_search' WHERE entityId = 'search';
|
||||
|
||||
UPDATE notes SET noteId = '_share', title = 'Shared Notes' WHERE noteId = 'share';
|
||||
UPDATE note_contents SET noteId = '_share' WHERE noteId = 'share';
|
||||
UPDATE note_revisions SET noteId = '_share' WHERE noteId = 'share';
|
||||
UPDATE branches SET noteId = '_share' WHERE noteId = 'share';
|
||||
UPDATE branches SET parentNoteId = '_share' WHERE parentNoteId = 'share';
|
||||
UPDATE attributes SET noteId = '_share' WHERE noteId = 'share';
|
||||
UPDATE attributes SET value = '_share' WHERE type = 'relation' AND value = 'share';
|
||||
UPDATE entity_changes SET entityId = '_share' WHERE entityId = 'share';
|
@ -1,12 +0,0 @@
|
||||
module.exports = () => {
|
||||
const hiddenSubtreeService = require('../../src/services/hidden_subtree');
|
||||
const cls = require("../../src/services/cls");
|
||||
const beccaLoader = require("../../src/becca/becca_loader");
|
||||
|
||||
cls.init(() => {
|
||||
beccaLoader.load();
|
||||
// make sure the hidden subtree exists since the subsequent migrations we will move some existing notes into it (share...)
|
||||
// in previous releases hidden subtree was created lazily
|
||||
hiddenSubtreeService.checkHiddenSubtree(true);
|
||||
});
|
||||
};
|
@ -1,2 +0,0 @@
|
||||
DELETE FROM branches WHERE noteId = '_share' AND parentNoteId != 'root' AND parentNoteId != '_hidden'; -- delete all other branches of _share if any
|
||||
UPDATE branches SET parentNoteId = '_hidden' WHERE noteId = '_share';
|
@ -1,2 +0,0 @@
|
||||
DELETE FROM branches WHERE noteId = '_globalNoteMap' AND parentNoteId != 'singles' AND parentNoteId != '_hidden'; -- make sure there are no clones which would fail at the next line
|
||||
UPDATE branches SET parentNoteId = '_hidden' WHERE noteId = '_globalNoteMap';
|
@ -1,6 +0,0 @@
|
||||
DELETE FROM branches WHERE noteId = 'singles';
|
||||
DELETE FROM notes WHERE noteId = 'singles';
|
||||
DELETE FROM note_contents WHERE noteId = 'singles';
|
||||
DELETE FROM note_revisions WHERE noteId = 'singles';
|
||||
DELETE FROM attributes WHERE noteId = 'singles';
|
||||
DELETE FROM entity_changes WHERE entityId = 'singles';
|
@ -1,21 +0,0 @@
|
||||
module.exports = () => {
|
||||
const cls = require("../../src/services/cls");
|
||||
const cloningService = require("../../src/services/cloning");
|
||||
const beccaLoader = require("../../src/becca/becca_loader");
|
||||
const becca = require("../../src/becca/becca");
|
||||
|
||||
cls.init(() => {
|
||||
beccaLoader.load();
|
||||
|
||||
for (const attr of becca.findAttributes('label','bookmarked')) {
|
||||
cloningService.toggleNoteInParent(true, attr.noteId, '_lbBookmarks');
|
||||
|
||||
attr.markAsDeleted("0204__migrate_bookmarks_to_clones");
|
||||
}
|
||||
|
||||
// bookmarkFolder used to work in 0.57 without the bookmarked label
|
||||
for (const attr of becca.findAttributes('label','bookmarkFolder')) {
|
||||
cloningService.toggleNoteInParent(true, attr.noteId, '_lbBookmarks');
|
||||
}
|
||||
});
|
||||
};
|
@ -1,3 +0,0 @@
|
||||
UPDATE notes SET type = 'relationMap' WHERE type = 'relation-map';
|
||||
UPDATE notes SET type = 'noteMap' WHERE type = 'note-map';
|
||||
UPDATE notes SET type = 'webView' WHERE type = 'web-view';
|
@ -1,33 +0,0 @@
|
||||
// the history was previously not exposed and the fact they were not cleaned up is rather a side-effect than an intention
|
||||
|
||||
module.exports = () => {
|
||||
const cls = require("../../src/services/cls");
|
||||
const beccaLoader = require("../../src/becca/becca_loader");
|
||||
const becca = require("../../src/becca/becca");
|
||||
|
||||
cls.init(() => {
|
||||
beccaLoader.load();
|
||||
|
||||
// deleting just branches because they might be cloned (and therefore saved) also outside of the hidden subtree
|
||||
|
||||
const searchRoot = becca.getNote('_search');
|
||||
|
||||
for (const searchBranch of searchRoot.getChildBranches()) {
|
||||
const searchNote = searchBranch.getNote();
|
||||
|
||||
if (searchNote.type === 'search') {
|
||||
searchBranch.deleteBranch('0206__delete_search_and_sql_console_history');
|
||||
}
|
||||
}
|
||||
|
||||
const sqlConsoleRoot = becca.getNote('_sqlConsole');
|
||||
|
||||
for (const sqlConsoleBranch of sqlConsoleRoot.getChildBranches()) {
|
||||
const sqlConsoleNote = sqlConsoleBranch.getNote();
|
||||
|
||||
if (sqlConsoleNote.type === 'code' && sqlConsoleNote.mime === 'text/x-sqlite;schema=trilium') {
|
||||
sqlConsoleBranch.deleteBranch('0206__delete_search_and_sql_console_history');
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
@ -1,2 +0,0 @@
|
||||
UPDATE notes SET title = 'SQL Console History' WHERE noteId = '_sqlConsole';
|
||||
UPDATE notes SET title = 'Search History' WHERE noteId = '_search';
|
@ -1,13 +0,0 @@
|
||||
module.exports = () => {
|
||||
const cls = require("../../src/services/cls");
|
||||
const beccaLoader = require("../../src/becca/becca_loader");
|
||||
const becca = require("../../src/becca/becca");
|
||||
|
||||
cls.init(() => {
|
||||
beccaLoader.load();
|
||||
|
||||
for (const label of becca.getNote('_hidden').getLabels('archived')) {
|
||||
label.markAsDeleted('0208__remove_archived_from_hidden');
|
||||
}
|
||||
});
|
||||
};
|
@ -1,5 +0,0 @@
|
||||
UPDATE attributes SET name = 'workspaceInbox' WHERE type = 'label' AND name = 'hoistedInbox';
|
||||
UPDATE entity_changes SET entityId = 'workspaceInbox' WHERE entityName = 'attributes' AND entityId = 'hoistedInbox';
|
||||
|
||||
UPDATE attributes SET name = 'workspaceSearchHome' WHERE type = 'label' AND name = 'hoistedSearchHome';
|
||||
UPDATE entity_changes SET entityId = 'workspaceSearchHome' WHERE entityName = 'attributes' AND entityId = 'hoistedSearchHome';
|
@ -1,24 +0,0 @@
|
||||
module.exports = async () => {
|
||||
const cls = require("../../src/services/cls");
|
||||
const beccaLoader = require("../../src/becca/becca_loader");
|
||||
const log = require("../../src/services/log");
|
||||
const consistencyChecks = require("../../src/services/consistency_checks");
|
||||
const eraseService = require("../../src/services/erase");
|
||||
|
||||
await cls.init(async () => {
|
||||
// precaution for the 0211 migration
|
||||
eraseService.eraseDeletedNotesNow();
|
||||
|
||||
beccaLoader.load();
|
||||
|
||||
try {
|
||||
// precaution before running 211 which might produce unique constraint problems if the DB was not consistent
|
||||
consistencyChecks.runOnDemandChecksWithoutExclusiveLock(true);
|
||||
}
|
||||
catch (e) {
|
||||
// consistency checks might start failing in the future if there's some incompatible migration down the road
|
||||
// we can optimistically assume the DB is consistent and still continue
|
||||
log.error(`Consistency checks failed in migration 0210: ${e.message} ${e.stack}`);
|
||||
}
|
||||
});
|
||||
};
|
@ -1,12 +0,0 @@
|
||||
-- case based on isDeleted is needed, otherwise 2 branches (1 deleted, 1 not) might get the same ID
|
||||
UPDATE entity_changes SET entityId = COALESCE((
|
||||
SELECT
|
||||
CASE isDeleted
|
||||
WHEN 0 THEN parentNoteId || '_' || noteId
|
||||
WHEN 1 THEN branchId
|
||||
END
|
||||
FROM branches WHERE branchId = entityId
|
||||
), entityId)
|
||||
WHERE entityName = 'branches' AND isErased = 0;
|
||||
|
||||
UPDATE branches SET branchId = parentNoteId || '_' || noteId WHERE isDeleted = 0;
|
@ -1,27 +0,0 @@
|
||||
module.exports = () => {
|
||||
const cls = require("../../src/services/cls");
|
||||
const beccaLoader = require("../../src/becca/becca_loader");
|
||||
const becca = require("../../src/becca/becca");
|
||||
const log = require("../../src/services/log");
|
||||
|
||||
cls.init(() => {
|
||||
beccaLoader.load();
|
||||
|
||||
const hidden = becca.getNote("_hidden");
|
||||
|
||||
if (!hidden) {
|
||||
log.info("MIGRATION 212: no _hidden note, skipping.");
|
||||
return;
|
||||
}
|
||||
|
||||
for (const noteId of hidden.getSubtreeNoteIds({includeHidden: true})) {
|
||||
if (noteId.startsWith("_")) { // is "named" note
|
||||
const note = becca.getNote(noteId);
|
||||
|
||||
for (const attr of note.getOwnedAttributes().slice()) {
|
||||
attr.markAsDeleted("0212__delete_all_attributes_of_named_notes");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
@ -1,48 +0,0 @@
|
||||
module.exports = () => {
|
||||
const beccaLoader = require("../../src/becca/becca_loader");
|
||||
const becca = require("../../src/becca/becca");
|
||||
const cls = require("../../src/services/cls");
|
||||
const log = require("../../src/services/log");
|
||||
|
||||
cls.init(() => {
|
||||
beccaLoader.load();
|
||||
|
||||
for (const note of Object.values(becca.notes)) {
|
||||
try {
|
||||
if (!note.isJavaScript()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!note.mime?.endsWith('env=frontend') && !note.mime?.endsWith('env=backend')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const origContent = note.getContent().toString();
|
||||
const fixedContent = origContent
|
||||
.replaceAll("runOnServer", "runOnBackend")
|
||||
.replaceAll("api.refreshTree()", "")
|
||||
.replaceAll("addTextToActiveTabEditor", "addTextToActiveContextEditor")
|
||||
.replaceAll("getActiveTabNote", "getActiveContextNote")
|
||||
.replaceAll("getActiveTabTextEditor", "getActiveContextTextEditor")
|
||||
.replaceAll("getActiveTabNotePath", "getActiveContextNotePath")
|
||||
.replaceAll("getDateNote", "getDayNote")
|
||||
.replaceAll("utils.unescapeHtml", "unescapeHtml")
|
||||
.replaceAll("sortNotesByTitle", "sortNotes")
|
||||
.replaceAll("CollapsibleWidget", "RightPanelWidget")
|
||||
.replaceAll("TabAwareWidget", "NoteContextAwareWidget")
|
||||
.replaceAll("TabCachingWidget", "NoteContextAwareWidget")
|
||||
.replaceAll("NoteContextCachingWidget", "NoteContextAwareWidget");
|
||||
|
||||
if (origContent !== fixedContent) {
|
||||
log.info(`Replacing legacy API calls for note '${note.noteId}'`);
|
||||
|
||||
note.saveNoteRevision();
|
||||
note.setContent(fixedContent);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
log.error(`Error during migration to 213 for note '${note.noteId}': ${e.message} ${e.stack}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
@ -1 +0,0 @@
|
||||
UPDATE branches SET notePosition = notePosition - 999899999 WHERE parentNoteId = 'root' AND notePosition > 999999999;
|
@ -586,6 +586,48 @@ function BackendScriptApi(currentNote, apiParams) {
|
||||
*/
|
||||
this.exportSubtreeToZipFile = async (noteId, format, zipFilePath) => await exportService.exportToZipFile(noteId, format, zipFilePath);
|
||||
|
||||
/**
|
||||
* Executes given anonymous function on the frontend(s).
|
||||
* Internally this serializes the anonymous function into string and sends it to frontend(s) via WebSocket.
|
||||
* Note that there can be multiple connected frontend instances (e.g. in different tabs). In such case, all
|
||||
* instances execute the given function.
|
||||
*
|
||||
* @method
|
||||
* @param {string} script - script to be executed on the frontend
|
||||
* @param {Array.<?>} params - list of parameters to the anonymous function to be sent to frontend
|
||||
* @returns {undefined} - no return value is provided.
|
||||
*/
|
||||
this.runOnFrontend = async (script, params = []) => {
|
||||
if (typeof script === "function") {
|
||||
script = script.toString();
|
||||
}
|
||||
|
||||
ws.sendMessageToAllClients({
|
||||
type: 'execute-script',
|
||||
script: script,
|
||||
params: prepareParams(params),
|
||||
startNoteId: this.startNote.noteId,
|
||||
currentNoteId: this.currentNote.noteId,
|
||||
originEntityName: "notes", // currently there's no other entity on the frontend which can trigger event
|
||||
originEntityId: this.originEntity?.noteId || null
|
||||
});
|
||||
|
||||
function prepareParams(params) {
|
||||
if (!params) {
|
||||
return params;
|
||||
}
|
||||
|
||||
return params.map(p => {
|
||||
if (typeof p === "function") {
|
||||
return `!@#Function: ${p.toString()}`;
|
||||
}
|
||||
else {
|
||||
return p;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This object contains "at your risk" and "no BC guarantees" objects for advanced use cases.
|
||||
*
|
||||
|
@ -167,10 +167,9 @@ class FNote {
|
||||
}
|
||||
|
||||
async getContent() {
|
||||
// we're not caching content since these objects are in froca and as such pretty long-lived
|
||||
const note = await server.get(`notes/${this.noteId}`);
|
||||
const blob = await this.getBlob();
|
||||
|
||||
return note.content;
|
||||
return blob?.content;
|
||||
}
|
||||
|
||||
async getJsonContent() {
|
||||
|
@ -5,8 +5,8 @@
|
||||
}
|
||||
|
||||
/*
|
||||
* CKEditor 5 (v38.1.1) content styles.
|
||||
* Generated on Thu, 27 Jul 2023 08:16:09 GMT.
|
||||
* CKEditor 5 (v39.0.1) content styles.
|
||||
* Generated on Thu, 10 Aug 2023 09:21:07 GMT.
|
||||
* For more information, check out https://ckeditor.com/docs/ckeditor5/latest/installation/advanced/content-styles.html
|
||||
*/
|
||||
|
||||
@ -15,8 +15,8 @@
|
||||
--ck-color-image-caption-text: hsl(0, 0%, 20%);
|
||||
--ck-color-mention-background: hsla(341, 100%, 30%, 0.1);
|
||||
--ck-color-mention-text: hsl(341, 100%, 30%);
|
||||
--ck-color-table-caption-background: hsl(0, 0%, 97%);
|
||||
--ck-color-table-caption-text: hsl(0, 0%, 20%);
|
||||
--ck-color-selector-caption-background: hsl(0, 0%, 97%);
|
||||
--ck-color-selector-caption-text: hsl(0, 0%, 20%);
|
||||
--ck-highlight-marker-blue: hsl(201, 97%, 72%);
|
||||
--ck-highlight-marker-green: hsl(120, 93%, 68%);
|
||||
--ck-highlight-marker-pink: hsl(345, 96%, 73%);
|
||||
@ -42,18 +42,6 @@
|
||||
overflow-wrap: break-word;
|
||||
position: relative;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-table/theme/tablecaption.css */
|
||||
.ck-content .table > figcaption {
|
||||
display: table-caption;
|
||||
caption-side: top;
|
||||
word-break: break-word;
|
||||
text-align: center;
|
||||
color: var(--ck-color-table-caption-text);
|
||||
background-color: var(--ck-color-table-caption-background);
|
||||
padding: .6em;
|
||||
font-size: .75em;
|
||||
outline-offset: -1px;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-table/theme/table.css */
|
||||
.ck-content .table {
|
||||
margin: 0.9em auto;
|
||||
@ -87,6 +75,18 @@
|
||||
.ck-content[dir="ltr"] .table th {
|
||||
text-align: left;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-table/theme/tablecaption.css */
|
||||
.ck-content .table > figcaption {
|
||||
display: table-caption;
|
||||
caption-side: top;
|
||||
word-break: break-word;
|
||||
text-align: center;
|
||||
color: var(--ck-color-selector-caption-text);
|
||||
background-color: var(--ck-color-selector-caption-background);
|
||||
padding: .6em;
|
||||
font-size: .75em;
|
||||
outline-offset: -1px;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */
|
||||
.ck-content .page-break {
|
||||
position: relative;
|
||||
@ -382,12 +382,6 @@
|
||||
.ck-content .image-inline.image-style-align-right {
|
||||
margin-left: var(--ck-inline-image-style-spacing);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-basic-styles/theme/code.css */
|
||||
.ck-content code {
|
||||
background-color: hsla(0, 0%, 78%, 0.3);
|
||||
padding: .15em;
|
||||
border-radius: 2px;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-block-quote/theme/blockquote.css */
|
||||
.ck-content blockquote {
|
||||
overflow: hidden;
|
||||
@ -403,6 +397,12 @@
|
||||
border-left: 0;
|
||||
border-right: solid 5px hsl(0, 0%, 80%);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-basic-styles/theme/code.css */
|
||||
.ck-content code {
|
||||
background-color: hsla(0, 0%, 78%, 0.3);
|
||||
padding: .15em;
|
||||
border-radius: 2px;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-font/theme/fontsize.css */
|
||||
.ck-content .text-tiny {
|
||||
font-size: .7em;
|
||||
|
2
libraries/ckeditor/ckeditor.js
vendored
2
libraries/ckeditor/ckeditor.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
379
libraries/mermaid.min.js
vendored
379
libraries/mermaid.min.js
vendored
File diff suppressed because one or more lines are too long
32
package.json
32
package.json
@ -2,7 +2,7 @@
|
||||
"name": "trilium",
|
||||
"productName": "Trilium Notes",
|
||||
"description": "Trilium Notes",
|
||||
"version": "0.61.4-beta",
|
||||
"version": "0.61.5-beta",
|
||||
"license": "AGPL-3.0-only",
|
||||
"main": "electron.js",
|
||||
"bin": {
|
||||
@ -32,11 +32,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "6.0.4",
|
||||
"@electron/remote": "2.0.10",
|
||||
"@excalidraw/excalidraw": "0.15.2",
|
||||
"@electron/remote": "2.0.11",
|
||||
"@excalidraw/excalidraw": "0.15.3",
|
||||
"archiver": "5.3.1",
|
||||
"async-mutex": "0.4.0",
|
||||
"axios": "1.4.0",
|
||||
"axios": "1.5.0",
|
||||
"better-sqlite3": "8.4.0",
|
||||
"chokidar": "3.5.3",
|
||||
"cls-hooked": "4.2.2",
|
||||
@ -53,14 +53,14 @@
|
||||
"escape-html": "1.0.3",
|
||||
"express": "4.18.2",
|
||||
"express-partial-content": "1.0.2",
|
||||
"express-rate-limit": "6.9.0",
|
||||
"express-rate-limit": "6.10.0",
|
||||
"express-session": "1.17.3",
|
||||
"fs-extra": "11.1.1",
|
||||
"helmet": "7.0.0",
|
||||
"html": "1.0.0",
|
||||
"html2plaintext": "2.1.4",
|
||||
"http-proxy-agent": "7.0.0",
|
||||
"https-proxy-agent": "7.0.1",
|
||||
"https-proxy-agent": "7.0.2",
|
||||
"image-type": "4.1.0",
|
||||
"ini": "3.0.1",
|
||||
"is-animated": "2.0.2",
|
||||
@ -68,10 +68,10 @@
|
||||
"jimp": "0.22.10",
|
||||
"joplin-turndown-plugin-gfm": "1.0.12",
|
||||
"jsdom": "22.1.0",
|
||||
"marked": "7.0.2",
|
||||
"marked": "7.0.5",
|
||||
"mime-types": "2.1.35",
|
||||
"multer": "1.4.5-lts.1",
|
||||
"node-abi": "3.45.0",
|
||||
"node-abi": "3.47.0",
|
||||
"normalize-strings": "1.1.1",
|
||||
"open": "8.4.1",
|
||||
"rand-token": "1.0.1",
|
||||
@ -97,14 +97,14 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"cross-env": "7.0.3",
|
||||
"electron": "25.5.0",
|
||||
"electron-builder": "24.6.3",
|
||||
"electron-packager": "17.1.1",
|
||||
"electron": "25.8.0",
|
||||
"electron-builder": "24.6.4",
|
||||
"electron-packager": "17.1.2",
|
||||
"electron-rebuild": "3.2.9",
|
||||
"eslint": "8.46.0",
|
||||
"eslint": "8.48.0",
|
||||
"eslint-config-airbnb-base": "15.0.0",
|
||||
"eslint-config-prettier": "9.0.0",
|
||||
"eslint-plugin-import": "2.28.0",
|
||||
"eslint-plugin-import": "2.28.1",
|
||||
"eslint-plugin-jsonc": "2.9.0",
|
||||
"eslint-plugin-prettier": "5.0.0",
|
||||
"esm": "3.2.25",
|
||||
@ -112,16 +112,16 @@
|
||||
"jasmine": "5.1.0",
|
||||
"jsdoc": "4.0.2",
|
||||
"jsonc-eslint-parser": "2.3.0",
|
||||
"lint-staged": "13.2.3",
|
||||
"lint-staged": "14.0.1",
|
||||
"lorem-ipsum": "2.0.8",
|
||||
"nodemon": "3.0.1",
|
||||
"prettier": "3.0.1",
|
||||
"prettier": "3.0.3",
|
||||
"rcedit": "3.1.0",
|
||||
"webpack": "5.88.2",
|
||||
"webpack-cli": "5.1.4"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"electron-installer-debian": "3.1.0"
|
||||
"electron-installer-debian": "3.2.0"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.js": "eslint --cache --fix"
|
||||
|
@ -4,6 +4,9 @@ describe("Lexer fulltext", () => {
|
||||
it("simple lexing", () => {
|
||||
expect(lex("hello world").fulltextTokens.map(t => t.token))
|
||||
.toEqual(["hello", "world"]);
|
||||
|
||||
expect(lex("hello, world").fulltextTokens.map(t => t.token))
|
||||
.toEqual(["hello", "world"]);
|
||||
});
|
||||
|
||||
it("use quotes to keep words together", () => {
|
||||
@ -147,6 +150,11 @@ describe("Lexer expression", () => {
|
||||
expect(lex(`# not(#capital) and note.noteId != "root"`).expressionTokens.map(t => t.token))
|
||||
.toEqual(["#", "not", "(", "#capital", ")", "and", "note", ".", "noteid", "!=", "root"]);
|
||||
});
|
||||
|
||||
it("order by multiple labels", () => {
|
||||
expect(lex(`# orderby #a,#b`).expressionTokens.map(t => t.token))
|
||||
.toEqual(["#", "orderby", "#a", ",", "#b"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Lexer invalid queries and edge cases", () => {
|
||||
|
@ -36,9 +36,9 @@ describe("Parser", () => {
|
||||
|
||||
expect(rootExp.constructor.name).toEqual("AndExp");
|
||||
expect(rootExp.subExpressions[0].constructor.name).toEqual("PropertyComparisonExp");
|
||||
expect(rootExp.subExpressions[1].constructor.name).toEqual("OrExp");
|
||||
expect(rootExp.subExpressions[1].subExpressions[0].constructor.name).toEqual("NoteFlatTextExp");
|
||||
expect(rootExp.subExpressions[1].subExpressions[0].tokens).toEqual(["hello", "hi"]);
|
||||
expect(rootExp.subExpressions[2].constructor.name).toEqual("OrExp");
|
||||
expect(rootExp.subExpressions[2].subExpressions[0].constructor.name).toEqual("NoteFlatTextExp");
|
||||
expect(rootExp.subExpressions[2].subExpressions[0].tokens).toEqual(["hello", "hi"]);
|
||||
});
|
||||
|
||||
it("fulltext parser with content", () => {
|
||||
@ -51,9 +51,9 @@ describe("Parser", () => {
|
||||
expect(rootExp.constructor.name).toEqual("AndExp");
|
||||
assertIsArchived(rootExp.subExpressions[0]);
|
||||
|
||||
expect(rootExp.subExpressions[1].constructor.name).toEqual("OrExp");
|
||||
expect(rootExp.subExpressions[2].constructor.name).toEqual("OrExp");
|
||||
|
||||
const subs = rootExp.subExpressions[1].subExpressions;
|
||||
const subs = rootExp.subExpressions[2].subExpressions;
|
||||
|
||||
expect(subs[0].constructor.name).toEqual("NoteFlatTextExp");
|
||||
expect(subs[0].tokens).toEqual(["hello", "hi"]);
|
||||
@ -71,10 +71,10 @@ describe("Parser", () => {
|
||||
|
||||
expect(rootExp.constructor.name).toEqual("AndExp");
|
||||
assertIsArchived(rootExp.subExpressions[0]);
|
||||
expect(rootExp.subExpressions[1].constructor.name).toEqual("LabelComparisonExp");
|
||||
expect(rootExp.subExpressions[1].attributeType).toEqual("label");
|
||||
expect(rootExp.subExpressions[1].attributeName).toEqual("mylabel");
|
||||
expect(rootExp.subExpressions[1].comparator).toBeTruthy();
|
||||
expect(rootExp.subExpressions[2].constructor.name).toEqual("LabelComparisonExp");
|
||||
expect(rootExp.subExpressions[2].attributeType).toEqual("label");
|
||||
expect(rootExp.subExpressions[2].attributeName).toEqual("mylabel");
|
||||
expect(rootExp.subExpressions[2].comparator).toBeTruthy();
|
||||
});
|
||||
|
||||
it("simple attribute negation", () => {
|
||||
@ -86,10 +86,10 @@ describe("Parser", () => {
|
||||
|
||||
expect(rootExp.constructor.name).toEqual("AndExp");
|
||||
assertIsArchived(rootExp.subExpressions[0]);
|
||||
expect(rootExp.subExpressions[1].constructor.name).toEqual("NotExp");
|
||||
expect(rootExp.subExpressions[1].subExpression.constructor.name).toEqual("AttributeExistsExp");
|
||||
expect(rootExp.subExpressions[1].subExpression.attributeType).toEqual("label");
|
||||
expect(rootExp.subExpressions[1].subExpression.attributeName).toEqual("mylabel");
|
||||
expect(rootExp.subExpressions[2].constructor.name).toEqual("NotExp");
|
||||
expect(rootExp.subExpressions[2].subExpression.constructor.name).toEqual("AttributeExistsExp");
|
||||
expect(rootExp.subExpressions[2].subExpression.attributeType).toEqual("label");
|
||||
expect(rootExp.subExpressions[2].subExpression.attributeName).toEqual("mylabel");
|
||||
|
||||
rootExp = parse({
|
||||
fulltextTokens: [],
|
||||
@ -99,10 +99,10 @@ describe("Parser", () => {
|
||||
|
||||
expect(rootExp.constructor.name).toEqual("AndExp");
|
||||
assertIsArchived(rootExp.subExpressions[0]);
|
||||
expect(rootExp.subExpressions[1].constructor.name).toEqual("NotExp");
|
||||
expect(rootExp.subExpressions[1].subExpression.constructor.name).toEqual("AttributeExistsExp");
|
||||
expect(rootExp.subExpressions[1].subExpression.attributeType).toEqual("relation");
|
||||
expect(rootExp.subExpressions[1].subExpression.attributeName).toEqual("myrelation");
|
||||
expect(rootExp.subExpressions[2].constructor.name).toEqual("NotExp");
|
||||
expect(rootExp.subExpressions[2].subExpression.constructor.name).toEqual("AttributeExistsExp");
|
||||
expect(rootExp.subExpressions[2].subExpression.attributeType).toEqual("relation");
|
||||
expect(rootExp.subExpressions[2].subExpression.attributeName).toEqual("myrelation");
|
||||
});
|
||||
|
||||
it("simple label AND", () => {
|
||||
@ -115,8 +115,8 @@ describe("Parser", () => {
|
||||
expect(rootExp.constructor.name).toEqual("AndExp");
|
||||
assertIsArchived(rootExp.subExpressions[0]);
|
||||
|
||||
expect(rootExp.subExpressions[1].constructor.name).toEqual("AndExp");
|
||||
const [firstSub, secondSub] = rootExp.subExpressions[1].subExpressions;
|
||||
expect(rootExp.subExpressions[2].constructor.name).toEqual("AndExp");
|
||||
const [firstSub, secondSub] = rootExp.subExpressions[2].subExpressions;
|
||||
|
||||
expect(firstSub.constructor.name).toEqual("LabelComparisonExp");
|
||||
expect(firstSub.attributeName).toEqual("first");
|
||||
@ -135,8 +135,8 @@ describe("Parser", () => {
|
||||
expect(rootExp.constructor.name).toEqual("AndExp");
|
||||
assertIsArchived(rootExp.subExpressions[0]);
|
||||
|
||||
expect(rootExp.subExpressions[1].constructor.name).toEqual("AndExp");
|
||||
const [firstSub, secondSub] = rootExp.subExpressions[1].subExpressions;
|
||||
expect(rootExp.subExpressions[2].constructor.name).toEqual("AndExp");
|
||||
const [firstSub, secondSub] = rootExp.subExpressions[2].subExpressions;
|
||||
|
||||
expect(firstSub.constructor.name).toEqual("LabelComparisonExp");
|
||||
expect(firstSub.attributeName).toEqual("first");
|
||||
@ -155,8 +155,8 @@ describe("Parser", () => {
|
||||
expect(rootExp.constructor.name).toEqual("AndExp");
|
||||
assertIsArchived(rootExp.subExpressions[0]);
|
||||
|
||||
expect(rootExp.subExpressions[1].constructor.name).toEqual("OrExp");
|
||||
const [firstSub, secondSub] = rootExp.subExpressions[1].subExpressions;
|
||||
expect(rootExp.subExpressions[2].constructor.name).toEqual("OrExp");
|
||||
const [firstSub, secondSub] = rootExp.subExpressions[2].subExpressions;
|
||||
|
||||
expect(firstSub.constructor.name).toEqual("LabelComparisonExp");
|
||||
expect(firstSub.attributeName).toEqual("first");
|
||||
@ -173,17 +173,17 @@ describe("Parser", () => {
|
||||
});
|
||||
|
||||
expect(rootExp.constructor.name).toEqual("AndExp");
|
||||
const [firstSub, secondSub, thirdSub] = rootExp.subExpressions;
|
||||
const [firstSub, secondSub, thirdSub, fourth] = rootExp.subExpressions;
|
||||
|
||||
expect(firstSub.constructor.name).toEqual("PropertyComparisonExp");
|
||||
expect(firstSub.propertyName).toEqual('isArchived');
|
||||
|
||||
expect(secondSub.constructor.name).toEqual("OrExp");
|
||||
expect(secondSub.subExpressions[0].constructor.name).toEqual("NoteFlatTextExp");
|
||||
expect(secondSub.subExpressions[0].tokens).toEqual(["hello"]);
|
||||
expect(thirdSub.constructor.name).toEqual("OrExp");
|
||||
expect(thirdSub.subExpressions[0].constructor.name).toEqual("NoteFlatTextExp");
|
||||
expect(thirdSub.subExpressions[0].tokens).toEqual(["hello"]);
|
||||
|
||||
expect(thirdSub.constructor.name).toEqual("LabelComparisonExp");
|
||||
expect(thirdSub.attributeName).toEqual("mylabel");
|
||||
expect(fourth.constructor.name).toEqual("LabelComparisonExp");
|
||||
expect(fourth.attributeName).toEqual("mylabel");
|
||||
});
|
||||
|
||||
it("label sub-expression", () => {
|
||||
@ -196,8 +196,8 @@ describe("Parser", () => {
|
||||
expect(rootExp.constructor.name).toEqual("AndExp");
|
||||
assertIsArchived(rootExp.subExpressions[0]);
|
||||
|
||||
expect(rootExp.subExpressions[1].constructor.name).toEqual("OrExp");
|
||||
const [firstSub, secondSub] = rootExp.subExpressions[1].subExpressions;
|
||||
expect(rootExp.subExpressions[2].constructor.name).toEqual("OrExp");
|
||||
const [firstSub, secondSub] = rootExp.subExpressions[2].subExpressions;
|
||||
|
||||
expect(firstSub.constructor.name).toEqual("LabelComparisonExp");
|
||||
expect(firstSub.attributeName).toEqual("first");
|
||||
@ -222,8 +222,8 @@ describe("Parser", () => {
|
||||
expect(rootExp.constructor.name).toEqual("AndExp");
|
||||
assertIsArchived(rootExp.subExpressions[0]);
|
||||
|
||||
expect(rootExp.subExpressions[1].constructor.name).toEqual("AndExp");
|
||||
const [firstSub, secondSub, thirdSub] = rootExp.subExpressions[1].subExpressions;
|
||||
expect(rootExp.subExpressions[2].constructor.name).toEqual("AndExp");
|
||||
const [firstSub, secondSub, thirdSub] = rootExp.subExpressions[2].subExpressions;
|
||||
|
||||
expect(firstSub.constructor.name).toEqual("AttributeExistsExp");
|
||||
expect(firstSub.attributeName).toEqual("first");
|
||||
@ -290,10 +290,11 @@ describe("Invalid expressions", () => {
|
||||
|
||||
expect(rootExp.constructor.name).toEqual("AndExp");
|
||||
assertIsArchived(rootExp.subExpressions[0]);
|
||||
expect(rootExp.subExpressions[1].constructor.name).toEqual("LabelComparisonExp");
|
||||
expect(rootExp.subExpressions[1].attributeType).toEqual("label");
|
||||
expect(rootExp.subExpressions[1].attributeName).toEqual("first");
|
||||
expect(rootExp.subExpressions[1].comparator).toBeTruthy();
|
||||
|
||||
expect(rootExp.subExpressions[2].constructor.name).toEqual("LabelComparisonExp");
|
||||
expect(rootExp.subExpressions[2].attributeType).toEqual("label");
|
||||
expect(rootExp.subExpressions[2].attributeName).toEqual("first");
|
||||
expect(rootExp.subExpressions[2].comparator).toBeTruthy();
|
||||
});
|
||||
|
||||
it("searching by relation without note property", () => {
|
||||
|
@ -802,6 +802,12 @@ components:
|
||||
branchId:
|
||||
$ref: '#/components/schemas/EntityId'
|
||||
description: DON'T specify unless you want to force a specific branchId
|
||||
dateCreated:
|
||||
$ref: '#/components/schemas/LocalDateTime'
|
||||
description: Local timestap of the note creation. Specify only if you want to override the default (current datetime in the current timezone/offset).
|
||||
utcDateCreated:
|
||||
$ref: '#/components/schemas/UtcDateTime'
|
||||
description: UTC timestap of the note creation. Specify only if you want to override the default (current datetime).
|
||||
Note:
|
||||
type: object
|
||||
properties:
|
||||
@ -838,13 +844,11 @@ components:
|
||||
readOnly: true
|
||||
dateCreated:
|
||||
$ref: '#/components/schemas/LocalDateTime'
|
||||
readOnly: true
|
||||
dateModified:
|
||||
$ref: '#/components/schemas/LocalDateTime'
|
||||
readOnly: true
|
||||
utcDateCreated:
|
||||
$ref: '#/components/schemas/UtcDateTime'
|
||||
readOnly: true
|
||||
utcDateModified:
|
||||
$ref: '#/components/schemas/UtcDateTime'
|
||||
readOnly: true
|
||||
@ -937,11 +941,11 @@ components:
|
||||
LocalDateTime:
|
||||
type: string
|
||||
pattern: '[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}[\+\-][0-9]{4}'
|
||||
example: 2021-12-31 20:18:11.939+0100
|
||||
example: 2021-12-31 20:18:11.930+0100
|
||||
UtcDateTime:
|
||||
type: string
|
||||
pattern: '[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z'
|
||||
example: 2021-12-31 19:18:11.939Z
|
||||
example: 2021-12-31 19:18:11.930Z
|
||||
AppInfo:
|
||||
type: object
|
||||
properties:
|
||||
|
@ -50,7 +50,9 @@ function register(router) {
|
||||
'notePosition': [v.notNull, v.isInteger],
|
||||
'prefix': [v.notNull, v.isString],
|
||||
'isExpanded': [v.notNull, v.isBoolean],
|
||||
'noteId': [v.notNull, v.isValidEntityId]
|
||||
'noteId': [v.notNull, v.isValidEntityId],
|
||||
'dateCreated': [v.notNull, v.isString, v.isLocalDateTime],
|
||||
'utcDateCreated': [v.notNull, v.isString, v.isUtcDateTime]
|
||||
};
|
||||
|
||||
eu.route(router, 'post' ,'/etapi/create-note', (req, res, next) => {
|
||||
@ -74,7 +76,9 @@ function register(router) {
|
||||
const ALLOWED_PROPERTIES_FOR_PATCH = {
|
||||
'title': [v.notNull, v.isString],
|
||||
'type': [v.notNull, v.isString],
|
||||
'mime': [v.notNull, v.isString]
|
||||
'mime': [v.notNull, v.isString],
|
||||
'dateCreated': [v.notNull, v.isString, v.isLocalDateTime],
|
||||
'utcDateCreated': [v.notNull, v.isString, v.isUtcDateTime]
|
||||
};
|
||||
|
||||
eu.route(router, 'patch' ,'/etapi/notes/:noteId', (req, res, next) => {
|
||||
|
@ -1,4 +1,5 @@
|
||||
const noteTypeService = require("../services/note_types");
|
||||
const dateUtils = require("../services/date_utils");
|
||||
|
||||
function mandatory(obj) {
|
||||
if (obj === undefined ) {
|
||||
@ -22,6 +23,22 @@ function isString(obj) {
|
||||
}
|
||||
}
|
||||
|
||||
function isLocalDateTime(obj) {
|
||||
if (obj === undefined || obj === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
return dateUtils.validateLocalDateTime(obj);
|
||||
}
|
||||
|
||||
function isUtcDateTime(obj) {
|
||||
if (obj === undefined || obj === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
return dateUtils.validateUtcDateTime(obj);
|
||||
}
|
||||
|
||||
function isBoolean(obj) {
|
||||
if (obj === undefined || obj === null) {
|
||||
return;
|
||||
@ -99,5 +116,7 @@ module.exports = {
|
||||
isNoteId,
|
||||
isNoteType,
|
||||
isAttributeType,
|
||||
isValidEntityId
|
||||
isValidEntityId,
|
||||
isLocalDateTime,
|
||||
isUtcDateTime
|
||||
};
|
||||
|
@ -139,10 +139,9 @@ class FNote {
|
||||
}
|
||||
|
||||
async getContent() {
|
||||
// we're not caching content since these objects are in froca and as such pretty long-lived
|
||||
const note = await server.get(`notes/${this.noteId}`);
|
||||
const blob = await this.getBlob();
|
||||
|
||||
return note.content;
|
||||
return blob?.content;
|
||||
}
|
||||
|
||||
async getJsonContent() {
|
||||
|
@ -4,8 +4,11 @@ import toastService from "./toast.js";
|
||||
import froca from "./froca.js";
|
||||
import utils from "./utils.js";
|
||||
|
||||
async function getAndExecuteBundle(noteId, originEntity = null) {
|
||||
const bundle = await server.get(`script/bundle/${noteId}`);
|
||||
async function getAndExecuteBundle(noteId, originEntity = null, script = null, params = null) {
|
||||
const bundle = await server.post(`script/bundle/${noteId}`, {
|
||||
script,
|
||||
params
|
||||
});
|
||||
|
||||
return await executeBundle(bundle, originEntity);
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ import shortcutService from "./shortcuts.js";
|
||||
* @constructor
|
||||
*/
|
||||
function FrontendScriptApi(startNote, currentNote, originEntity = null, $container = null) {
|
||||
/**
|
||||
/**
|
||||
* Container of all the rendered script content
|
||||
* @type {jQuery}
|
||||
* */
|
||||
@ -51,7 +51,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
|
||||
* @type {FNote}
|
||||
*/
|
||||
this.currentNote = currentNote;
|
||||
/**
|
||||
/**
|
||||
* Entity whose event triggered this execution
|
||||
* @type {object|null}
|
||||
*/
|
||||
@ -331,6 +331,8 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
|
||||
* @param {boolean} [params.showNotePath=false] - show also whole note's path as part of the link
|
||||
* @param {boolean} [params.showNoteIcon=false] - show also note icon before the title
|
||||
* @param {string} [params.title] - custom link tile with note's title as default
|
||||
* @param {string} [params.title=] - custom link tile with note's title as default
|
||||
* @returns {jQuery} - jQuery element with the link (wrapped in <span>)
|
||||
*/
|
||||
this.createLink = linkService.createLink;
|
||||
|
||||
|
@ -10,7 +10,7 @@ async function render(note, $el) {
|
||||
$el.empty().toggle(renderNoteIds.length > 0);
|
||||
|
||||
for (const renderNoteId of renderNoteIds) {
|
||||
const bundle = await server.get(`script/bundle/${renderNoteId}`);
|
||||
const bundle = await server.post(`script/bundle/${renderNoteId}`);
|
||||
|
||||
const $scriptContainer = $('<div>');
|
||||
$el.append($scriptContainer);
|
||||
|
@ -125,6 +125,13 @@ async function handleMessage(event) {
|
||||
else if (message.type === 'toast') {
|
||||
toastService.showMessage(message.message);
|
||||
}
|
||||
else if (message.type === 'execute-script') {
|
||||
const bundleService = (await import("../services/bundle.js")).default;
|
||||
const froca = (await import("../services/froca.js")).default;
|
||||
const originEntity = message.originEntityId ? await froca.getNote(message.originEntityId) : null;
|
||||
|
||||
bundleService.getAndExecuteBundle(message.currentNoteId, originEntity, message.script, message.params);
|
||||
}
|
||||
}
|
||||
|
||||
let entityChangeIdReachedListeners = [];
|
||||
|
@ -115,7 +115,7 @@ export default class FindInCode {
|
||||
|
||||
return {
|
||||
totalFound,
|
||||
currentFound: currentFound + 1
|
||||
currentFound: Math.min(currentFound + 1, totalFound)
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -39,7 +39,7 @@ export default class FindInHtml {
|
||||
|
||||
res({
|
||||
totalFound: this.$results.length,
|
||||
currentFound: 1
|
||||
currentFound: Math.min(1, this.$results.length)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -59,7 +59,7 @@ export default class FindInText {
|
||||
|
||||
return {
|
||||
totalFound,
|
||||
currentFound: currentFound + 1
|
||||
currentFound: Math.min(currentFound + 1, totalFound)
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -37,7 +37,9 @@ export default class NoteListWidget extends NoteContextAwareWidget {
|
||||
threshold: 0.1
|
||||
});
|
||||
|
||||
observer.observe(this.$widget[0]);
|
||||
// there seems to be a race condition on Firefox which triggers the observer only before the widget is visible
|
||||
// (intersection is false). https://github.com/zadam/trilium/issues/4165
|
||||
setTimeout(() => observer.observe(this.$widget[0]), 10);
|
||||
}
|
||||
|
||||
checkRenderStatus() {
|
||||
|
@ -94,13 +94,12 @@ function createNote(req) {
|
||||
clipType = htmlSanitizer.sanitize(clipType);
|
||||
|
||||
const clipperInbox = getClipperInboxNote();
|
||||
const dailyNote = dateNoteService.getDayNote(dateUtils.localNowDate());
|
||||
pageUrl = htmlSanitizer.sanitizeUrl(pageUrl);
|
||||
let note = findClippingNote(clipperInbox, pageUrl, clipType);
|
||||
|
||||
if (!note) {
|
||||
note = noteService.createNewNote({
|
||||
parentNoteId: dailyNote.noteId,
|
||||
parentNoteId: clipperInbox.noteId,
|
||||
title,
|
||||
content: '',
|
||||
type: 'text'
|
||||
|
@ -107,8 +107,9 @@ function getRelationBundles(req) {
|
||||
|
||||
function getBundle(req) {
|
||||
const note = becca.getNote(req.params.noteId);
|
||||
const {script, params} = req.body;
|
||||
|
||||
return scriptService.getScriptBundleForFrontend(note);
|
||||
return scriptService.getScriptBundleForFrontend(note, script, params);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
@ -302,7 +302,7 @@ function register(app) {
|
||||
apiRoute(PST, '/api/script/run/:noteId', scriptRoute.run);
|
||||
apiRoute(GET, '/api/script/startup', scriptRoute.getStartupBundles);
|
||||
apiRoute(GET, '/api/script/widgets', scriptRoute.getWidgetBundles);
|
||||
apiRoute(GET, '/api/script/bundle/:noteId', scriptRoute.getBundle);
|
||||
apiRoute(PST, '/api/script/bundle/:noteId', scriptRoute.getBundle);
|
||||
apiRoute(GET, '/api/script/relation/:noteId/:relationName', scriptRoute.getRelationBundles);
|
||||
|
||||
// no CSRF since this is called from android app
|
||||
|
@ -558,6 +558,48 @@ function BackendScriptApi(currentNote, apiParams) {
|
||||
*/
|
||||
this.exportSubtreeToZipFile = async (noteId, format, zipFilePath) => await exportService.exportToZipFile(noteId, format, zipFilePath);
|
||||
|
||||
/**
|
||||
* Executes given anonymous function on the frontend(s).
|
||||
* Internally this serializes the anonymous function into string and sends it to frontend(s) via WebSocket.
|
||||
* Note that there can be multiple connected frontend instances (e.g. in different tabs). In such case, all
|
||||
* instances execute the given function.
|
||||
*
|
||||
* @method
|
||||
* @param {string} script - script to be executed on the frontend
|
||||
* @param {Array.<?>} params - list of parameters to the anonymous function to be sent to frontend
|
||||
* @returns {undefined} - no return value is provided.
|
||||
*/
|
||||
this.runOnFrontend = async (script, params = []) => {
|
||||
if (typeof script === "function") {
|
||||
script = script.toString();
|
||||
}
|
||||
|
||||
ws.sendMessageToAllClients({
|
||||
type: 'execute-script',
|
||||
script: script,
|
||||
params: prepareParams(params),
|
||||
startNoteId: this.startNote.noteId,
|
||||
currentNoteId: this.currentNote.noteId,
|
||||
originEntityName: "notes", // currently there's no other entity on the frontend which can trigger event
|
||||
originEntityId: this.originEntity?.noteId || null
|
||||
});
|
||||
|
||||
function prepareParams(params) {
|
||||
if (!params) {
|
||||
return params;
|
||||
}
|
||||
|
||||
return params.map(p => {
|
||||
if (typeof p === "function") {
|
||||
return `!@#Function: ${p.toString()}`;
|
||||
}
|
||||
else {
|
||||
return p;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This object contains "at your risk" and "no BC guarantees" objects for advanced use cases.
|
||||
*
|
||||
|
@ -1 +1 @@
|
||||
module.exports = { buildDate:"2023-08-10T23:49:37+02:00", buildRevision: "e741c2826c3b2ca5f3d6c7505f45a684e5231dba" };
|
||||
module.exports = { buildDate:"2023-08-16T23:02:15+02:00", buildRevision: "3f7a5504c77263a7118cede5c0d9b450ba37f424" };
|
||||
|
@ -758,7 +758,7 @@ class ConsistencyChecks {
|
||||
return `${tableName}: ${count}`;
|
||||
}
|
||||
|
||||
const tables = [ "notes", "revisions", "attachments", "branches", "attributes", "etapi_tokens" ];
|
||||
const tables = [ "notes", "revisions", "attachments", "branches", "attributes", "etapi_tokens", "blobs" ];
|
||||
|
||||
log.info(`Table counts: ${tables.map(tableName => getTableRowCount(tableName)).join(", ")}`);
|
||||
}
|
||||
@ -767,7 +767,13 @@ class ConsistencyChecks {
|
||||
let elapsedTimeMs;
|
||||
|
||||
await syncMutexService.doExclusively(() => {
|
||||
elapsedTimeMs = this.runChecksInner();
|
||||
const startTimeMs = Date.now();
|
||||
|
||||
this.runDbDiagnostics();
|
||||
|
||||
this.runAllChecksAndFixers();
|
||||
|
||||
elapsedTimeMs = Date.now() - startTimeMs;
|
||||
});
|
||||
|
||||
if (this.unrecoveredConsistencyErrors) {
|
||||
@ -781,16 +787,6 @@ class ConsistencyChecks {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
runChecksInner() {
|
||||
const startTimeMs = Date.now();
|
||||
|
||||
this.runDbDiagnostics();
|
||||
|
||||
this.runAllChecksAndFixers();
|
||||
|
||||
return Date.now() - startTimeMs;
|
||||
}
|
||||
}
|
||||
|
||||
function getBlankContent(isProtected, type, mime) {
|
||||
@ -825,11 +821,6 @@ async function runOnDemandChecks(autoFix) {
|
||||
await consistencyChecks.runChecks();
|
||||
}
|
||||
|
||||
function runOnDemandChecksWithoutExclusiveLock(autoFix) {
|
||||
const consistencyChecks = new ConsistencyChecks(autoFix);
|
||||
consistencyChecks.runChecksInner();
|
||||
}
|
||||
|
||||
function runEntityChangesChecks() {
|
||||
const consistencyChecks = new ConsistencyChecks(true);
|
||||
consistencyChecks.findEntityChangeIssues();
|
||||
@ -844,6 +835,5 @@ sqlInit.dbReady.then(() => {
|
||||
|
||||
module.exports = {
|
||||
runOnDemandChecks,
|
||||
runOnDemandChecksWithoutExclusiveLock,
|
||||
runEntityChangesChecks
|
||||
};
|
||||
|
@ -1,6 +1,9 @@
|
||||
const dayjs = require('dayjs');
|
||||
const cls = require('./cls');
|
||||
|
||||
const LOCAL_DATETIME_FORMAT = 'YYYY-MM-DD HH:mm:ss.SSSZZ';
|
||||
const UTC_DATETIME_FORMAT = 'YYYY-MM-DD HH:mm:ssZ';
|
||||
|
||||
function utcNowDateTime() {
|
||||
return utcDateTimeStr(new Date());
|
||||
}
|
||||
@ -10,7 +13,7 @@ function utcNowDateTime() {
|
||||
// "trilium-local-now-datetime" header which is then stored in CLS
|
||||
function localNowDateTime() {
|
||||
return cls.getLocalNowDateTime()
|
||||
|| dayjs().format('YYYY-MM-DD HH:mm:ss.SSSZZ')
|
||||
|| dayjs().format(LOCAL_DATETIME_FORMAT)
|
||||
}
|
||||
|
||||
function localNowDate() {
|
||||
@ -62,6 +65,36 @@ function getDateTimeForFile() {
|
||||
return new Date().toISOString().substr(0, 19).replace(/:/g, '');
|
||||
}
|
||||
|
||||
function validateLocalDateTime(str) {
|
||||
if (!str) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!/[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}[+-][0-9]{4}/.test(str)) {
|
||||
return `Invalid local date time format in '${str}'. Correct format shoud follow example: '2023-08-21 23:38:51.110+0200'`;
|
||||
}
|
||||
|
||||
|
||||
if (!dayjs(str, LOCAL_DATETIME_FORMAT)) {
|
||||
return `Date '${str}' appears to be in the correct format, but cannot be parsed. It likely represents an invalid date.`;
|
||||
}
|
||||
}
|
||||
|
||||
function validateUtcDateTime(str) {
|
||||
if (!str) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!/[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z/.test(str)) {
|
||||
return `Invalid UTC date time format in '${str}'. Correct format shoud follow example: '2023-08-21 23:38:51.110Z'`;
|
||||
}
|
||||
|
||||
|
||||
if (!dayjs(str, UTC_DATETIME_FORMAT)) {
|
||||
return `Date '${str}' appears to be in the correct format, but cannot be parsed. It likely represents an invalid date.`;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
utcNowDateTime,
|
||||
localNowDateTime,
|
||||
@ -70,5 +103,7 @@ module.exports = {
|
||||
utcDateTimeStr,
|
||||
parseDateTime,
|
||||
parseLocalDate,
|
||||
getDateTimeForFile
|
||||
getDateTimeForFile,
|
||||
validateLocalDateTime,
|
||||
validateUtcDateTime
|
||||
};
|
||||
|
@ -15,6 +15,12 @@ function putEntityChangeWithInstanceId(origEntityChange, instanceId) {
|
||||
putEntityChange(ec);
|
||||
}
|
||||
|
||||
function putEntityChangeWithForcedChange(origEntityChange) {
|
||||
const ec = {...origEntityChange, changeId: null};
|
||||
|
||||
putEntityChange(ec);
|
||||
}
|
||||
|
||||
function putEntityChange(origEntityChange) {
|
||||
const ec = {...origEntityChange};
|
||||
|
||||
@ -66,13 +72,37 @@ function putEntityChangeForOtherInstances(ec) {
|
||||
function addEntityChangesForSector(entityName, sector) {
|
||||
const entityChanges = sql.getRows(`SELECT * FROM entity_changes WHERE entityName = ? AND SUBSTR(entityId, 1, 1) = ?`, [entityName, sector]);
|
||||
|
||||
let entitiesInserted = entityChanges.length;
|
||||
|
||||
sql.transactional(() => {
|
||||
if (entityName === 'blobs') {
|
||||
entitiesInserted += addEntityChangesForDependingEntity(sector, 'notes', 'noteId');
|
||||
entitiesInserted += addEntityChangesForDependingEntity(sector, 'attachments', 'attachmentId');
|
||||
entitiesInserted += addEntityChangesForDependingEntity(sector, 'revisions', 'revisionId');
|
||||
}
|
||||
|
||||
for (const ec of entityChanges) {
|
||||
putEntityChange(ec);
|
||||
putEntityChangeWithForcedChange(ec);
|
||||
}
|
||||
});
|
||||
|
||||
log.info(`Added sector ${sector} of '${entityName}' (${entityChanges.length} entities) to the sync queue.`);
|
||||
log.info(`Added sector ${sector} of '${entityName}' (${entitiesInserted} entities) to the sync queue.`);
|
||||
}
|
||||
|
||||
function addEntityChangesForDependingEntity(sector, tableName, primaryKeyColumn) {
|
||||
// problem in blobs might be caused by problem in entity referencing the blob
|
||||
const dependingEntityChanges = sql.getRows(`
|
||||
SELECT dep_change.*
|
||||
FROM entity_changes orig_sector
|
||||
JOIN ${tableName} ON ${tableName}.blobId = orig_sector.entityId
|
||||
JOIN entity_changes dep_change ON dep_change.entityName = '${tableName}' AND dep_change.entityId = ${tableName}.${primaryKeyColumn}
|
||||
WHERE orig_sector.entityName = 'blobs' AND SUBSTR(orig_sector.entityId, 1, 1) = ?`, [sector]);
|
||||
|
||||
for (const ec of dependingEntityChanges) {
|
||||
putEntityChangeWithForcedChange(ec);
|
||||
}
|
||||
|
||||
return dependingEntityChanges.length;
|
||||
}
|
||||
|
||||
function cleanupEntityChangesForMissingEntities(entityName, entityPrimaryKey) {
|
||||
@ -161,6 +191,7 @@ function recalculateMaxEntityChangeId() {
|
||||
module.exports = {
|
||||
putNoteReorderingEntityChange,
|
||||
putEntityChangeForOtherInstances,
|
||||
putEntityChangeWithForcedChange,
|
||||
putEntityChange,
|
||||
putEntityChangeWithInstanceId,
|
||||
fillAllEntityChanges,
|
||||
|
@ -39,7 +39,7 @@ function setEntityChangesAsErased(entityChanges) {
|
||||
ec.isErased = true;
|
||||
ec.utcDateChanged = dateUtils.utcNowDateTime();
|
||||
|
||||
entityChangesService.putEntityChange(ec);
|
||||
entityChangesService.putEntityChangeWithForcedChange(ec);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -301,16 +301,10 @@ function importEnex(taskContext, file, parentNote) {
|
||||
? resource.title
|
||||
: `image.${resource.mime.substr(6)}`; // default if real name is not present
|
||||
|
||||
const {url, note: imageNote} = imageService.saveImage(noteEntity.noteId, resource.content, originalName, taskContext.data.shrinkImages);
|
||||
|
||||
for (const attr of resource.attributes) {
|
||||
if (attr.name !== 'originalFileName') { // this one is already saved in imageService
|
||||
imageNote.addAttribute(attr.type, attr.name, attr.value);
|
||||
}
|
||||
}
|
||||
|
||||
updateDates(imageNote, utcDateCreated, utcDateModified);
|
||||
const attachment = imageService.saveImageToAttachment(noteEntity.noteId, resource.content, originalName, taskContext.data.shrinkImages);
|
||||
|
||||
const sanitizedTitle = attachment.title.replace(/[^a-z0-9-.]/gi, "");
|
||||
const url = `api/attachments/${attachment.attachmentId}/image/${sanitizedTitle}`;
|
||||
const imageLink = `<img src="${url}">`;
|
||||
|
||||
content = content.replace(mediaRegex, imageLink);
|
||||
|
@ -9,8 +9,8 @@ const appInfo = require('./app_info');
|
||||
async function migrate() {
|
||||
const currentDbVersion = getDbVersion();
|
||||
|
||||
if (currentDbVersion < 183) {
|
||||
log.error("Direct migration from your current version is not supported. Please upgrade to the latest v0.47.X first and only then to this version.");
|
||||
if (currentDbVersion < 214) {
|
||||
log.error("Direct migration from your current version is not supported. Please upgrade to the latest v0.60.X first and only then to this version.");
|
||||
|
||||
utils.crash();
|
||||
return;
|
||||
@ -18,9 +18,9 @@ async function migrate() {
|
||||
|
||||
// backup before attempting migration
|
||||
await backupService.backupNow(
|
||||
// creating a special backup for versions 0.60.X and older, the changes in 0.61 are major.
|
||||
currentDbVersion < 214
|
||||
? `before-migration-v${currentDbVersion}`
|
||||
// creating a special backup for versions 0.60.X, the changes in 0.61 are major.
|
||||
currentDbVersion === 214
|
||||
? `before-migration-v060`
|
||||
: 'before-migration'
|
||||
);
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
const sql = require('./sql');
|
||||
const sqlInit = require('./sql_init');
|
||||
const optionService = require('./options');
|
||||
const dateUtils = require('./date_utils');
|
||||
const entityChangesService = require('./entity_changes');
|
||||
@ -169,6 +168,15 @@ function createNewNote(params) {
|
||||
throw new Error(`Note content must be set`);
|
||||
}
|
||||
|
||||
let error;
|
||||
if (error = dateUtils.validateLocalDateTime(params.dateCreated)) {
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
if (error = dateUtils.validateUtcDateTime(params.utcDateCreated)) {
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
return sql.transactional(() => {
|
||||
let note, branch, isEntityEventsDisabled;
|
||||
|
||||
@ -189,7 +197,9 @@ function createNewNote(params) {
|
||||
title: params.title,
|
||||
isProtected: !!params.isProtected,
|
||||
type: params.type,
|
||||
mime: deriveMime(params.type, params.mime)
|
||||
mime: deriveMime(params.type, params.mime),
|
||||
dateCreated: params.dateCreated,
|
||||
utcDateCreated: params.utcDateCreated
|
||||
}).save();
|
||||
|
||||
note.setContent(params.content);
|
||||
|
@ -9,7 +9,7 @@ function getOptionOrNull(name) {
|
||||
option = becca.getOption(name);
|
||||
} else {
|
||||
// e.g. in initial sync becca is not loaded because DB is not initialized
|
||||
option = sql.getRow("SELECT * FROM options WHERE name = ?", name);
|
||||
option = sql.getRow("SELECT * FROM options WHERE name = ?", [name]);
|
||||
}
|
||||
|
||||
return option ? option.value : null;
|
||||
|
@ -10,7 +10,7 @@ function executeNote(note, apiParams) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bundle = getScriptBundle(note);
|
||||
const bundle = getScriptBundle(note, true, 'backend');
|
||||
|
||||
return executeBundle(bundle, apiParams);
|
||||
}
|
||||
@ -68,9 +68,9 @@ function executeScript(script, params, startNoteId, currentNoteId, originEntityN
|
||||
|
||||
// we're just executing an excerpt of the original frontend script in the backend context, so we must
|
||||
// override normal note's content, and it's mime type / script environment
|
||||
const backendOverrideContent = `return (${script}\r\n)(${getParams(params)})`;
|
||||
const overrideContent = `return (${script}\r\n)(${getParams(params)})`;
|
||||
|
||||
const bundle = getScriptBundle(currentNote, true, null, [], backendOverrideContent);
|
||||
const bundle = getScriptBundle(currentNote, true, 'backend', [], overrideContent);
|
||||
|
||||
return executeBundle(bundle, { startNote, originEntity });
|
||||
}
|
||||
@ -96,9 +96,17 @@ function getParams(params) {
|
||||
|
||||
/**
|
||||
* @param {BNote} note
|
||||
* @param {string} [script]
|
||||
* @param {Array} [params]
|
||||
*/
|
||||
function getScriptBundleForFrontend(note) {
|
||||
const bundle = getScriptBundle(note);
|
||||
function getScriptBundleForFrontend(note, script, params) {
|
||||
let overrideContent = null;
|
||||
|
||||
if (script) {
|
||||
overrideContent = `return (${script}\r\n)(${getParams(params)})`;
|
||||
}
|
||||
|
||||
const bundle = getScriptBundle(note, true, 'frontend', [], overrideContent);
|
||||
|
||||
if (!bundle) {
|
||||
return;
|
||||
@ -119,9 +127,9 @@ function getScriptBundleForFrontend(note) {
|
||||
* @param {boolean} [root=true]
|
||||
* @param {string|null} [scriptEnv]
|
||||
* @param {string[]} [includedNoteIds]
|
||||
* @param {string|null} [backendOverrideContent]
|
||||
* @param {string|null} [overrideContent]
|
||||
*/
|
||||
function getScriptBundle(note, root = true, scriptEnv = null, includedNoteIds = [], backendOverrideContent = null) {
|
||||
function getScriptBundle(note, root = true, scriptEnv = null, includedNoteIds = [], overrideContent = null) {
|
||||
if (!note.isContentAvailable()) {
|
||||
return;
|
||||
}
|
||||
@ -134,12 +142,6 @@ function getScriptBundle(note, root = true, scriptEnv = null, includedNoteIds =
|
||||
return;
|
||||
}
|
||||
|
||||
if (root) {
|
||||
scriptEnv = backendOverrideContent
|
||||
? 'backend'
|
||||
: note.getScriptEnv();
|
||||
}
|
||||
|
||||
if (note.type !== 'file' && !root && scriptEnv !== note.getScriptEnv()) {
|
||||
return;
|
||||
}
|
||||
@ -180,7 +182,7 @@ function getScriptBundle(note, root = true, scriptEnv = null, includedNoteIds =
|
||||
apiContext.modules['${note.noteId}'] = { exports: {} };
|
||||
${root ? 'return ' : ''}${isFrontend ? 'await' : ''} ((${isFrontend ? 'async' : ''} function(exports, module, require, api${modules.length > 0 ? ', ' : ''}${modules.map(child => sanitizeVariableName(child.title)).join(', ')}) {
|
||||
try {
|
||||
${backendOverrideContent || note.getContent()};
|
||||
${overrideContent || note.getContent()};
|
||||
} catch (e) { throw new Error("Load of script note \\"${note.title}\\" (${note.noteId}) failed with: " + e.message); }
|
||||
for (const exportKey in exports) module.exports[exportKey] = exports[exportKey];
|
||||
return module.exports;
|
||||
|
@ -11,7 +11,7 @@ function lex(str) {
|
||||
let currentWord = '';
|
||||
|
||||
function isSymbolAnOperator(chr) {
|
||||
return ['=', '*', '>', '<', '!', "-", "+", '%'].includes(chr);
|
||||
return ['=', '*', '>', '<', '!', "-", "+", '%', ','].includes(chr);
|
||||
}
|
||||
|
||||
function isPreviousSymbolAnOperator() {
|
||||
@ -128,6 +128,10 @@ function lex(str) {
|
||||
}
|
||||
}
|
||||
|
||||
if (chr === ',') {
|
||||
continue;
|
||||
}
|
||||
|
||||
currentWord += chr;
|
||||
}
|
||||
|
||||
|
@ -40,13 +40,12 @@ function updateNormalEntity(remoteEC, remoteEntityRow, instanceId) {
|
||||
// on this side, we can't unerase the entity, so force the entity to be erased on the other side.
|
||||
entityChangesService.putEntityChangeForOtherInstances(localEC);
|
||||
|
||||
return false;
|
||||
} else if (localEC?.isErased && remoteEC.isErased) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!localEC
|
||||
|| localEC.utcDateChanged < remoteEC.utcDateChanged
|
||||
|| (localEC.utcDateChanged === remoteEC.utcDateChanged && localEC.hash !== remoteEC.hash) // sync error, we should still update
|
||||
) {
|
||||
if (!localEC || localEC.utcDateChanged <= remoteEC.utcDateChanged) {
|
||||
if (remoteEC.entityName === 'blobs' && remoteEntityRow.content !== null) {
|
||||
// we always use a Buffer object which is different from normal saving - there we use a simple string type for
|
||||
// "string notes". The problem is that in general, it's not possible to detect whether a blob content
|
||||
@ -62,7 +61,9 @@ function updateNormalEntity(remoteEC, remoteEntityRow, instanceId) {
|
||||
|
||||
sql.replace(remoteEC.entityName, remoteEntityRow);
|
||||
|
||||
entityChangesService.putEntityChangeWithInstanceId(remoteEC, instanceId);
|
||||
if (!localEC || localEC.utcDateChanged < remoteEC.utcDateChanged) {
|
||||
entityChangesService.putEntityChangeWithInstanceId(remoteEC, instanceId);
|
||||
}
|
||||
|
||||
return true;
|
||||
} else if (localEC.hash !== remoteEC.hash && localEC.utcDateChanged > remoteEC.utcDateChanged) {
|
||||
|
@ -184,10 +184,8 @@ function sortNotesIfNeeded(parentNoteId) {
|
||||
}
|
||||
|
||||
const sortReversed = parentNote.getLabelValue('sortDirection')?.toLowerCase() === "desc";
|
||||
const sortFoldersFirstLabel = parentNote.getLabel('sortFoldersFirst');
|
||||
const sortFoldersFirst = sortFoldersFirstLabel && sortFoldersFirstLabel.value.toLowerCase() !== "false";
|
||||
const sortNaturalLabel = parentNote.getLabel('sortNatural');
|
||||
const sortNatural = sortNaturalLabel && sortNaturalLabel.value.toLowerCase() !== "false";
|
||||
const sortFoldersFirst = parentNote.isLabelTruthy('sortFoldersFirst');
|
||||
const sortNatural = parentNote.isLabelTruthy('sortNatural');
|
||||
const sortLocale = parentNote.getLabelValue('sortLocale');
|
||||
|
||||
sortNotes(parentNoteId, sortedLabel.value, sortReversed, sortFoldersFirst, sortNatural, sortLocale);
|
||||
|
@ -7,13 +7,17 @@ Content-Type: application/json
|
||||
"parentNoteId": "root",
|
||||
"title": "Hello",
|
||||
"type": "text",
|
||||
"content": "Hi there!"
|
||||
"content": "Hi there!",
|
||||
"dateCreated": "2023-08-21 23:38:51.123+0200",
|
||||
"utcDateCreated": "2023-08-21 23:38:51.123Z"
|
||||
}
|
||||
|
||||
> {%
|
||||
client.assert(response.status === 201);
|
||||
client.assert(response.body.note.noteId.startsWith("forcedId"));
|
||||
client.assert(response.body.note.title == "Hello");
|
||||
client.assert(response.body.note.dateCreated == "2023-08-21 23:38:51.123+0200");
|
||||
client.assert(response.body.note.utcDateCreated == "2023-08-21 23:38:51.123Z");
|
||||
client.assert(response.body.branch.parentNoteId == "root");
|
||||
|
||||
client.log(`Created note ` + response.body.note.noteId + ` and branch ` + response.body.branch.branchId);
|
||||
|
@ -17,11 +17,11 @@ Content-Type: application/json
|
||||
GET {{triliumHost}}/etapi/notes/{{createdNoteId}}
|
||||
Authorization: {{authToken}}
|
||||
|
||||
> {%
|
||||
> {%
|
||||
client.assert(response.status === 200);
|
||||
client.assert(response.body.title === 'Hello');
|
||||
client.assert(response.body.type === 'code');
|
||||
client.assert(response.body.mime === 'application/json');
|
||||
client.assert(response.body.title === 'Hello');
|
||||
client.assert(response.body.type === 'code');
|
||||
client.assert(response.body.mime === 'application/json');
|
||||
%}
|
||||
|
||||
###
|
||||
@ -33,7 +33,9 @@ Content-Type: application/json
|
||||
{
|
||||
"title": "Wassup",
|
||||
"type": "html",
|
||||
"mime": "text/html"
|
||||
"mime": "text/html",
|
||||
"dateCreated": "2023-08-21 23:38:51.123+0200",
|
||||
"utcDateCreated": "2023-08-21 23:38:51.123Z"
|
||||
}
|
||||
|
||||
###
|
||||
@ -41,11 +43,13 @@ Content-Type: application/json
|
||||
GET {{triliumHost}}/etapi/notes/{{createdNoteId}}
|
||||
Authorization: {{authToken}}
|
||||
|
||||
> {%
|
||||
> {%
|
||||
client.assert(response.status === 200);
|
||||
client.assert(response.body.title === 'Wassup');
|
||||
client.assert(response.body.type === 'html');
|
||||
client.assert(response.body.mime === 'text/html');
|
||||
client.assert(response.body.title === 'Wassup');
|
||||
client.assert(response.body.type === 'html');
|
||||
client.assert(response.body.mime === 'text/html');
|
||||
client.assert(response.body.dateCreated == "2023-08-21 23:38:51.123+0200");
|
||||
client.assert(response.body.utcDateCreated == "2023-08-21 23:38:51.123Z");
|
||||
%}
|
||||
|
||||
###
|
||||
@ -58,8 +62,8 @@ Content-Type: application/json
|
||||
"isProtected": true
|
||||
}
|
||||
|
||||
> {%
|
||||
client.assert(response.status === 400);
|
||||
> {%
|
||||
client.assert(response.status === 400);
|
||||
client.assert(response.body.code == "PROPERTY_NOT_ALLOWED");
|
||||
%}
|
||||
|
||||
@ -73,7 +77,7 @@ Content-Type: application/json
|
||||
"title": true
|
||||
}
|
||||
|
||||
> {%
|
||||
client.assert(response.status === 400);
|
||||
> {%
|
||||
client.assert(response.status === 400);
|
||||
client.assert(response.body.code == "PROPERTY_VALIDATION_ERROR");
|
||||
%}
|
||||
|
Loading…
x
Reference in New Issue
Block a user