Merge branch 'master' into dev

# Conflicts:
#	src/services/app_info.js
This commit is contained in:
zadam 2023-03-08 07:54:23 +01:00
commit 6c4df5110e
34 changed files with 350 additions and 118 deletions

View File

@ -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"/>
## 特性

View File

@ -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).

View File

@ -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"/>
## Возможности

View File

@ -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',

Binary file not shown.

View 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}`);
}
}
});
};

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
View File

@ -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": {

View File

@ -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": {

View File

@ -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;

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -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() {

View File

@ -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;

View File

@ -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);
}

View File

@ -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();
}
}

View File

@ -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;

View File

@ -58,7 +58,7 @@ async function resolveNotePathToSegments(notePath, hoistedNoteId = 'root', logEr
return;
}
child.resortParents();
child.sortParents();
const parents = child.getParentNotes();

View File

@ -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.");
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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}`;
@ -101,6 +101,13 @@ function createNote(req) {
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);
note.setContent(rewrittenContent);

View File

@ -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 = [];

View File

@ -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" };

View File

@ -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;
}

View File

@ -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);
}
// 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, {

View File

@ -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');

View File

@ -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;
}

View File

@ -28,15 +28,20 @@ class NoteFlatTextExp extends Expression {
if (retPath) {
const noteId = retPath[retPath.length - 1];
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]);
}
}
}

View File

@ -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);

View File

@ -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">

View File

@ -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;