mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
Merge branch 'master' into dev
# Conflicts: # src/services/app_info.js
This commit is contained in:
commit
6c4df5110e
@ -10,6 +10,7 @@ Trilium Notes 是一个层次化的笔记应用程序,专注于建立大型个
|
||||
Ukraine is currently suffering from Russian aggression, please consider donating to [one of these charities](https://old.reddit.com/r/ukraine/comments/s6g5un/want_to_support_ukraine_heres_a_list_of_charities/).
|
||||
|
||||
<img src="https://upload.wikimedia.org/wikipedia/commons/4/49/Flag_of_Ukraine.svg" alt="drawing" width="600"/>
|
||||
<img src="https://signmyrocket.com//uploads/2b2a523cd0c0e76cdbba95a89a9636b2_1676971281.jpg" alt="Trilium Notes supports Ukraine!" width="600"/>
|
||||
|
||||
## 特性
|
||||
|
||||
|
@ -11,6 +11,8 @@ Ukraine is currently defending itself from Russian aggression, please consider [
|
||||
|
||||
<img src="https://upload.wikimedia.org/wikipedia/commons/4/49/Flag_of_Ukraine.svg" alt="drawing" width="600"/>
|
||||
|
||||
<img src="https://signmyrocket.com//uploads/2b2a523cd0c0e76cdbba95a89a9636b2_1676971281.jpg" alt="Trilium Notes supports Ukraine!" width="600"/>
|
||||
|
||||
## Features
|
||||
|
||||
* Notes can be arranged into arbitrarily deep tree. Single note can be placed into multiple places in the tree (see [cloning](https://github.com/zadam/trilium/wiki/Cloning-notes))
|
||||
@ -33,6 +35,8 @@ Ukraine is currently defending itself from Russian aggression, please consider [
|
||||
* [Evernote](https://github.com/zadam/trilium/wiki/Evernote-import) and [Markdown import & export](https://github.com/zadam/trilium/wiki/Markdown)
|
||||
* [Web Clipper](https://github.com/zadam/trilium/wiki/Web-clipper) for easy saving of web content
|
||||
|
||||
Check out [awesome-trilium](https://github.com/Nriver/awesome-trilium) for 3rd party themes, scripts, plugins and more.
|
||||
|
||||
## Builds
|
||||
|
||||
Trilium is provided as either desktop application (Linux and Windows) or web application hosted on your server (Linux). Mac OS desktop build is available, but it is [unsupported](https://github.com/zadam/trilium/wiki/FAQ#mac-os-support).
|
||||
|
@ -10,6 +10,7 @@ Trilium Notes – это приложение для заметок с иера
|
||||
Ukraine is currently suffering from Russian aggression, please consider donating to [one of these charities](https://old.reddit.com/r/ukraine/comments/s6g5un/want_to_support_ukraine_heres_a_list_of_charities/).
|
||||
|
||||
<img src="https://upload.wikimedia.org/wikipedia/commons/4/49/Flag_of_Ukraine.svg" alt="drawing" width="600"/>
|
||||
<img src="https://signmyrocket.com//uploads/2b2a523cd0c0e76cdbba95a89a9636b2_1676971281.jpg" alt="Trilium Notes supports Ukraine!" width="600"/>
|
||||
|
||||
## Возможности
|
||||
|
||||
|
@ -1,13 +1,162 @@
|
||||
|
||||
UPDATE etapi_tokens SET tokenHash = 'API token hash value';
|
||||
UPDATE notes SET title = 'title' WHERE title NOT IN ('root', '_hidden', '_share');
|
||||
UPDATE notes SET title = 'title' WHERE noteId != 'root' AND noteId NOT LIKE '\_%' ESCAPE '\';
|
||||
UPDATE note_contents SET content = 'text' WHERE content IS NOT NULL;
|
||||
UPDATE note_revisions SET title = 'title';
|
||||
UPDATE note_revision_contents SET content = 'text' WHERE content IS NOT NULL;
|
||||
UPDATE note_ancillary_contents SET content = 'text' WHERE content IS NOT NULL;
|
||||
|
||||
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', 'noteRevisionsWidgetDisabled', 'whatLinksHereWidgetDisabled', 'similarNotesWidgetDisabled', 'workspace', 'workspaceIconClass', 'workspaceTabBackgroundColor', 'searchHome', 'workspaceInbox', 'workspaceSearchHome', 'sqlConsoleHome', 'datePattern', 'pageSize', 'viewType', 'mapRootNoteId', 'bookmarkFolder', 'sorted', 'top', 'fullContentWidth', 'shareHiddenFromTree', 'shareAlias', 'shareOmitDefaultCss', 'shareRoot', 'shareDescription', 'internalLink', 'imageLink', 'relationMapLink', 'includeMapLink', 'runOnNoteCreation', 'runOnNoteTitleChange', 'runOnNoteContentChange', 'runOnNoteChange', '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', 'noteRevisionsWidgetDisabled', 'whatLinksHereWidgetDisabled', 'similarNotesWidgetDisabled', 'workspace', 'workspaceIconClass', 'workspaceTabBackgroundColor', 'searchHome', 'workspaceInbox', 'workspaceSearchHome', 'sqlConsoleHome', 'datePattern', 'pageSize', 'viewType', 'mapRootNoteId', 'bookmarkFolder', 'sorted', 'top', 'fullContentWidth', 'shareHiddenFromTree', 'shareAlias', 'shareOmitDefaultCss', 'shareRoot', 'shareDescription', 'internalLink', 'imageLink', 'relationMapLink', 'includeMapLink', 'runOnNoteCreation', 'runOnNoteTitleChange', 'runOnNoteContentChange', 'runOnNoteChange', 'runOnChildNoteCreation', 'runOnAttributeCreation', 'runOnAttributeChange', 'template', 'inherit', 'widget', 'renderNote', 'shareCss', 'shareJs', 'shareFavicon');
|
||||
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',
|
||||
'noteRevisionsWidgetDisabled',
|
||||
'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',
|
||||
'noteRevisionsWidgetDisabled',
|
||||
'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 branches SET prefix = 'prefix' WHERE prefix IS NOT NULL AND prefix != 'recovered';
|
||||
UPDATE options SET value = 'anonymized' WHERE name IN
|
||||
('documentId', 'documentSecret', 'encryptedDataKey',
|
||||
|
BIN
db/demo.zip
BIN
db/demo.zip
Binary file not shown.
48
db/migrations/0213__migrate_scripts.js
Normal file
48
db/migrations/0213__migrate_scripts.js
Normal file
@ -0,0 +1,48 @@
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
4
libraries/ckeditor/ckeditor.js
vendored
4
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
5
package-lock.json
generated
5
package-lock.json
generated
@ -1,11 +1,12 @@
|
||||
{
|
||||
"name": "trilium",
|
||||
"version": "0.58.8",
|
||||
"version": "0.59.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"version": "0.58.8",
|
||||
"name": "trilium",
|
||||
"version": "0.59.1",
|
||||
"hasInstallScript": true,
|
||||
"license": "AGPL-3.0-only",
|
||||
"dependencies": {
|
||||
|
@ -2,7 +2,7 @@
|
||||
"name": "trilium",
|
||||
"productName": "Trilium Notes",
|
||||
"description": "Trilium Notes",
|
||||
"version": "0.58.8",
|
||||
"version": "0.59.1",
|
||||
"license": "AGPL-3.0-only",
|
||||
"main": "electron.js",
|
||||
"bin": {
|
||||
|
@ -84,7 +84,7 @@ eventService.subscribeBeccaLoader([eventService.ENTITY_CHANGE_SYNCED], ({entity
|
||||
return;
|
||||
}
|
||||
|
||||
if (["notes", "branches", "attributes", "etapi_tokens"].includes(entityName)) {
|
||||
if (["notes", "branches", "attributes", "etapi_tokens", "options"].includes(entityName)) {
|
||||
const EntityClass = entityConstructor.getEntityFromEntityName(entityName);
|
||||
const primaryKeyName = EntityClass.primaryKeyName;
|
||||
|
||||
|
@ -124,35 +124,43 @@ function getNoteTitleForPath(notePathArray) {
|
||||
* Archived (and hidden) notes are also returned, but non-archived paths are preferred if available
|
||||
* - this means that archived paths is returned only if there's no non-archived path
|
||||
* - you can check whether returned path is archived using isArchived
|
||||
*
|
||||
* @param {BNote} note
|
||||
* @param {string[]} path
|
||||
*/
|
||||
function getSomePath(note, path = []) {
|
||||
// first try to find note within hoisted note, otherwise take any existing note path
|
||||
// each branch needs a separate copy since it's mutable
|
||||
return getSomePathInner(note, [...path], true)
|
||||
|| getSomePathInner(note, [...path], false);
|
||||
return getSomePathInner(note, path, true)
|
||||
|| getSomePathInner(note, path, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {BNote} note
|
||||
* @param {string[]} path
|
||||
* @param {boolean}respectHoisting
|
||||
* @returns {string[]|false}
|
||||
*/
|
||||
function getSomePathInner(note, path, respectHoisting) {
|
||||
if (note.isRoot()) {
|
||||
path.push(note.noteId);
|
||||
path.reverse();
|
||||
const foundPath = [...path, note.noteId];
|
||||
foundPath.reverse();
|
||||
|
||||
if (respectHoisting && !path.includes(cls.getHoistedNoteId())) {
|
||||
if (respectHoisting && !foundPath.includes(cls.getHoistedNoteId())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return path;
|
||||
return foundPath;
|
||||
}
|
||||
|
||||
const parents = note.parents;
|
||||
if (parents.length === 0) {
|
||||
console.log(`Note ${note.noteId} - "${note.title}" has no parents.`);
|
||||
console.log(`Note '${note.noteId}' - '${note.title}' has no parents.`);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const parentNote of parents) {
|
||||
const retPath = getSomePathInner(parentNote, path.concat([note.noteId]), respectHoisting);
|
||||
const retPath = getSomePathInner(parentNote, [...path, note.noteId], respectHoisting);
|
||||
|
||||
if (retPath) {
|
||||
return retPath;
|
||||
|
@ -1182,9 +1182,7 @@ class BNote extends AbstractBeccaEntity {
|
||||
return false;
|
||||
} else if (parentNote.noteId === '_hidden') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!parentNote.isHiddenCompletely()) {
|
||||
} else if (!parentNote.isHiddenCompletely()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -166,12 +166,14 @@ class BNoteRevision extends AbstractBeccaEntity {
|
||||
utcDateLastEdited: this.utcDateLastEdited,
|
||||
utcDateCreated: this.utcDateCreated,
|
||||
utcDateModified: this.utcDateModified,
|
||||
content: this.content, // used when retrieving full note revision to frontend
|
||||
contentLength: this.contentLength
|
||||
};
|
||||
}
|
||||
|
||||
getPojoToSave() {
|
||||
const pojo = this.getPojo();
|
||||
delete pojo.content; // not getting persisted
|
||||
delete pojo.contentLength; // not getting persisted
|
||||
|
||||
if (pojo.isProtected) {
|
||||
|
@ -16,6 +16,11 @@ class BOption extends AbstractBeccaEntity {
|
||||
constructor(row) {
|
||||
super();
|
||||
|
||||
this.updateFromRow(row);
|
||||
this.becca.options[this.name] = this;
|
||||
}
|
||||
|
||||
updateFromRow(row) {
|
||||
/** @type {string} */
|
||||
this.name = row.name;
|
||||
/** @type {string} */
|
||||
@ -24,8 +29,6 @@ class BOption extends AbstractBeccaEntity {
|
||||
this.isSynced = !!row.isSynced;
|
||||
/** @type {string} */
|
||||
this.utcDateModified = row.utcDateModified;
|
||||
|
||||
this.becca.options[this.name] = this;
|
||||
}
|
||||
|
||||
beforeSaving() {
|
||||
|
@ -73,7 +73,7 @@ class FNote {
|
||||
this.mime = row.mime;
|
||||
}
|
||||
|
||||
addParent(parentNoteId, branchId) {
|
||||
addParent(parentNoteId, branchId, sort = true) {
|
||||
if (parentNoteId === 'none') {
|
||||
return;
|
||||
}
|
||||
@ -83,6 +83,10 @@ class FNote {
|
||||
}
|
||||
|
||||
this.parentToBranch[parentNoteId] = branchId;
|
||||
|
||||
if (sort) {
|
||||
this.sortParents();
|
||||
}
|
||||
}
|
||||
|
||||
addChild(childNoteId, branchId, sort = true) {
|
||||
@ -189,7 +193,7 @@ class FNote {
|
||||
|
||||
// will sort the parents so that non-search & non-archived are first and archived at the end
|
||||
// this is done so that non-search & non-archived paths are always explored as first when looking for note path
|
||||
resortParents() {
|
||||
sortParents() {
|
||||
this.parents.sort((aNoteId, bNoteId) => {
|
||||
const aBranchId = this.parentToBranch[aNoteId];
|
||||
|
||||
@ -197,7 +201,7 @@ class FNote {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const aNote = this.froca.getNoteFromCache([aNoteId]);
|
||||
const aNote = this.froca.getNoteFromCache(aNoteId);
|
||||
|
||||
if (aNote.isArchived || aNote.isHiddenCompletely()) {
|
||||
return 1;
|
||||
|
@ -14,7 +14,8 @@ function createClassForColor(color) {
|
||||
const className = `color-${normalizedColorName}`;
|
||||
|
||||
if (!registeredClasses.has(className)) {
|
||||
$("head").append(`<style>.${className} { color: ${color} !important; }</style>`);
|
||||
// make the active fancytree selector more specific than the normal color setting
|
||||
$("head").append(`<style>.${className}, span.fancytree-active.${className} { color: ${color} !important; }</style>`);
|
||||
|
||||
registeredClasses.add(className);
|
||||
}
|
||||
|
@ -115,7 +115,7 @@ class Froca {
|
||||
const childNote = this.notes[branch.noteId];
|
||||
|
||||
if (childNote) {
|
||||
childNote.addParent(branch.parentNoteId, branch.branchId);
|
||||
childNote.addParent(branch.parentNoteId, branch.branchId, false);
|
||||
}
|
||||
|
||||
const parentNote = this.notes[branch.parentNoteId];
|
||||
@ -152,6 +152,7 @@ class Froca {
|
||||
// sort all of them at once, this avoids repeated sorts (#1480)
|
||||
for (const noteId of noteIdsToSort) {
|
||||
this.notes[noteId].sortChildren();
|
||||
this.notes[noteId].sortParents();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -35,36 +35,12 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
|
||||
/** @property {dayjs} day.js library for date manipulation. See {@link https://day.js.org} for documentation */
|
||||
this.dayjs = dayjs;
|
||||
|
||||
/**
|
||||
* @property {RightPanelWidget}
|
||||
* @deprecated use api.RightPanelWidget instead
|
||||
*/
|
||||
this.CollapsibleWidget = RightPanelWidget;
|
||||
|
||||
/** @property {RightPanelWidget} */
|
||||
this.RightPanelWidget = RightPanelWidget;
|
||||
|
||||
/** @property {NoteContextAwareWidget} */
|
||||
this.NoteContextAwareWidget = NoteContextAwareWidget;
|
||||
|
||||
/**
|
||||
* @property {NoteContextAwareWidget}
|
||||
* @deprecated use NoteContextAwareWidget instead
|
||||
*/
|
||||
this.TabAwareWidget = NoteContextAwareWidget;
|
||||
|
||||
/**
|
||||
* @property {NoteContextAwareWidget}
|
||||
* @deprecated use NoteContextAwareWidget instead
|
||||
*/
|
||||
this.TabCachingWidget = NoteContextAwareWidget;
|
||||
|
||||
/**
|
||||
* @property {NoteContextAwareWidget}
|
||||
* @deprecated use NoteContextAwareWidget instead
|
||||
*/
|
||||
this.NoteContextCachingWidget = NoteContextAwareWidget;
|
||||
|
||||
/** @property {BasicWidget} */
|
||||
this.BasicWidget = BasicWidget;
|
||||
|
||||
|
@ -58,7 +58,7 @@ async function resolveNotePathToSegments(notePath, hoistedNoteId = 'root', logEr
|
||||
return;
|
||||
}
|
||||
|
||||
child.resortParents();
|
||||
child.sortParents();
|
||||
|
||||
const parents = child.getParentNotes();
|
||||
|
||||
|
@ -172,11 +172,16 @@ export default class NoteRevisionsDialog extends BasicWidget {
|
||||
|
||||
const revisionItem = this.revisionItems.find(r => r.noteRevisionId === noteRevisionId);
|
||||
|
||||
this.$titleButtons.empty();
|
||||
this.$content.empty();
|
||||
|
||||
this.$title.html(revisionItem.title);
|
||||
|
||||
this.renderContentButtons(revisionItem);
|
||||
|
||||
await this.renderContent(revisionItem);
|
||||
}
|
||||
|
||||
renderContentButtons(revisionItem) {
|
||||
this.$titleButtons.empty();
|
||||
|
||||
const $restoreRevisionButton = $('<button class="btn btn-sm" type="button">Restore this revision</button>');
|
||||
|
||||
$restoreRevisionButton.on('click', async () => {
|
||||
@ -222,9 +227,15 @@ export default class NoteRevisionsDialog extends BasicWidget {
|
||||
if (!revisionItem.isProtected || protectedSessionHolder.isProtectedSessionAvailable()) {
|
||||
this.$titleButtons.append($downloadButton);
|
||||
}
|
||||
}
|
||||
|
||||
async renderContent(revisionItem) {
|
||||
this.$content.empty();
|
||||
|
||||
const fullNoteRevision = await server.get(`notes/${revisionItem.noteId}/revisions/${revisionItem.noteRevisionId}`);
|
||||
|
||||
console.log(fullNoteRevision);
|
||||
|
||||
if (revisionItem.type === 'text') {
|
||||
this.$content.html(fullNoteRevision.content);
|
||||
|
||||
@ -233,19 +244,16 @@ export default class NoteRevisionsDialog extends BasicWidget {
|
||||
|
||||
renderMathInElement($content[0], {trust: true});
|
||||
}
|
||||
}
|
||||
else if (revisionItem.type === 'code' || revisionItem.type === 'mermaid') {
|
||||
} else if (revisionItem.type === 'code' || revisionItem.type === 'mermaid') {
|
||||
this.$content.html($("<pre>").text(fullNoteRevision.content));
|
||||
}
|
||||
else if (revisionItem.type === 'image') {
|
||||
} else if (revisionItem.type === 'image') {
|
||||
this.$content.html($("<img>")
|
||||
// reason why we put this inline as base64 is that we do not want to let user copy this
|
||||
// as a URL to be used in a note. Instead, if they copy and paste it into a note, it will be an uploaded as a new note
|
||||
.attr("src", `data:${fullNoteRevision.mime};base64,${fullNoteRevision.content}`)
|
||||
.css("max-width", "100%")
|
||||
.css("max-height", "100%"));
|
||||
}
|
||||
else if (revisionItem.type === 'file') {
|
||||
} else if (revisionItem.type === 'file') {
|
||||
const $table = $("<table cellpadding='10'>")
|
||||
.append($("<tr>").append(
|
||||
$("<th>").text("MIME: "),
|
||||
@ -267,8 +275,7 @@ export default class NoteRevisionsDialog extends BasicWidget {
|
||||
}
|
||||
|
||||
this.$content.html($table);
|
||||
}
|
||||
else if (revisionItem.type === 'canvas') {
|
||||
} 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
|
||||
@ -285,12 +292,11 @@ export default class NoteRevisionsDialog extends BasicWidget {
|
||||
*/
|
||||
const $svgHtml = $(svg).css({maxWidth: "100%", height: "auto"});
|
||||
this.$content.html($('<div>').append($svgHtml));
|
||||
} catch(err) {
|
||||
} catch (err) {
|
||||
console.error("error parsing fullNoteRevision.content as JSON", fullNoteRevision.content, err);
|
||||
this.$content.html($("<div>").text("Error parsing content. Please check console.error() for more details."));
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
this.$content.text("Preview isn't available for this note type.");
|
||||
}
|
||||
}
|
||||
|
@ -13,4 +13,11 @@
|
||||
.relation-map-wrapper {
|
||||
height: 100vh !important;
|
||||
}
|
||||
|
||||
.table thead th,
|
||||
.table td, .table th {
|
||||
/* Fix center vertical alignment of table cells */
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -990,3 +990,9 @@ button.close:hover {
|
||||
textarea {
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
.table thead th,
|
||||
.table td, .table th {
|
||||
/* Fix center vertical alignment of table cells */
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ function addClipping(req) {
|
||||
}
|
||||
|
||||
function createNote(req) {
|
||||
let {title, content, pageUrl, images, clipType} = req.body;
|
||||
let {title, content, pageUrl, images, clipType, labels} = req.body;
|
||||
|
||||
if (!title || !title.trim()) {
|
||||
title = `Clipped note from ${pageUrl}`;
|
||||
@ -100,6 +100,13 @@ function createNote(req) {
|
||||
note.setLabel('pageUrl', pageUrl);
|
||||
note.setLabel('iconClass', 'bx bx-globe');
|
||||
}
|
||||
|
||||
if (labels) {
|
||||
for (const labelName in labels) {
|
||||
const labelValue = htmlSanitizer.sanitize(labels[labelName]);
|
||||
note.setLabel(labelName, labelValue);
|
||||
}
|
||||
}
|
||||
|
||||
const rewrittenContent = processContent(images, note, content);
|
||||
|
||||
|
@ -38,7 +38,7 @@ function run(req) {
|
||||
}
|
||||
|
||||
function getBundlesWithLabel(label, value) {
|
||||
const notes = attributeService.getNotesWithLabelFast(label, value);
|
||||
const notes = attributeService.getNotesWithLabel(label, value);
|
||||
|
||||
const bundles = [];
|
||||
|
||||
|
@ -1 +1 @@
|
||||
module.exports = { buildDate:"2023-02-13T21:50:54+01:00", buildRevision: "17085e5578d2a20a77a6ade058f74e6d5b798ecc" };
|
||||
module.exports = { buildDate:"2023-02-28T23:39:34+01:00", buildRevision: "9eb3075f65af98bd8c4a8ca8cc241ae2ae05bfa9" };
|
||||
|
@ -250,12 +250,6 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
||||
|
||||
const {noteMeta} = getMeta(absUrl);
|
||||
|
||||
if (!noteMeta) {
|
||||
log.info(`Could not find note meta for URL '${absUrl}'.`);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const targetNoteId = getNoteId(noteMeta, absUrl);
|
||||
return targetNoteId;
|
||||
}
|
||||
@ -344,6 +338,9 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
||||
content = content.replace(new RegExp(link.value, "g"), getNewNoteId(link.value));
|
||||
}
|
||||
}
|
||||
|
||||
content = content.trim();
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
|
@ -55,12 +55,22 @@ function deriveMime(type, mime) {
|
||||
}
|
||||
|
||||
function copyChildAttributes(parentNote, childNote) {
|
||||
const hasAlreadyTemplate = childNote.hasRelation('template');
|
||||
|
||||
for (const attr of parentNote.getAttributes()) {
|
||||
if (attr.name.startsWith("child:")) {
|
||||
const name = attr.name.substr(6);
|
||||
|
||||
if (hasAlreadyTemplate && attr.type === 'relation' && name === 'template') {
|
||||
// if the note already has a template, it means the template was chosen by the user explicitly
|
||||
// in the menu. In that case we should override the default templates defined in the child: attrs
|
||||
continue;
|
||||
}
|
||||
|
||||
new BAttribute({
|
||||
noteId: childNote.noteId,
|
||||
type: attr.type,
|
||||
name: attr.name.substr(6),
|
||||
name: name,
|
||||
value: attr.value,
|
||||
position: attr.position,
|
||||
isInheritable: attr.isInheritable
|
||||
@ -194,21 +204,18 @@ function createNewNote(params) {
|
||||
|
||||
asyncPostProcessContent(note, params.content);
|
||||
|
||||
copyChildAttributes(parentNote, note);
|
||||
|
||||
if (params.templateNoteId) {
|
||||
if (!becca.getNote(params.templateNoteId)) {
|
||||
throw new Error(`Template note '${params.templateNoteId}' does not exist.`);
|
||||
}
|
||||
|
||||
// could be already copied from the parent via `child:`, no need to have 2
|
||||
if (!note.hasOwnedRelation('template', params.templateNoteId)) {
|
||||
note.addRelation('template', params.templateNoteId);
|
||||
}
|
||||
note.addRelation('template', params.templateNoteId);
|
||||
|
||||
// no special handling for ~inherit since it doesn't matter if it's assigned with the note creation or later
|
||||
}
|
||||
|
||||
copyChildAttributes(parentNote, note);
|
||||
|
||||
triggerNoteTitleChanged(note);
|
||||
|
||||
eventService.emit(eventService.ENTITY_CREATED, {
|
||||
|
@ -3,8 +3,7 @@ const cls = require('./cls');
|
||||
const sqlInit = require('./sql_init');
|
||||
const config = require('./config');
|
||||
const log = require('./log');
|
||||
const sql = require("./sql");
|
||||
const becca = require("../becca/becca");
|
||||
const attributeService = require("../services/attributes");
|
||||
const protectedSessionService = require("../services/protected_session");
|
||||
const hiddenSubtreeService = require("./hidden_subtree");
|
||||
const userGuideImportService = require("./user_guide_import");
|
||||
@ -21,23 +20,9 @@ function getRunAtHours(note) {
|
||||
}
|
||||
|
||||
function runNotesWithLabel(runAttrValue) {
|
||||
// TODO: should be refactored into becca search
|
||||
const noteIds = sql.getColumn(`
|
||||
SELECT notes.noteId
|
||||
FROM notes
|
||||
JOIN attributes ON attributes.noteId = notes.noteId
|
||||
AND attributes.isDeleted = 0
|
||||
AND attributes.type = 'label'
|
||||
AND attributes.name = 'run'
|
||||
AND attributes.value = ?
|
||||
WHERE
|
||||
notes.type = 'code'
|
||||
AND notes.isDeleted = 0`, [runAttrValue]);
|
||||
|
||||
const notes = becca.getNotes(noteIds);
|
||||
|
||||
const instanceName = config.General ? config.General.instanceName : null;
|
||||
const currentHours = new Date().getHours();
|
||||
const notes = attributeService.getNotesWithLabel('run', runAttrValue);
|
||||
|
||||
for (const note of notes) {
|
||||
const runOnInstances = note.getLabelValues('runOnInstance');
|
||||
|
@ -79,6 +79,10 @@ class NoteContentFulltextExp extends Expression {
|
||||
}
|
||||
}
|
||||
|
||||
if (!content) {
|
||||
return;
|
||||
}
|
||||
|
||||
content = this.preprocessContent(content, type, mime);
|
||||
|
||||
if (this.tokens.length === 1) {
|
||||
@ -108,6 +112,7 @@ class NoteContentFulltextExp extends Expression {
|
||||
resultNoteSet.add(becca.notes[noteId]);
|
||||
}
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
|
@ -28,15 +28,20 @@ class NoteFlatTextExp extends Expression {
|
||||
|
||||
if (retPath) {
|
||||
const noteId = retPath[retPath.length - 1];
|
||||
executionContext.noteIdToNotePath[noteId] = retPath;
|
||||
|
||||
resultNoteSet.add(becca.notes[noteId]);
|
||||
if (!resultNoteSet.hasNoteId(noteId)) {
|
||||
// we could get here from multiple paths, the first one wins because the paths
|
||||
// are sorted by importance
|
||||
executionContext.noteIdToNotePath[noteId] = retPath;
|
||||
|
||||
resultNoteSet.add(becca.notes[noteId]);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!note.parents.length === 0 || note.noteId === 'root') {
|
||||
if (note.parents.length === 0 || note.noteId === 'root') {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -49,9 +54,11 @@ class NoteFlatTextExp extends Expression {
|
||||
}
|
||||
|
||||
for (const attribute of note.ownedAttributes) {
|
||||
const normalizedName = utils.normalize(attribute.name);
|
||||
const normalizedValue = utils.normalize(attribute.value);
|
||||
|
||||
for (const token of tokens) {
|
||||
if (utils.normalize(attribute.name).includes(token)
|
||||
|| utils.normalize(attribute.value).includes(token)) {
|
||||
if (normalizedName.includes(token) || normalizedValue.includes(token)) {
|
||||
foundAttrTokens.push(token);
|
||||
}
|
||||
}
|
||||
@ -70,10 +77,10 @@ class NoteFlatTextExp extends Expression {
|
||||
if (foundTokens.length > 0) {
|
||||
const remainingTokens = tokens.filter(token => !foundTokens.includes(token));
|
||||
|
||||
searchDownThePath(parentNote, remainingTokens, path.concat([note.noteId]));
|
||||
searchDownThePath(parentNote, remainingTokens, [...path, note.noteId]);
|
||||
}
|
||||
else {
|
||||
searchDownThePath(parentNote, tokens, path.concat([note.noteId]));
|
||||
searchDownThePath(parentNote, tokens, [...path, note.noteId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -155,11 +155,8 @@ function findResultsWithExpression(expression, searchContext) {
|
||||
const noteSet = expression.execute(allNoteSet, executionContext, searchContext);
|
||||
|
||||
const searchResults = noteSet.notes
|
||||
.filter(note => !note.isDeleted)
|
||||
.map(note => {
|
||||
if (note.isDeleted) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const notePathArray = executionContext.noteIdToNotePath[note.noteId] || beccaService.getSomePath(note);
|
||||
|
||||
if (!notePathArray) {
|
||||
@ -167,8 +164,7 @@ function findResultsWithExpression(expression, searchContext) {
|
||||
}
|
||||
|
||||
return new SearchResult(notePathArray);
|
||||
})
|
||||
.filter(note => !!note);
|
||||
});
|
||||
|
||||
for (const res of searchResults) {
|
||||
res.computeScore(searchContext.fulltextQuery, searchContext.highlightedTokens);
|
||||
|
@ -52,8 +52,7 @@
|
||||
device = "mobile";
|
||||
}
|
||||
else {
|
||||
// mobile device detection based on https://stackoverflow.com/a/24600597/944162
|
||||
device = /Mobi/.test(navigator.userAgent) ? "mobile" : "desktop";
|
||||
device = isMobile() ? "mobile" : "desktop";
|
||||
}
|
||||
|
||||
console.log("Setting device cookie to:", device);
|
||||
@ -66,6 +65,19 @@
|
||||
|
||||
document.cookie = name + "=" + (value || "") + expires + "; path=/";
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/73731646/944162
|
||||
function isMobile() {
|
||||
if ('maxTouchPoints' in navigator) return navigator.maxTouchPoints > 0;
|
||||
|
||||
const mQ = matchMedia?.('(pointer:coarse)');
|
||||
if (mQ?.media === '(pointer:coarse)') return !!mQ.matches;
|
||||
|
||||
if ('orientation' in window) return true;
|
||||
|
||||
return /\b(BlackBerry|webOS|iPhone|IEMobile)\b/i.test(navigator.userAgent) ||
|
||||
/\b(Android|Windows Phone|iPad|iPod)\b/i.test(navigator.userAgent);
|
||||
}
|
||||
</script>
|
||||
|
||||
<link href="<%= assetPath %>/libraries/bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
||||
|
4
src/www
4
src/www
@ -109,12 +109,12 @@ async function startTrilium() {
|
||||
// handle specific listen errors with friendly messages
|
||||
switch (error.code) {
|
||||
case 'EACCES':
|
||||
console.error(`Port ${port} requires elevated privileges`);
|
||||
console.error(`Port ${port} requires elevated privileges. It's recommended to use port above 1024.`);
|
||||
process.exit(1);
|
||||
break;
|
||||
|
||||
case 'EADDRINUSE':
|
||||
console.error(`Port ${port} is already in use`);
|
||||
console.error(`Port ${port} is already in use. Most likely, another Trilium process is already running. You might try to find it, kill it, and try again.`);
|
||||
process.exit(1);
|
||||
break;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user