diff --git a/.editorconfig b/.editorconfig index cd301498e..cebb2ba58 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,6 +1,6 @@ root = true -[*.{js,ts,.tsx}] +[*.{js,ts,tsx}] charset = utf-8 end_of_line = lf indent_size = 4 diff --git a/apps/client/src/components/app_context.ts b/apps/client/src/components/app_context.ts index d888eba6f..ce33d1447 100644 --- a/apps/client/src/components/app_context.ts +++ b/apps/client/src/components/app_context.ts @@ -116,7 +116,7 @@ export type CommandMappings = { openedFileUpdated: CommandData & { entityType: string; entityId: string; - lastModifiedMs: number; + lastModifiedMs?: number; filePath: string; }; focusAndSelectTitle: CommandData & { @@ -650,7 +650,7 @@ export class AppContext extends Component { } getComponentByEl(el: HTMLElement) { - return $(el).closest(".component").prop("component"); + return $(el).closest("[data-component-id]").prop("component"); } addBeforeUnloadListener(obj: BeforeUploadListener | (() => boolean)) { diff --git a/apps/client/src/components/touch_bar.ts b/apps/client/src/components/touch_bar.ts index 7bf10d7f1..226318a92 100644 --- a/apps/client/src/components/touch_bar.ts +++ b/apps/client/src/components/touch_bar.ts @@ -23,11 +23,11 @@ export default class TouchBarComponent extends Component { this.$widget = $("
"); $(window).on("focusin", async (e) => { - const $target = $(e.target); + const focusedEl = e.target as unknown as HTMLElement; + const $target = $(focusedEl); this.$activeModal = $target.closest(".modal-dialog"); - const parentComponentEl = $target.closest(".component"); - this.lastFocusedComponent = appContext.getComponentByEl(parentComponentEl[0]); + this.lastFocusedComponent = appContext.getComponentByEl(focusedEl); this.#refreshTouchBar(); }); } diff --git a/apps/client/src/entities/fnote.ts b/apps/client/src/entities/fnote.ts index b80e8e3fb..dcb768dd7 100644 --- a/apps/client/src/entities/fnote.ts +++ b/apps/client/src/entities/fnote.ts @@ -256,18 +256,20 @@ export default class FNote { return this.children; } - async getSubtreeNoteIds() { + async getSubtreeNoteIds(includeArchived = false) { let noteIds: (string | string[])[] = []; for (const child of await this.getChildNotes()) { + if (child.isArchived && !includeArchived) continue; + noteIds.push(child.noteId); - noteIds.push(await child.getSubtreeNoteIds()); + noteIds.push(await child.getSubtreeNoteIds(includeArchived)); } return noteIds.flat(); } async getSubtreeNotes() { const noteIds = await this.getSubtreeNoteIds(); - return this.froca.getNotes(noteIds); + return (await this.froca.getNotes(noteIds)); } async getChildNotes() { @@ -905,8 +907,8 @@ export default class FNote { return this.getBlob(); } - async getBlob() { - return await this.froca.getBlob("notes", this.noteId); + getBlob() { + return this.froca.getBlob("notes", this.noteId); } toString() { diff --git a/apps/client/src/layouts/desktop_layout.tsx b/apps/client/src/layouts/desktop_layout.tsx index 530170282..0e9a39caa 100644 --- a/apps/client/src/layouts/desktop_layout.tsx +++ b/apps/client/src/layouts/desktop_layout.tsx @@ -5,7 +5,6 @@ import NoteTreeWidget from "../widgets/note_tree.js"; import NoteTitleWidget from "../widgets/note_title.jsx"; import NoteDetailWidget from "../widgets/note_detail.js"; import PromotedAttributesWidget from "../widgets/promoted_attributes.js"; -import NoteListWidget from "../widgets/note_list.js"; import NoteIconWidget from "../widgets/note_icon.jsx"; import ScrollingContainer from "../widgets/containers/scrolling_container.js"; import RootContainer from "../widgets/containers/root_container.js"; @@ -42,6 +41,7 @@ import LeftPaneToggle from "../widgets/buttons/left_pane_toggle.js"; import ApiLog from "../widgets/api_log.jsx"; import CloseZenModeButton from "../widgets/close_zen_button.jsx"; import SharedInfo from "../widgets/shared_info.jsx"; +import NoteList from "../widgets/collections/NoteList.jsx"; export default class DesktopLayout { @@ -138,7 +138,7 @@ export default class DesktopLayout { .child(new PromotedAttributesWidget()) .child() .child(new NoteDetailWidget()) - .child(new NoteListWidget(false)) + .child() .child() .child() .child() diff --git a/apps/client/src/layouts/layout_commons.tsx b/apps/client/src/layouts/layout_commons.tsx index 02171db60..292006011 100644 --- a/apps/client/src/layouts/layout_commons.tsx +++ b/apps/client/src/layouts/layout_commons.tsx @@ -27,10 +27,10 @@ import FlexContainer from "../widgets/containers/flex_container.js"; import NoteIconWidget from "../widgets/note_icon"; import PromotedAttributesWidget from "../widgets/promoted_attributes.js"; import NoteDetailWidget from "../widgets/note_detail.js"; -import NoteListWidget from "../widgets/note_list.js"; import CallToActionDialog from "../widgets/dialogs/call_to_action.jsx"; import NoteTitleWidget from "../widgets/note_title.jsx"; import { PopupEditorFormattingToolbar } from "../widgets/ribbon/FormattingToolbar.js"; +import NoteList from "../widgets/collections/NoteList.jsx"; export function applyModals(rootContainer: RootContainer) { rootContainer @@ -66,6 +66,6 @@ export function applyModals(rootContainer: RootContainer) { .child() .child(new PromotedAttributesWidget()) .child(new NoteDetailWidget()) - .child(new NoteListWidget(true))) + .child()) .child(); } diff --git a/apps/client/src/layouts/mobile_layout.tsx b/apps/client/src/layouts/mobile_layout.tsx index a324152c9..1bc944418 100644 --- a/apps/client/src/layouts/mobile_layout.tsx +++ b/apps/client/src/layouts/mobile_layout.tsx @@ -5,7 +5,6 @@ import QuickSearchWidget from "../widgets/quick_search.js"; import NoteTreeWidget from "../widgets/note_tree.js"; import ScreenContainer from "../widgets/mobile_widgets/screen_container.js"; import ScrollingContainer from "../widgets/containers/scrolling_container.js"; -import NoteListWidget from "../widgets/note_list.js"; import GlobalMenuWidget from "../widgets/buttons/global_menu.js"; import LauncherContainer from "../widgets/containers/launcher_container.js"; import RootContainer from "../widgets/containers/root_container.js"; @@ -24,6 +23,7 @@ import ToggleSidebarButton from "../widgets/mobile_widgets/toggle_sidebar_button import CloseZenModeButton from "../widgets/close_zen_button.js"; import NoteWrapperWidget from "../widgets/note_wrapper.js"; import MobileDetailMenu from "../widgets/mobile_widgets/mobile_detail_menu.js"; +import NoteList from "../widgets/collections/NoteList.jsx"; const MOBILE_CSS = ` - -
-
-
`; - -export default class NoteListWidget extends NoteContextAwareWidget { - - private $content!: JQuery; - private isIntersecting?: boolean; - private noteIdRefreshed?: string; - private shownNoteId?: string | null; - private viewMode?: ViewMode | null; - private displayOnlyCollections: boolean; - - /** - * @param displayOnlyCollections if set to `true` then only collection-type views are displayed such as geo-map and the calendar. The original book types grid and list will be ignored. - */ - constructor(displayOnlyCollections: boolean) { - super(); - - this.displayOnlyCollections = displayOnlyCollections; - } - - isEnabled() { - if (!super.isEnabled()) { - return false; - } - - if (this.displayOnlyCollections && this.note?.type !== "book") { - const viewType = this.note?.getLabelValue("viewType"); - if (!viewType || ["grid", "list"].includes(viewType)) { - return false; - } - } - - return this.noteContext?.hasNoteList(); - } - - doRender() { - this.$widget = $(TPL); - this.contentSized(); - this.$content = this.$widget.find(".note-list-widget-content"); - - const observer = new IntersectionObserver( - (entries) => { - this.isIntersecting = entries[0].isIntersecting; - - this.checkRenderStatus(); - }, - { - rootMargin: "50px", - threshold: 0.1 - } - ); - - // 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() { - // console.log("this.isIntersecting", this.isIntersecting); - // console.log(`${this.noteIdRefreshed} === ${this.noteId}`, this.noteIdRefreshed === this.noteId); - // console.log("this.shownNoteId !== this.noteId", this.shownNoteId !== this.noteId); - - if (this.note && this.isIntersecting && this.noteIdRefreshed === this.noteId && this.shownNoteId !== this.noteId) { - this.shownNoteId = this.noteId; - this.renderNoteList(this.note); - } - } - - async renderNoteList(note: FNote) { - const noteListRenderer = new NoteListRenderer({ - $parent: this.$content, - parentNote: note, - parentNotePath: this.notePath - }); - this.$widget.toggleClass("full-height", noteListRenderer.isFullHeight); - await noteListRenderer.renderList(); - this.viewMode = noteListRenderer.viewMode; - } - - async refresh() { - this.shownNoteId = null; - - await super.refresh(); - } - - async refreshNoteListEvent({ noteId }: EventData<"refreshNoteList">) { - if (this.isNote(noteId) && this.note) { - await this.renderNoteList(this.note); - } - } - - /** - * We have this event so that we evaluate intersection only after note detail is loaded. - * If it's evaluated before note detail, then it's clearly intersected (visible) although after note detail load - * it is not intersected (visible) anymore. - */ - noteDetailRefreshedEvent({ ntxId }: EventData<"noteDetailRefreshed">) { - if (!this.isNoteContext(ntxId)) { - return; - } - - this.noteIdRefreshed = this.noteId; - - setTimeout(() => this.checkRenderStatus(), 100); - } - - notesReloadedEvent({ noteIds }: EventData<"notesReloaded">) { - if (this.noteId && noteIds.includes(this.noteId)) { - this.refresh(); - } - } - - entitiesReloadedEvent(e: EventData<"entitiesReloaded">) { - if (e.loadResults.getAttributeRows().find((attr) => attr.noteId === this.noteId && attr.name && ["viewType", "expanded", "pageSize"].includes(attr.name))) { - this.refresh(); - this.checkRenderStatus(); - } - } - - buildTouchBarCommand(data: CommandListenerData<"buildTouchBar">) { - if (this.viewMode && "buildTouchBarCommand" in this.viewMode) { - return (this.viewMode as CommandListener<"buildTouchBar">).buildTouchBarCommand(data); - } - } - - triggerCommand(name: K, data?: CommandMappings[K]): Promise | undefined | null { - // Pass the commands to the view mode, which is not actually attached to the hierarchy. - if (this.viewMode?.triggerCommand(name, data)) { - return; - } - - return super.triggerCommand(name, data); - } - - handleEventInChildren(name: T, data: EventData): Promise | null { - super.handleEventInChildren(name, data); - - if (this.viewMode) { - const ret = this.viewMode.handleEvent(name, data); - if (ret) { - return ret; - } - } - - return null; - } - -} diff --git a/apps/client/src/widgets/note_tree.ts b/apps/client/src/widgets/note_tree.ts index 2bbee7b36..4636cd60d 100644 --- a/apps/client/src/widgets/note_tree.ts +++ b/apps/client/src/widgets/note_tree.ts @@ -195,6 +195,8 @@ export interface DragData { title: string; } +export const TREE_CLIPBOARD_TYPE = "application/x-fancytree-node"; + export default class NoteTreeWidget extends NoteContextAwareWidget { private $tree!: JQuery; private $treeActions!: JQuery; diff --git a/apps/client/src/widgets/react/ActionButton.tsx b/apps/client/src/widgets/react/ActionButton.tsx index 5e6f3266b..2eb69bab8 100644 --- a/apps/client/src/widgets/react/ActionButton.tsx +++ b/apps/client/src/widgets/react/ActionButton.tsx @@ -11,18 +11,19 @@ export interface ActionButtonProps { onClick?: (e: MouseEvent) => void; triggerCommand?: CommandNames; noIconActionClass?: boolean; + frame?: boolean; } -export default function ActionButton({ text, icon, className, onClick, triggerCommand, titlePosition, noIconActionClass }: ActionButtonProps) { +export default function ActionButton({ text, icon, className, onClick, triggerCommand, titlePosition, noIconActionClass, frame }: ActionButtonProps) { const buttonRef = useRef(null); const [ keyboardShortcut, setKeyboardShortcut ] = useState(); - + useStaticTooltip(buttonRef, { title: keyboardShortcut?.length ? `${text} (${keyboardShortcut?.join(",")})` : text, placement: titlePosition ?? "bottom", fallbackPlacements: [ titlePosition ?? "bottom" ] }); - + useEffect(() => { if (triggerCommand) { keyboard_actions.getAction(triggerCommand, true).then(action => setKeyboardShortcut(action?.effectiveShortcuts)); @@ -31,8 +32,8 @@ export default function ActionButton({ text, icon, className, onClick, triggerCo return - - - `.trimStart(); -} diff --git a/apps/client/src/widgets/view_widgets/table_view/formatters.ts b/apps/client/src/widgets/view_widgets/table_view/formatters.ts deleted file mode 100644 index a333742e9..000000000 --- a/apps/client/src/widgets/view_widgets/table_view/formatters.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { CellComponent } from "tabulator-tables"; -import froca from "../../../services/froca.js"; -import FNote from "../../../entities/fnote.js"; - -/** - * Custom formatter to represent a note, with the icon and note title being rendered. - * - * The value of the cell must be the note ID. - */ -export function NoteFormatter(cell: CellComponent, _formatterParams, onRendered): string { - let noteId = cell.getValue(); - if (!noteId) { - return ""; - } - - function buildLink(note: FNote | undefined) { - if (!note) { - return; - } - - const iconClass = note.getIcon(); - const title = note.title; - const { $noteRef } = buildNoteLink(noteId, title, iconClass, note.getColorClass()); - return $noteRef[0]; - } - - const cachedNote = froca.getNoteFromCache(noteId); - if (cachedNote) { - // Cache hit, build the link immediately - const el = buildLink(cachedNote); - return el?.outerHTML ?? ""; - } else { - // Cache miss, load the note asynchronously - onRendered(async () => { - const note = await froca.getNote(noteId); - if (!note) { - return; - } - - const el = buildLink(note); - if (el) { - cell.getElement().appendChild(el); - } - }); - - return ""; - } -} - -/** - * Custom formatter for the note title that is quite similar to {@link NoteFormatter}, but where the title and icons are read from separate fields. - */ -export function NoteTitleFormatter(cell: CellComponent) { - const { noteId, iconClass, colorClass } = cell.getRow().getData(); - if (!noteId) { - return ""; - } - - const { $noteRef } = buildNoteLink(noteId, cell.getValue(), iconClass, colorClass); - return $noteRef[0].outerHTML; -} - -export function RowNumberFormatter(draggableRows: boolean) { - return (cell: CellComponent) => { - let html = ""; - if (draggableRows) { - html += ` `; - } - html += cell.getRow().getPosition(true); - return html; - }; -} - -export function MonospaceFormatter(cell: CellComponent) { - return `${cell.getValue()}`; -} - -function buildNoteLink(noteId: string, title: string, iconClass: string, colorClass?: string) { - const $noteRef = $(""); - const href = `#root/${noteId}`; - $noteRef.addClass("reference-link"); - $noteRef.attr("data-href", href); - $noteRef.text(title); - $noteRef.prepend($("").addClass(iconClass)); - if (colorClass) { - $noteRef.addClass(colorClass); - } - return { $noteRef, href }; -} diff --git a/apps/client/src/widgets/view_widgets/table_view/index.ts b/apps/client/src/widgets/view_widgets/table_view/index.ts deleted file mode 100644 index 0dd47b27a..000000000 --- a/apps/client/src/widgets/view_widgets/table_view/index.ts +++ /dev/null @@ -1,272 +0,0 @@ -import ViewMode, { type ViewModeArgs } from "../view_mode.js"; -import attributes from "../../../services/attributes.js"; -import SpacedUpdate from "../../../services/spaced_update.js"; -import type { EventData } from "../../../components/app_context.js"; -import {Tabulator, SortModule, FormatModule, InteractionModule, EditModule, ResizeColumnsModule, FrozenColumnsModule, PersistenceModule, MoveColumnsModule, MoveRowsModule, ColumnDefinition, DataTreeModule, Options, RowComponent, ColumnComponent} from 'tabulator-tables'; -import "tabulator-tables/dist/css/tabulator.css"; -import "../../../../src/stylesheets/table.css"; -import { canReorderRows, configureReorderingRows } from "./dragging.js"; -import buildFooter from "./footer.js"; -import getAttributeDefinitionInformation, { buildRowDefinitions } from "./rows.js"; -import { AttributeDefinitionInformation, buildColumnDefinitions } from "./columns.js"; -import { setupContextMenu } from "./context_menu.js"; -import TableColumnEditing from "./col_editing.js"; -import TableRowEditing from "./row_editing.js"; - -const TPL = /*html*/` -
- - -
-
-`; - -export interface StateInfo { - tableData?: { - columns?: ColumnDefinition[]; - }; -} - -export default class TableView extends ViewMode { - - private $root: JQuery; - private $container: JQuery; - private spacedUpdate: SpacedUpdate; - private api?: Tabulator; - private persistentData: StateInfo["tableData"]; - private colEditing?: TableColumnEditing; - private rowEditing?: TableRowEditing; - private maxDepth: number = -1; - private rowNumberHint: number = 1; - - constructor(args: ViewModeArgs) { - super(args, "table"); - - this.$root = $(TPL); - this.$container = this.$root.find(".table-view-container"); - this.spacedUpdate = new SpacedUpdate(() => this.onSave(), 5_000); - this.persistentData = {}; - args.$parent.append(this.$root); - } - - async renderList() { - this.$container.empty(); - this.renderTable(this.$container[0]); - return this.$root; - } - - private async renderTable(el: HTMLElement) { - const info = getAttributeDefinitionInformation(this.parentNote); - const modules = [ SortModule, FormatModule, InteractionModule, EditModule, ResizeColumnsModule, FrozenColumnsModule, PersistenceModule, MoveColumnsModule, MoveRowsModule, DataTreeModule ]; - for (const module of modules) { - Tabulator.registerModule(module); - } - - this.initialize(el, info); - } - - private async initialize(el: HTMLElement, info: AttributeDefinitionInformation[]) { - const viewStorage = await this.viewStorage.restore(); - this.persistentData = viewStorage?.tableData || {}; - - this.maxDepth = parseInt(this.parentNote.getLabelValue("maxNestingDepth") ?? "-1", 10); - const { definitions: rowData, hasSubtree: hasChildren, rowNumber } = await buildRowDefinitions(this.parentNote, info, this.maxDepth); - this.rowNumberHint = rowNumber; - const movableRows = canReorderRows(this.parentNote) && !hasChildren; - const columnDefs = buildColumnDefinitions({ - info, - movableRows, - existingColumnData: this.persistentData.columns, - rowNumberHint: this.rowNumberHint - }); - let opts: Options = { - layout: "fitDataFill", - index: "branchId", - columns: columnDefs, - data: rowData, - persistence: true, - movableColumns: true, - movableRows, - footerElement: buildFooter(this.parentNote), - persistenceWriterFunc: (_id, type: string, data: object) => { - (this.persistentData as Record)[type] = data; - this.spacedUpdate.scheduleUpdate(); - }, - persistenceReaderFunc: (_id, type: string) => this.persistentData?.[type], - }; - - if (hasChildren) { - opts = { - ...opts, - dataTree: hasChildren, - dataTreeStartExpanded: true, - dataTreeBranchElement: false, - dataTreeElementColumn: "title", - dataTreeChildIndent: 20, - dataTreeExpandElement: ``, - dataTreeCollapseElement: `` - } - } - - this.api = new Tabulator(el, opts); - - this.colEditing = new TableColumnEditing(this.args.$parent, this.args.parentNote, this.api); - this.rowEditing = new TableRowEditing(this.api, this.args.parentNotePath!); - - if (movableRows) { - configureReorderingRows(this.api); - } - setupContextMenu(this.api, this.parentNote); - } - - private onSave() { - this.viewStorage.store({ - tableData: this.persistentData, - }); - } - - async onEntitiesReloaded({ loadResults }: EventData<"entitiesReloaded">) { - if (!this.api) { - return; - } - - // Force a refresh if sorted is changed since we need to disable reordering. - if (loadResults.getAttributeRows().find(a => a.name === "sorted" && attributes.isAffecting(a, this.parentNote))) { - return true; - } - - // Refresh if promoted attributes get changed. - if (loadResults.getAttributeRows().find(attr => - attr.type === "label" && - (attr.name?.startsWith("label:") || attr.name?.startsWith("relation:")) && - attributes.isAffecting(attr, this.parentNote))) { - this.#manageColumnUpdate(); - return await this.#manageRowsUpdate(); - } - - // Refresh max depth - if (loadResults.getAttributeRows().find(attr => attr.type === "label" && attr.name === "maxNestingDepth" && attributes.isAffecting(attr, this.parentNote))) { - this.maxDepth = parseInt(this.parentNote.getLabelValue("maxNestingDepth") ?? "-1", 10); - return await this.#manageRowsUpdate(); - } - - if (loadResults.getBranchRows().some(branch => branch.parentNoteId === this.parentNote.noteId || this.noteIds.includes(branch.parentNoteId ?? "")) - || loadResults.getNoteIds().some(noteId => this.noteIds.includes(noteId)) - || loadResults.getAttributeRows().some(attr => this.noteIds.includes(attr.noteId!))) { - return await this.#manageRowsUpdate(); - } - - return false; - } - - #manageColumnUpdate() { - if (!this.api) { - return; - } - - const info = getAttributeDefinitionInformation(this.parentNote); - const columnDefs = buildColumnDefinitions({ - info, - movableRows: !!this.api.options.movableRows, - existingColumnData: this.persistentData?.columns, - rowNumberHint: this.rowNumberHint, - position: this.colEditing?.getNewAttributePosition() - }); - this.api.setColumns(columnDefs); - this.colEditing?.resetNewAttributePosition(); - } - - addNewRowCommand(e) { this.rowEditing?.addNewRowCommand(e); } - addNewTableColumnCommand(e) { this.colEditing?.addNewTableColumnCommand(e); } - deleteTableColumnCommand(e) { this.colEditing?.deleteTableColumnCommand(e); } - updateAttributeListCommand(e) { this.colEditing?.updateAttributeListCommand(e); } - saveAttributesCommand() { this.colEditing?.saveAttributesCommand(); } - - async #manageRowsUpdate() { - if (!this.api) { - return; - } - - const info = getAttributeDefinitionInformation(this.parentNote); - const { definitions, hasSubtree, rowNumber } = await buildRowDefinitions(this.parentNote, info, this.maxDepth); - this.rowNumberHint = rowNumber; - - // Force a refresh if the data tree needs enabling/disabling. - if (this.api.options.dataTree !== hasSubtree) { - return true; - } - - await this.api.replaceData(definitions); - return false; - } - -} - diff --git a/apps/client/src/widgets/view_widgets/table_view/relation_editor.ts b/apps/client/src/widgets/view_widgets/table_view/relation_editor.ts deleted file mode 100644 index 8e948cc2a..000000000 --- a/apps/client/src/widgets/view_widgets/table_view/relation_editor.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { CellComponent } from "tabulator-tables"; -import note_autocomplete from "../../../services/note_autocomplete"; -import froca from "../../../services/froca"; - -export function RelationEditor(cell: CellComponent, onRendered, success, cancel, editorParams){ - //cell - the cell component for the editable cell - //onRendered - function to call when the editor has been rendered - //success - function to call to pass thesuccessfully updated value to Tabulator - //cancel - function to call to abort the edit and return to a normal cell - //editorParams - params object passed into the editorParams column definition property - - //create and style editor - const editor = document.createElement("input"); - - const $editor = $(editor); - editor.classList.add("form-control"); - - //create and style input - editor.style.padding = "3px"; - editor.style.width = "100%"; - editor.style.boxSizing = "border-box"; - - //Set value of editor to the current value of the cell - const originalNoteId = cell.getValue(); - if (originalNoteId) { - const note = froca.getNoteFromCache(originalNoteId); - editor.value = note.title; - } else { - editor.value = ""; - } - - //set focus on the select box when the editor is selected - onRendered(function(){ - let newNoteId = originalNoteId; - - note_autocomplete.initNoteAutocomplete($editor, { - allowCreatingNotes: true, - hideAllButtons: true - }).on("autocomplete:noteselected", (event, suggestion, dataset) => { - const notePath = suggestion.notePath; - newNoteId = (notePath ?? "").split("/").at(-1); - }).on("blur", () => { - if (!editor.value) { - newNoteId = ""; - } - success(newNoteId); - }); - editor.focus(); - }); - - const container = document.createElement("div"); - container.classList.add("input-group"); - container.classList.add("autocomplete"); - container.appendChild(editor); - return container; -}; diff --git a/apps/client/src/widgets/view_widgets/table_view/row_editing.ts b/apps/client/src/widgets/view_widgets/table_view/row_editing.ts deleted file mode 100644 index 92b0eeea4..000000000 --- a/apps/client/src/widgets/view_widgets/table_view/row_editing.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { RowComponent, Tabulator } from "tabulator-tables"; -import Component from "../../../components/component.js"; -import { setAttribute, setLabel } from "../../../services/attributes.js"; -import server from "../../../services/server.js"; -import froca from "../../../services/froca.js"; -import note_create, { CreateNoteOpts } from "../../../services/note_create.js"; -import { CommandListenerData } from "../../../components/app_context.js"; - -export default class TableRowEditing extends Component { - - private parentNotePath: string; - private api: Tabulator; - - constructor(api: Tabulator, parentNotePath: string) { - super(); - this.api = api; - this.parentNotePath = parentNotePath; - api.on("cellEdited", async (cell) => { - const noteId = cell.getRow().getData().noteId; - const field = cell.getField(); - let newValue = cell.getValue(); - - if (field === "title") { - server.put(`notes/${noteId}/title`, { title: newValue }); - return; - } - - if (field.includes(".")) { - const [ type, name ] = field.split(".", 2); - if (type === "labels") { - if (typeof newValue === "boolean") { - newValue = newValue ? "true" : "false"; - } - setLabel(noteId, name, newValue); - } else if (type === "relations") { - const note = await froca.getNote(noteId); - if (note) { - setAttribute(note, "relation", name, newValue); - } - } - } - }); - } - - addNewRowCommand({ customOpts, parentNotePath: customNotePath }: CommandListenerData<"addNewRow">) { - const parentNotePath = customNotePath ?? this.parentNotePath; - if (parentNotePath) { - const opts: CreateNoteOpts = { - activate: false, - ...customOpts - } - note_create.createNote(parentNotePath, opts).then(({ branch }) => { - if (branch) { - setTimeout(() => { - this.focusOnBranch(branch?.branchId); - }); - } - }) - } - } - - focusOnBranch(branchId: string) { - if (!this.api) { - return; - } - - const row = findRowDataById(this.api.getRows(), branchId); - if (!row) { - return; - } - - // Expand the parent tree if any. - if (this.api.options.dataTree) { - const parent = row.getTreeParent(); - if (parent) { - parent.treeExpand(); - } - } - - row.getCell("title").edit(); - } - -} - -function findRowDataById(rows: RowComponent[], branchId: string): RowComponent | null { - for (let row of rows) { - const item = row.getIndex() as string; - - if (item === branchId) { - return row; - } - - let found = findRowDataById(row.getTreeChildren(), branchId); - if (found) return found; - } - return null; -} diff --git a/apps/client/src/widgets/view_widgets/view_mode.ts b/apps/client/src/widgets/view_widgets/view_mode.ts deleted file mode 100644 index 4755294f8..000000000 --- a/apps/client/src/widgets/view_widgets/view_mode.ts +++ /dev/null @@ -1,83 +0,0 @@ -import type { EventData } from "../../components/app_context.js"; -import appContext from "../../components/app_context.js"; -import Component from "../../components/component.js"; -import type FNote from "../../entities/fnote.js"; -import type { ViewTypeOptions } from "../../services/note_list_renderer.js"; -import ViewModeStorage from "./view_mode_storage.js"; - -export interface ViewModeArgs { - $parent: JQuery; - parentNote: FNote; - parentNotePath?: string | null; - showNotePath?: boolean; -} - -export default abstract class ViewMode extends Component { - - private _viewStorage: ViewModeStorage | null; - protected parentNote: FNote; - protected viewType: ViewTypeOptions; - protected noteIds: string[]; - protected args: ViewModeArgs; - - constructor(args: ViewModeArgs, viewType: ViewTypeOptions) { - super(); - this.parentNote = args.parentNote; - this._viewStorage = null; - // note list must be added to the DOM immediately, otherwise some functionality scripting (canvas) won't work - args.$parent.empty(); - this.viewType = viewType; - this.args = args; - this.noteIds = []; - } - - async beforeRender() { - await this.#refreshNoteIds(); - } - - abstract renderList(): Promise | undefined>; - - /** - * Called whenever an "entitiesReloaded" event has been received by the parent component. - * - * @param e the event data. - * @return {@code true} if the view should be re-rendered, a falsy value otherwise. - */ - async onEntitiesReloaded(e: EventData<"entitiesReloaded">): Promise { - // Do nothing by default. - } - - async entitiesReloadedEvent(e: EventData<"entitiesReloaded">) { - if (e.loadResults.getBranchRows().some(branch => branch.parentNoteId === this.parentNote.noteId || this.noteIds.includes(branch.parentNoteId ?? ""))) { - this.#refreshNoteIds(); - } - - if (await this.onEntitiesReloaded(e)) { - appContext.triggerEvent("refreshNoteList", { noteId: this.parentNote.noteId }); - } - } - - get isReadOnly() { - return this.parentNote.hasLabel("readOnly"); - } - - get viewStorage() { - if (this._viewStorage) { - return this._viewStorage; - } - - this._viewStorage = new ViewModeStorage(this.parentNote, this.viewType); - return this._viewStorage; - } - - async #refreshNoteIds() { - let noteIds: string[]; - if (this.viewType === "list" || this.viewType === "grid") { - noteIds = this.args.parentNote.getChildNoteIds(); - } else { - noteIds = await this.args.parentNote.getSubtreeNoteIds(); - } - this.noteIds = noteIds; - } - -} diff --git a/apps/client/src/widgets/watched_file_update_status.ts b/apps/client/src/widgets/watched_file_update_status.ts index 5181c333b..efec3d2af 100644 --- a/apps/client/src/widgets/watched_file_update_status.ts +++ b/apps/client/src/widgets/watched_file_update_status.ts @@ -73,13 +73,13 @@ export default class WatchedFileUpdateStatusWidget extends NoteContextAwareWidge async refreshWithNote(note: FNote) { const { entityType, entityId } = this.getEntity(); - if (!entityType || !entityId) { - return; - } + if (!entityType || !entityId) return; const status = fileWatcher.getFileModificationStatus(entityType, entityId); this.$filePath.text(status.filePath); - this.$fileLastModified.text(dayjs.unix(status.lastModifiedMs / 1000).format("HH:mm:ss")); + if (status.lastModifiedMs) { + this.$fileLastModified.text(dayjs.unix(status.lastModifiedMs / 1000).format("HH:mm:ss")); + } } getEntity() { diff --git a/apps/client/src/widgets/widget_utils.ts b/apps/client/src/widgets/widget_utils.ts index f27fe6814..ba48a38df 100644 --- a/apps/client/src/widgets/widget_utils.ts +++ b/apps/client/src/widgets/widget_utils.ts @@ -7,12 +7,15 @@ import utils from "../services/utils.js"; */ export function setupHorizontalScrollViaWheel($container: JQuery) { $container.on("wheel", (event) => { - const wheelEvent = event.originalEvent as WheelEvent; - if (utils.isCtrlKey(event) || event.altKey || event.shiftKey) { - return; - } - event.preventDefault(); - event.stopImmediatePropagation(); - event.currentTarget.scrollLeft += wheelEvent.deltaY + wheelEvent.deltaX; + onWheelHorizontalScroll(event.originalEvent as WheelEvent); }); } + +export function onWheelHorizontalScroll(event: WheelEvent) { + if (!event.currentTarget || utils.isCtrlKey(event) || event.altKey || event.shiftKey) { + return; + } + event.preventDefault(); + event.stopImmediatePropagation(); + (event.currentTarget as HTMLElement).scrollLeft += event.deltaY + event.deltaX; +} diff --git a/apps/client/tsconfig.spec.json b/apps/client/tsconfig.spec.json index dedbb0293..152d9cb27 100644 --- a/apps/client/tsconfig.spec.json +++ b/apps/client/tsconfig.spec.json @@ -8,6 +8,9 @@ "node", "vitest" ], + "jsx": "preserve", + "jsxFactory": "h", + "jsxImportSource": "preact", "module": "esnext", "moduleResolution": "bundler" }, diff --git a/apps/edit-docs/src/utils.ts b/apps/edit-docs/src/utils.ts index 28740f3bd..8a03ef6a0 100644 --- a/apps/edit-docs/src/utils.ts +++ b/apps/edit-docs/src/utils.ts @@ -52,7 +52,7 @@ export function startElectron(callback: () => void): DeferredPromise { export async function importData(path: string) { const buffer = await createImportZip(path); const importService = (await import("@triliumnext/server/src/services/import/zip.js")).default; - const context = new TaskContext("no-progress-reporting", "import", false); + const context = new TaskContext("no-progress-reporting", "importNotes", null); const becca = (await import("@triliumnext/server/src/becca/becca.js")).default; const rootNote = becca.getRoot(); diff --git a/apps/server/src/assets/doc_notes/en/User Guide/!!!meta.json b/apps/server/src/assets/doc_notes/en/User Guide/!!!meta.json index b41d39363..bd6d49aab 100644 --- a/apps/server/src/assets/doc_notes/en/User Guide/!!!meta.json +++ b/apps/server/src/assets/doc_notes/en/User Guide/!!!meta.json @@ -1 +1 @@ -[{"id":"_help_BOCnjTMBCoxW","title":"Feature Highlights","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Feature Highlights"},{"name":"iconClass","value":"bx bx-star","type":"label"}]},{"id":"_help_Otzi9La2YAUX","title":"Installation & Setup","type":"book","attributes":[{"name":"iconClass","value":"bx bx-cog","type":"label"}],"children":[{"id":"_help_poXkQfguuA0U","title":"Desktop Installation","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Desktop Installation"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_WOcw2SLH6tbX","title":"Server Installation","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"children":[{"id":"_help_Dgg7bR3b6K9j","title":"1. Installing the server","type":"book","attributes":[{"name":"iconClass","value":"bx bx-folder","type":"label"}],"children":[{"id":"_help_3tW6mORuTHnB","title":"Packaged version for Linux","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Packaged version for Linux"},{"name":"iconClass","value":"bx bxl-tux","type":"label"}]},{"id":"_help_rWX5eY045zbE","title":"Using Docker","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Using Docker"},{"name":"iconClass","value":"bx bxl-docker","type":"label"}]},{"id":"_help_moVgBcoxE3EK","title":"On NixOS","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/On NixOS"},{"name":"iconClass","value":"bx bxl-tux","type":"label"}]},{"id":"_help_J1Bb6lVlwU5T","title":"Manually","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Manually"},{"name":"iconClass","value":"bx bx-code-alt","type":"label"}]},{"id":"_help_DCmT6e7clMoP","title":"Using Kubernetes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Using Kubernetes"},{"name":"iconClass","value":"bx bxl-kubernetes","type":"label"}]},{"id":"_help_klCWNks3ReaQ","title":"Multiple server instances","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Multiple server instances"},{"name":"iconClass","value":"bx bxs-user-account","type":"label"}]}]},{"id":"_help_vcjrb3VVYPZI","title":"2. Reverse proxy","type":"book","attributes":[{"name":"iconClass","value":"bx bx-folder","type":"label"}],"children":[{"id":"_help_ud6MShXL4WpO","title":"Nginx","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/2. Reverse proxy/Nginx"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_fDLvzOx29Pfg","title":"Apache","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/2. Reverse proxy/Apache"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_l2VkvOwUNfZj","title":"TLS Configuration","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/TLS Configuration"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_0hzsNCP31IAB","title":"Authentication","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/Authentication"},{"name":"iconClass","value":"bx bx-lock-alt","type":"label"}]},{"id":"_help_7DAiwaf8Z7Rz","title":"Multi-Factor Authentication","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/Multi-Factor Authentication"},{"name":"iconClass","value":"bx bx-stopwatch","type":"label"}]}]},{"id":"_help_cbkrhQjrkKrh","title":"Synchronization","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Synchronization"},{"name":"iconClass","value":"bx bx-sync","type":"label"}]},{"id":"_help_RDslemsQ6gCp","title":"Mobile Frontend","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Mobile Frontend"},{"name":"iconClass","value":"bx bx-mobile-alt","type":"label"}]},{"id":"_help_MtPxeAWVAzMg","title":"Web Clipper","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Web Clipper"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_n1lujUxCwipy","title":"Upgrading TriliumNext","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Upgrading TriliumNext"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_ODY7qQn5m2FT","title":"Backup","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Backup"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_tAassRL4RSQL","title":"Data directory","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Data directory"},{"name":"iconClass","value":"bx bx-folder-open","type":"label"}]}]},{"id":"_help_gh7bpGYxajRS","title":"Basic Concepts and Features","type":"book","attributes":[{"name":"iconClass","value":"bx bx-help-circle","type":"label"}],"children":[{"id":"_help_Vc8PjrjAGuOp","title":"UI Elements","type":"book","attributes":[{"name":"iconClass","value":"bx bx-window-alt","type":"label"}],"children":[{"id":"_help_x0JgW8UqGXvq","title":"Vertical and horizontal layout","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Vertical and horizontal layout"},{"name":"iconClass","value":"bx bxs-layout","type":"label"}]},{"id":"_help_x3i7MxGccDuM","title":"Global menu","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Global menu"},{"name":"iconClass","value":"bx bx-menu","type":"label"}]},{"id":"_help_oPVyFC7WL2Lp","title":"Note Tree","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tree"},{"name":"iconClass","value":"bx bxs-tree-alt","type":"label"}],"children":[{"id":"_help_YtSN43OrfzaA","title":"Note tree contextual menu","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tree/Note tree contextual menu"},{"name":"iconClass","value":"bx bx-menu","type":"label"}]},{"id":"_help_yTjUdsOi4CIE","title":"Multiple selection","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tree/Multiple selection"},{"name":"iconClass","value":"bx bx-list-plus","type":"label"}]},{"id":"_help_DvdZhoQZY9Yd","title":"Keyboard shortcuts","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tree/Keyboard shortcuts"},{"name":"iconClass","value":"bx bxs-keyboard","type":"label"}]}]},{"id":"_help_BlN9DFI679QC","title":"Ribbon","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Ribbon"},{"name":"iconClass","value":"bx bx-dots-horizontal","type":"label"}]},{"id":"_help_3seOhtN8uLIY","title":"Tabs","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Tabs"},{"name":"iconClass","value":"bx bx-dock-top","type":"label"}]},{"id":"_help_xYmIYSP6wE3F","title":"Launch Bar","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Launch Bar"},{"name":"iconClass","value":"bx bx-sidebar","type":"label"}]},{"id":"_help_8YBEPzcpUgxw","title":"Note buttons","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note buttons"},{"name":"iconClass","value":"bx bx-dots-vertical-rounded","type":"label"}]},{"id":"_help_4TIF1oA4VQRO","title":"Options","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Options"},{"name":"iconClass","value":"bx bx-cog","type":"label"}]},{"id":"_help_luNhaphA37EO","title":"Split View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Split View"},{"name":"iconClass","value":"bx bx-dock-right","type":"label"}]},{"id":"_help_XpOYSgsLkTJy","title":"Floating buttons","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Floating buttons"},{"name":"iconClass","value":"bx bx-rectangle","type":"label"}]},{"id":"_help_RnaPdbciOfeq","title":"Right Sidebar","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Right Sidebar"},{"name":"iconClass","value":"bx bxs-dock-right","type":"label"}]},{"id":"_help_r5JGHN99bVKn","title":"Recent Changes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Recent Changes"},{"name":"iconClass","value":"bx bx-history","type":"label"}]},{"id":"_help_ny318J39E5Z0","title":"Zoom","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Zoom"},{"name":"iconClass","value":"bx bx-zoom-in","type":"label"}]},{"id":"_help_lgKX7r3aL30x","title":"Note Tooltip","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tooltip"},{"name":"iconClass","value":"bx bx-message-detail","type":"label"}]}]},{"id":"_help_BFs8mudNFgCS","title":"Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes"},{"name":"iconClass","value":"bx bx-notepad","type":"label"}],"children":[{"id":"_help_p9kXRFAkwN4o","title":"Note Icons","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Note Icons"},{"name":"iconClass","value":"bx bxs-grid","type":"label"}]},{"id":"_help_0vhv7lsOLy82","title":"Attachments","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Attachments"},{"name":"iconClass","value":"bx bx-paperclip","type":"label"}]},{"id":"_help_IakOLONlIfGI","title":"Cloning Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Cloning Notes"},{"name":"iconClass","value":"bx bx-duplicate","type":"label"}],"children":[{"id":"_help_TBwsyfadTA18","title":"Branch prefix","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Cloning Notes/Branch prefix"},{"name":"iconClass","value":"bx bx-rename","type":"label"}]}]},{"id":"_help_bwg0e8ewQMak","title":"Protected Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Protected Notes"},{"name":"iconClass","value":"bx bx-lock-alt","type":"label"}]},{"id":"_help_MKmLg5x6xkor","title":"Archived Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Archived Notes"},{"name":"iconClass","value":"bx bx-box","type":"label"}]},{"id":"_help_vZWERwf8U3nx","title":"Note Revisions","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Note Revisions"},{"name":"iconClass","value":"bx bx-history","type":"label"}]},{"id":"_help_aGlEvb9hyDhS","title":"Sorting Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Sorting Notes"},{"name":"iconClass","value":"bx bx-sort-up","type":"label"}]},{"id":"_help_NRnIZmSMc5sj","title":"Export as PDF","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Export as PDF"},{"name":"iconClass","value":"bx bxs-file-pdf","type":"label"}]},{"id":"_help_CoFPLs3dRlXc","title":"Read-Only Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Read-Only Notes"},{"name":"iconClass","value":"bx bx-edit-alt","type":"label"}]},{"id":"_help_0ESUbbAxVnoK","title":"Note List","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Note List"},{"name":"iconClass","value":"bx bxs-grid","type":"label"}]}]},{"id":"_help_wArbEsdSae6g","title":"Navigation","type":"book","attributes":[{"name":"iconClass","value":"bx bx-navigation","type":"label"}],"children":[{"id":"_help_kBrnXNG3Hplm","title":"Tree Concepts","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Tree Concepts"},{"name":"iconClass","value":"bx bx-pyramid","type":"label"}]},{"id":"_help_MMiBEQljMQh2","title":"Note Navigation","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Note Navigation"},{"name":"iconClass","value":"bx bxs-navigation","type":"label"}]},{"id":"_help_Ms1nauBra7gq","title":"Quick search","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Quick search"},{"name":"iconClass","value":"bx bx-search-alt-2","type":"label"}]},{"id":"_help_F1r9QtzQLZqm","title":"Jump to...","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Jump to"},{"name":"iconClass","value":"bx bx-send","type":"label"}]},{"id":"_help_eIg8jdvaoNNd","title":"Search","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Search"},{"name":"iconClass","value":"bx bx-search-alt-2","type":"label"}]},{"id":"_help_u3YFHC9tQlpm","title":"Bookmarks","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Bookmarks"},{"name":"iconClass","value":"bx bx-bookmarks","type":"label"}]},{"id":"_help_OR8WJ7Iz9K4U","title":"Note Hoisting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Note Hoisting"},{"name":"iconClass","value":"bx bxs-chevrons-up","type":"label"}]},{"id":"_help_ZjLYv08Rp3qC","title":"Quick edit","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Quick edit"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_9sRHySam5fXb","title":"Workspaces","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Workspaces"},{"name":"iconClass","value":"bx bx-door-open","type":"label"}]},{"id":"_help_xWtq5NUHOwql","title":"Similar Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Similar Notes"},{"name":"iconClass","value":"bx bx-bar-chart","type":"label"}]},{"id":"_help_McngOG2jbUWX","title":"Search in note","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Search in note"},{"name":"iconClass","value":"bx bx-search-alt-2","type":"label"}]}]},{"id":"_help_A9Oc6YKKc65v","title":"Keyboard Shortcuts","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Keyboard Shortcuts"},{"name":"iconClass","value":"bx bxs-keyboard","type":"label"}]},{"id":"_help_Wy267RK4M69c","title":"Themes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Themes"},{"name":"iconClass","value":"bx bx-palette","type":"label"}],"children":[{"id":"_help_VbjZvtUek0Ln","title":"Theme Gallery","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Themes/Theme Gallery"},{"name":"iconClass","value":"bx bx-book-reader","type":"label"}]}]},{"id":"_help_mHbBMPDPkVV5","title":"Import & Export","type":"book","attributes":[{"name":"iconClass","value":"bx bx-import","type":"label"}],"children":[{"id":"_help_Oau6X9rCuegd","title":"Markdown","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Import & Export/Markdown"},{"name":"iconClass","value":"bx bxl-markdown","type":"label"}],"children":[{"id":"_help_rJ9grSgoExl9","title":"Supported syntax","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Import & Export/Markdown/Supported syntax"},{"name":"iconClass","value":"bx bx-code-alt","type":"label"}]}]},{"id":"_help_syuSEKf2rUGr","title":"Evernote","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Import & Export/Evernote"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_GnhlmrATVqcH","title":"OneNote","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Import & Export/OneNote"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_rC3pL2aptaRE","title":"Zen mode","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Zen mode"},{"name":"iconClass","value":"bx bxs-yin-yang","type":"label"}]}]},{"id":"_help_s3YCWHBfmYuM","title":"Quick Start","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Quick Start"},{"name":"iconClass","value":"bx bx-run","type":"label"}]},{"id":"_help_i6dbnitykE5D","title":"FAQ","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/FAQ"},{"name":"iconClass","value":"bx bx-question-mark","type":"label"}]},{"id":"_help_KSZ04uQ2D1St","title":"Note Types","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types"},{"name":"iconClass","value":"bx bx-edit","type":"label"}],"children":[{"id":"_help_iPIMuisry3hd","title":"Text","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text"},{"name":"iconClass","value":"bx bx-note","type":"label"}],"children":[{"id":"_help_NwBbFdNZ9h7O","title":"Block quotes & admonitions","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Block quotes & admonitions"},{"name":"iconClass","value":"bx bx-info-circle","type":"label"}]},{"id":"_help_oSuaNgyyKnhu","title":"Bookmarks","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Bookmarks"},{"name":"iconClass","value":"bx bx-bookmark","type":"label"}]},{"id":"_help_veGu4faJErEM","title":"Content language & Right-to-left support","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Content language & Right-to-le"},{"name":"iconClass","value":"bx bx-align-right","type":"label"}]},{"id":"_help_2x0ZAX9ePtzV","title":"Cut to subnote","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Cut to subnote"},{"name":"iconClass","value":"bx bx-cut","type":"label"}]},{"id":"_help_UYuUB1ZekNQU","title":"Developer-specific formatting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Developer-specific formatting"},{"name":"iconClass","value":"bx bx-code-alt","type":"label"}],"children":[{"id":"_help_QxEyIjRBizuC","title":"Code blocks","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Developer-specific formatting/Code blocks"},{"name":"iconClass","value":"bx bx-code","type":"label"}]}]},{"id":"_help_AgjCISero73a","title":"Footnotes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Footnotes"},{"name":"iconClass","value":"bx bx-bracket","type":"label"}]},{"id":"_help_nRhnJkTT8cPs","title":"Formatting toolbar","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Formatting toolbar"},{"name":"iconClass","value":"bx bx-text","type":"label"}]},{"id":"_help_Gr6xFaF6ioJ5","title":"General formatting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/General formatting"},{"name":"iconClass","value":"bx bx-bold","type":"label"}]},{"id":"_help_AxshuNRegLAv","title":"Highlights list","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Highlights list"},{"name":"iconClass","value":"bx bx-highlight","type":"label"}]},{"id":"_help_mT0HEkOsz6i1","title":"Images","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Images"},{"name":"iconClass","value":"bx bx-image-alt","type":"label"}],"children":[{"id":"_help_0Ofbk1aSuVRu","title":"Image references","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Images/Image references"},{"name":"iconClass","value":"bx bxs-file-image","type":"label"}]}]},{"id":"_help_nBAXQFj20hS1","title":"Include Note","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Include Note"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_CohkqWQC1iBv","title":"Insert buttons","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Insert buttons"},{"name":"iconClass","value":"bx bx-plus","type":"label"}]},{"id":"_help_oiVPnW8QfnvS","title":"Keyboard shortcuts","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Keyboard shortcuts"},{"name":"iconClass","value":"bx bxs-keyboard","type":"label"}]},{"id":"_help_QEAPj01N5f7w","title":"Links","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Links"},{"name":"iconClass","value":"bx bx-link-alt","type":"label"}],"children":[{"id":"_help_3IDVtesTQ8ds","title":"External links","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Links/External links"},{"name":"iconClass","value":"bx bx-link-external","type":"label"}]},{"id":"_help_hrZ1D00cLbal","title":"Internal (reference) links","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Links/Internal (reference) links"},{"name":"iconClass","value":"bx bx-link","type":"label"}]}]},{"id":"_help_S6Xx8QIWTV66","title":"Lists","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Lists"},{"name":"iconClass","value":"bx bx-list-ul","type":"label"}]},{"id":"_help_QrtTYPmdd1qq","title":"Markdown-like formatting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Markdown-like formatting"},{"name":"iconClass","value":"bx bxl-markdown","type":"label"}]},{"id":"_help_YfYAtQBcfo5V","title":"Math Equations","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Math Equations"},{"name":"iconClass","value":"bx bx-math","type":"label"}]},{"id":"_help_dEHYtoWWi8ct","title":"Other features","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Other features"},{"name":"iconClass","value":"bx bxs-grid","type":"label"}]},{"id":"_help_gLt3vA97tMcp","title":"Premium features","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Premium features"},{"name":"iconClass","value":"bx bx-star","type":"label"}],"children":[{"id":"_help_ZlN4nump6EbW","title":"Slash Commands","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Premium features/Slash Commands"},{"name":"iconClass","value":"bx bx-menu","type":"label"}]},{"id":"_help_pwc194wlRzcH","title":"Text Snippets","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Premium features/Text Snippets"},{"name":"iconClass","value":"bx bx-align-left","type":"label"}]}]},{"id":"_help_BFvAtE74rbP6","title":"Table of contents","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Table of contents"},{"name":"iconClass","value":"bx bx-heading","type":"label"}]},{"id":"_help_NdowYOC1GFKS","title":"Tables","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Tables"},{"name":"iconClass","value":"bx bx-table","type":"label"}]}]},{"id":"_help_6f9hih2hXXZk","title":"Code","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Code"},{"name":"iconClass","value":"bx bx-code","type":"label"}]},{"id":"_help_m523cpzocqaD","title":"Saved Search","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Saved Search"},{"name":"iconClass","value":"bx bx-file-find","type":"label"}]},{"id":"_help_iRwzGnHPzonm","title":"Relation Map","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Relation Map"},{"name":"iconClass","value":"bx bxs-network-chart","type":"label"}]},{"id":"_help_bdUJEHsAPYQR","title":"Note Map","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Note Map"},{"name":"iconClass","value":"bx bxs-network-chart","type":"label"}]},{"id":"_help_HcABDtFCkbFN","title":"Render Note","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Render Note"},{"name":"iconClass","value":"bx bx-extension","type":"label"}]},{"id":"_help_GTwFsgaA0lCt","title":"Collections","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Collections"},{"name":"iconClass","value":"bx bx-book","type":"label"}],"children":[{"id":"_help_xWbu3jpNWapp","title":"Calendar View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Collections/Calendar View"},{"name":"iconClass","value":"bx bx-calendar","type":"label"}]},{"id":"_help_81SGnPGMk7Xc","title":"Geo Map View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Collections/Geo Map View"},{"name":"iconClass","value":"bx bx-map-alt","type":"label"}]},{"id":"_help_8QqnMzx393bx","title":"Grid View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Collections/Grid View"},{"name":"iconClass","value":"bx bxs-grid","type":"label"}]},{"id":"_help_mULW0Q3VojwY","title":"List View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Collections/List View"},{"name":"iconClass","value":"bx bx-list-ul","type":"label"}]},{"id":"_help_2FvYrpmOXm29","title":"Table View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Collections/Table View"},{"name":"iconClass","value":"bx bx-table","type":"label"}]},{"id":"_help_CtBQqbwXDx1w","title":"Board View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Collections/Board View"},{"name":"iconClass","value":"bx bx-columns","type":"label"}]}]},{"id":"_help_s1aBHPd79XYj","title":"Mermaid Diagrams","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Mermaid Diagrams"},{"name":"iconClass","value":"bx bx-selection","type":"label"}],"children":[{"id":"_help_RH6yLjjWJHof","title":"ELK layout","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Mermaid Diagrams/ELK layout"},{"name":"iconClass","value":"bx bxs-network-chart","type":"label"}]}]},{"id":"_help_grjYqerjn243","title":"Canvas","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Canvas"},{"name":"iconClass","value":"bx bx-pen","type":"label"}]},{"id":"_help_1vHRoWCEjj0L","title":"Web View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Web View"},{"name":"iconClass","value":"bx bx-globe-alt","type":"label"}]},{"id":"_help_gBbsAeiuUxI5","title":"Mind Map","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Mind Map"},{"name":"iconClass","value":"bx bx-sitemap","type":"label"}]},{"id":"_help_W8vYD3Q1zjCR","title":"File","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/File"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_BgmBlOIl72jZ","title":"Troubleshooting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting"},{"name":"iconClass","value":"bx bx-bug","type":"label"}],"children":[{"id":"_help_wy8So3yZZlH9","title":"Reporting issues","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Reporting issues"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_x59R8J8KV5Bp","title":"Anonymized Database","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Anonymized Database"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_qzNzp9LYQyPT","title":"Error logs","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Error logs"},{"name":"iconClass","value":"bx bx-comment-error","type":"label"}],"children":[{"id":"_help_bnyigUA2UK7s","title":"Backend (server) logs","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Error logs/Backend (server) logs"},{"name":"iconClass","value":"bx bx-server","type":"label"}]},{"id":"_help_9yEHzMyFirZR","title":"Frontend logs","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Error logs/Frontend logs"},{"name":"iconClass","value":"bx bx-window-alt","type":"label"}]}]},{"id":"_help_vdlYGAcpXAgc","title":"Synchronization fails with 504 Gateway Timeout","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Synchronization fails with 504"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_s8alTXmpFR61","title":"Refreshing the application","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Refreshing the application"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_pKK96zzmvBGf","title":"Theme development","type":"book","attributes":[{"name":"iconClass","value":"bx bx-palette","type":"label"}],"children":[{"id":"_help_7NfNr5pZpVKV","title":"Creating a custom theme","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Theme development/Creating a custom theme"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_WFGzWeUK6arS","title":"Customize the Next theme","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Theme development/Customize the Next theme"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_WN5z4M8ASACJ","title":"Reference","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Theme development/Reference"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_AlhDUqhENtH7","title":"Custom app-wide CSS","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Theme development/Custom app-wide CSS"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_tC7s2alapj8V","title":"Advanced Usage","type":"book","attributes":[{"name":"iconClass","value":"bx bx-rocket","type":"label"}],"children":[{"id":"_help_zEY4DaJG4YT5","title":"Attributes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Attributes"},{"name":"iconClass","value":"bx bx-list-check","type":"label"}],"children":[{"id":"_help_HI6GBBIduIgv","title":"Labels","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Attributes/Labels"},{"name":"iconClass","value":"bx bx-hash","type":"label"}]},{"id":"_help_Cq5X6iKQop6R","title":"Relations","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Attributes/Relations"},{"name":"iconClass","value":"bx bx-transfer","type":"label"}]},{"id":"_help_bwZpz2ajCEwO","title":"Attribute Inheritance","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Attributes/Attribute Inheritance"},{"name":"iconClass","value":"bx bx-list-plus","type":"label"}]},{"id":"_help_OFXdgB2nNk1F","title":"Promoted Attributes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Attributes/Promoted Attributes"},{"name":"iconClass","value":"bx bx-table","type":"label"}]}]},{"id":"_help_KC1HB96bqqHX","title":"Templates","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Templates"},{"name":"iconClass","value":"bx bx-copy","type":"label"}]},{"id":"_help_BCkXAVs63Ttv","title":"Note Map (Link map, Tree map)","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Note Map (Link map, Tree map)"},{"name":"iconClass","value":"bx bxs-network-chart","type":"label"}]},{"id":"_help_R9pX4DGra2Vt","title":"Sharing","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Sharing"},{"name":"iconClass","value":"bx bx-share-alt","type":"label"}],"children":[{"id":"_help_Qjt68inQ2bRj","title":"Serving directly the content of a note","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Sharing/Serving directly the content o"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_5668rwcirq1t","title":"Advanced Showcases","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Advanced Showcases"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"children":[{"id":"_help_l0tKav7yLHGF","title":"Day Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Advanced Showcases/Day Notes"},{"name":"iconClass","value":"bx bx-calendar","type":"label"}]},{"id":"_help_R7abl2fc6Mxi","title":"Weight Tracker","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Advanced Showcases/Weight Tracker"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_xYjQUYhpbUEW","title":"Task Manager","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Advanced Showcases/Task Manager"},{"name":"iconClass","value":"bx bx-calendar-check","type":"label"}]}]},{"id":"_help_J5Ex1ZrMbyJ6","title":"Custom Request Handler","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Custom Request Handler"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_d3fAXQ2diepH","title":"Custom Resource Providers","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Custom Resource Providers"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_pgxEVkzLl1OP","title":"ETAPI (REST API)","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/ETAPI (REST API)"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"children":[{"id":"_help_9qPsTWBorUhQ","title":"API Reference","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"/etapi/docs"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_47ZrP6FNuoG8","title":"Default Note Title","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Default Note Title"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_wX4HbRucYSDD","title":"Database","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Database"},{"name":"iconClass","value":"bx bx-data","type":"label"}],"children":[{"id":"_help_oyIAJ9PvvwHX","title":"Manually altering the database","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Database/Manually altering the database"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"children":[{"id":"_help_YKWqdJhzi2VY","title":"SQL Console","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Database/Manually altering the database/SQL Console"},{"name":"iconClass","value":"bx bx-data","type":"label"}]}]},{"id":"_help_6tZeKvSHEUiB","title":"Demo Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Database/Demo Notes"},{"name":"iconClass","value":"bx bx-package","type":"label"}]}]},{"id":"_help_Gzjqa934BdH4","title":"Configuration (config.ini or environment variables)","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Configuration (config.ini or e"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"children":[{"id":"_help_c5xB8m4g2IY6","title":"Trilium instance","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Configuration (config.ini or environment variables)/Trilium instance"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_LWtBjFej3wX3","title":"Cross-Origin Resource Sharing (CORS)","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Configuration (config.ini or environment variables)/Cross-Origin Resource Sharing "},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_ivYnonVFBxbQ","title":"Bulk Actions","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Bulk Actions"},{"name":"iconClass","value":"bx bx-list-plus","type":"label"}]},{"id":"_help_4FahAwuGTAwC","title":"Note source","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Note source"},{"name":"iconClass","value":"bx bx-code","type":"label"}]},{"id":"_help_1YeN2MzFUluU","title":"Technologies used","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Technologies used"},{"name":"iconClass","value":"bx bxs-component","type":"label"}],"children":[{"id":"_help_MI26XDLSAlCD","title":"CKEditor","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Technologies used/CKEditor"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_N4IDkixaDG9C","title":"MindElixir","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Technologies used/MindElixir"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_H0mM1lTxF9JI","title":"Excalidraw","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Technologies used/Excalidraw"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_MQHyy2dIFgxS","title":"Leaflet","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Technologies used/Leaflet"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_m1lbrzyKDaRB","title":"Note ID","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Note ID"},{"name":"iconClass","value":"bx bx-hash","type":"label"}]},{"id":"_help_0vTSyvhPTAOz","title":"Internal API","type":"book","attributes":[{"name":"iconClass","value":"bx bx-folder","type":"label"}],"children":[{"id":"_help_z8O2VG4ZZJD7","title":"API Reference","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"/api/docs"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_2mUhVmZK8RF3","title":"Hidden Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Hidden Notes"},{"name":"iconClass","value":"bx bx-hide","type":"label"}]},{"id":"_help_uYF7pmepw27K","title":"Metrics","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Metrics"},{"name":"iconClass","value":"bx bxs-data","type":"label"}],"children":[{"id":"_help_bOP3TB56fL1V","title":"grafana-dashboard.json","type":"doc","attributes":[{"name":"iconClass","value":"bx bx-file","type":"label"}]}]}]},{"id":"_help_LMAv4Uy3Wk6J","title":"AI","type":"book","attributes":[{"name":"iconClass","value":"bx bx-bot","type":"label"}],"children":[{"id":"_help_GBBMSlVSOIGP","title":"Introduction","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/AI/Introduction"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_WkM7gsEUyCXs","title":"AI Provider Information","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/AI/AI Provider Information"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"children":[{"id":"_help_7EdTxPADv95W","title":"Ollama","type":"book","attributes":[{"name":"iconClass","value":"bx bx-folder","type":"label"}],"children":[{"id":"_help_vvUCN7FDkq7G","title":"Installing Ollama","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/AI/AI Provider Information/Ollama/Installing Ollama"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_ZavFigBX9AwP","title":"OpenAI","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/AI/AI Provider Information/OpenAI"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_e0lkirXEiSNc","title":"Anthropic","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/AI/AI Provider Information/Anthropic"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]}]},{"id":"_help_CdNpE2pqjmI6","title":"Scripting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting"},{"name":"iconClass","value":"bx bxs-file-js","type":"label"}],"children":[{"id":"_help_yIhgI5H7A2Sm","title":"Frontend Basics","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_es8OU2GuguFU","title":"Examples","type":"book","attributes":[{"name":"iconClass","value":"bx bx-folder","type":"label"}],"children":[{"id":"_help_TjLYAo3JMO8X","title":"\"New Task\" launcher button","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Examples/New Task launcher button"},{"name":"iconClass","value":"bx bx-task","type":"label"}]},{"id":"_help_7kZPMD0uFwkH","title":"Downloading responses from Google Forms","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Examples/Downloading responses from Goo"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_DL92EjAaXT26","title":"Using promoted attributes to configure scripts","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Examples/Using promoted attributes to c"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_GPERMystNGTB","title":"Events","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Events"},{"name":"iconClass","value":"bx bx-rss","type":"label"}]},{"id":"_help_MgibgPcfeuGz","title":"Custom Widgets","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Custom Widgets"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"children":[{"id":"_help_YNxAqkI5Kg1M","title":"Word count widget","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Custom Widgets/Word count widget"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_SynTBQiBsdYJ","title":"Widget Basics","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Custom Widgets/Widget Basics"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_GLks18SNjxmC","title":"Script API","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Script API"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"children":[{"id":"_help_Q2z6av6JZVWm","title":"Frontend API","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"https://triliumnext.github.io/Notes/Script%20API/interfaces/Frontend_Script_API.Api.html"},{"name":"iconClass","value":"bx bx-folder","type":"label"}],"children":[{"id":"_help_habiZ3HU8Kw8","title":"FNote","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"https://triliumnext.github.io/Notes/Script%20API/classes/Frontend_Script_API.FNote.html"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_MEtfsqa5VwNi","title":"Backend API","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"https://triliumnext.github.io/Notes/Script%20API/interfaces/Backend_Script_API.Api.html"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]}]}] \ No newline at end of file +[{"id":"_help_BOCnjTMBCoxW","title":"Feature Highlights","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Feature Highlights"},{"name":"iconClass","value":"bx bx-star","type":"label"}]},{"id":"_help_Otzi9La2YAUX","title":"Installation & Setup","type":"book","attributes":[{"name":"iconClass","value":"bx bx-cog","type":"label"}],"children":[{"id":"_help_poXkQfguuA0U","title":"Desktop Installation","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Desktop Installation"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_WOcw2SLH6tbX","title":"Server Installation","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"children":[{"id":"_help_Dgg7bR3b6K9j","title":"1. Installing the server","type":"book","attributes":[{"name":"iconClass","value":"bx bx-folder","type":"label"}],"children":[{"id":"_help_3tW6mORuTHnB","title":"Packaged version for Linux","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Packaged version for Linux"},{"name":"iconClass","value":"bx bxl-tux","type":"label"}]},{"id":"_help_rWX5eY045zbE","title":"Using Docker","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Using Docker"},{"name":"iconClass","value":"bx bxl-docker","type":"label"}]},{"id":"_help_moVgBcoxE3EK","title":"On NixOS","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/On NixOS"},{"name":"iconClass","value":"bx bxl-tux","type":"label"}]},{"id":"_help_J1Bb6lVlwU5T","title":"Manually","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Manually"},{"name":"iconClass","value":"bx bx-code-alt","type":"label"}]},{"id":"_help_DCmT6e7clMoP","title":"Using Kubernetes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Using Kubernetes"},{"name":"iconClass","value":"bx bxl-kubernetes","type":"label"}]},{"id":"_help_klCWNks3ReaQ","title":"Multiple server instances","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Multiple server instances"},{"name":"iconClass","value":"bx bxs-user-account","type":"label"}]}]},{"id":"_help_vcjrb3VVYPZI","title":"2. Reverse proxy","type":"book","attributes":[{"name":"iconClass","value":"bx bx-folder","type":"label"}],"children":[{"id":"_help_ud6MShXL4WpO","title":"Nginx","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/2. Reverse proxy/Nginx"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_fDLvzOx29Pfg","title":"Apache","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/2. Reverse proxy/Apache"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_l2VkvOwUNfZj","title":"TLS Configuration","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/TLS Configuration"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_0hzsNCP31IAB","title":"Authentication","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/Authentication"},{"name":"iconClass","value":"bx bx-lock-alt","type":"label"}]},{"id":"_help_7DAiwaf8Z7Rz","title":"Multi-Factor Authentication","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/Multi-Factor Authentication"},{"name":"iconClass","value":"bx bx-stopwatch","type":"label"}]}]},{"id":"_help_cbkrhQjrkKrh","title":"Synchronization","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Synchronization"},{"name":"iconClass","value":"bx bx-sync","type":"label"}]},{"id":"_help_RDslemsQ6gCp","title":"Mobile Frontend","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Mobile Frontend"},{"name":"iconClass","value":"bx bx-mobile-alt","type":"label"}]},{"id":"_help_MtPxeAWVAzMg","title":"Web Clipper","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Web Clipper"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_n1lujUxCwipy","title":"Upgrading TriliumNext","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Upgrading TriliumNext"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_ODY7qQn5m2FT","title":"Backup","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Backup"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_tAassRL4RSQL","title":"Data directory","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Data directory"},{"name":"iconClass","value":"bx bx-folder-open","type":"label"}]}]},{"id":"_help_gh7bpGYxajRS","title":"Basic Concepts and Features","type":"book","attributes":[{"name":"iconClass","value":"bx bx-help-circle","type":"label"}],"children":[{"id":"_help_Vc8PjrjAGuOp","title":"UI Elements","type":"book","attributes":[{"name":"iconClass","value":"bx bx-window-alt","type":"label"}],"children":[{"id":"_help_x0JgW8UqGXvq","title":"Vertical and horizontal layout","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Vertical and horizontal layout"},{"name":"iconClass","value":"bx bxs-layout","type":"label"}]},{"id":"_help_x3i7MxGccDuM","title":"Global menu","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Global menu"},{"name":"iconClass","value":"bx bx-menu","type":"label"}]},{"id":"_help_oPVyFC7WL2Lp","title":"Note Tree","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tree"},{"name":"iconClass","value":"bx bxs-tree-alt","type":"label"}],"children":[{"id":"_help_YtSN43OrfzaA","title":"Note tree contextual menu","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tree/Note tree contextual menu"},{"name":"iconClass","value":"bx bx-menu","type":"label"}]},{"id":"_help_yTjUdsOi4CIE","title":"Multiple selection","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tree/Multiple selection"},{"name":"iconClass","value":"bx bx-list-plus","type":"label"}]},{"id":"_help_DvdZhoQZY9Yd","title":"Keyboard shortcuts","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tree/Keyboard shortcuts"},{"name":"iconClass","value":"bx bxs-keyboard","type":"label"}]}]},{"id":"_help_BlN9DFI679QC","title":"Ribbon","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Ribbon"},{"name":"iconClass","value":"bx bx-dots-horizontal","type":"label"}]},{"id":"_help_3seOhtN8uLIY","title":"Tabs","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Tabs"},{"name":"iconClass","value":"bx bx-dock-top","type":"label"}]},{"id":"_help_xYmIYSP6wE3F","title":"Launch Bar","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Launch Bar"},{"name":"iconClass","value":"bx bx-sidebar","type":"label"}]},{"id":"_help_8YBEPzcpUgxw","title":"Note buttons","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note buttons"},{"name":"iconClass","value":"bx bx-dots-vertical-rounded","type":"label"}]},{"id":"_help_4TIF1oA4VQRO","title":"Options","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Options"},{"name":"iconClass","value":"bx bx-cog","type":"label"}]},{"id":"_help_luNhaphA37EO","title":"Split View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Split View"},{"name":"iconClass","value":"bx bx-dock-right","type":"label"}]},{"id":"_help_XpOYSgsLkTJy","title":"Floating buttons","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Floating buttons"},{"name":"iconClass","value":"bx bx-rectangle","type":"label"}]},{"id":"_help_RnaPdbciOfeq","title":"Right Sidebar","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Right Sidebar"},{"name":"iconClass","value":"bx bxs-dock-right","type":"label"}]},{"id":"_help_r5JGHN99bVKn","title":"Recent Changes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Recent Changes"},{"name":"iconClass","value":"bx bx-history","type":"label"}]},{"id":"_help_ny318J39E5Z0","title":"Zoom","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Zoom"},{"name":"iconClass","value":"bx bx-zoom-in","type":"label"}]},{"id":"_help_ZjLYv08Rp3qC","title":"Quick edit","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Quick edit"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_lgKX7r3aL30x","title":"Note Tooltip","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tooltip"},{"name":"iconClass","value":"bx bx-message-detail","type":"label"}]}]},{"id":"_help_BFs8mudNFgCS","title":"Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes"},{"name":"iconClass","value":"bx bx-notepad","type":"label"}],"children":[{"id":"_help_p9kXRFAkwN4o","title":"Note Icons","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Note Icons"},{"name":"iconClass","value":"bx bxs-grid","type":"label"}]},{"id":"_help_0vhv7lsOLy82","title":"Attachments","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Attachments"},{"name":"iconClass","value":"bx bx-paperclip","type":"label"}]},{"id":"_help_IakOLONlIfGI","title":"Cloning Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Cloning Notes"},{"name":"iconClass","value":"bx bx-duplicate","type":"label"}],"children":[{"id":"_help_TBwsyfadTA18","title":"Branch prefix","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Cloning Notes/Branch prefix"},{"name":"iconClass","value":"bx bx-rename","type":"label"}]}]},{"id":"_help_bwg0e8ewQMak","title":"Protected Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Protected Notes"},{"name":"iconClass","value":"bx bx-lock-alt","type":"label"}]},{"id":"_help_MKmLg5x6xkor","title":"Archived Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Archived Notes"},{"name":"iconClass","value":"bx bx-box","type":"label"}]},{"id":"_help_vZWERwf8U3nx","title":"Note Revisions","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Note Revisions"},{"name":"iconClass","value":"bx bx-history","type":"label"}]},{"id":"_help_aGlEvb9hyDhS","title":"Sorting Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Sorting Notes"},{"name":"iconClass","value":"bx bx-sort-up","type":"label"}]},{"id":"_help_NRnIZmSMc5sj","title":"Export as PDF","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Export as PDF"},{"name":"iconClass","value":"bx bxs-file-pdf","type":"label"}]},{"id":"_help_CoFPLs3dRlXc","title":"Read-Only Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Read-Only Notes"},{"name":"iconClass","value":"bx bx-edit-alt","type":"label"}]},{"id":"_help_0ESUbbAxVnoK","title":"Note List","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Note List"},{"name":"iconClass","value":"bx bxs-grid","type":"label"}]}]},{"id":"_help_wArbEsdSae6g","title":"Navigation","type":"book","attributes":[{"name":"iconClass","value":"bx bx-navigation","type":"label"}],"children":[{"id":"_help_kBrnXNG3Hplm","title":"Tree Concepts","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Tree Concepts"},{"name":"iconClass","value":"bx bx-pyramid","type":"label"}]},{"id":"_help_MMiBEQljMQh2","title":"Note Navigation","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Note Navigation"},{"name":"iconClass","value":"bx bxs-navigation","type":"label"}]},{"id":"_help_Ms1nauBra7gq","title":"Quick search","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Quick search"},{"name":"iconClass","value":"bx bx-search-alt-2","type":"label"}]},{"id":"_help_F1r9QtzQLZqm","title":"Jump to...","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Jump to"},{"name":"iconClass","value":"bx bx-send","type":"label"}]},{"id":"_help_eIg8jdvaoNNd","title":"Search","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Search"},{"name":"iconClass","value":"bx bx-search-alt-2","type":"label"}]},{"id":"_help_u3YFHC9tQlpm","title":"Bookmarks","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Bookmarks"},{"name":"iconClass","value":"bx bx-bookmarks","type":"label"}]},{"id":"_help_OR8WJ7Iz9K4U","title":"Note Hoisting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Note Hoisting"},{"name":"iconClass","value":"bx bxs-chevrons-up","type":"label"}]},{"id":"_help_ZjLYv08Rp3qC","title":"Quick edit","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Quick edit.clone"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_9sRHySam5fXb","title":"Workspaces","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Workspaces"},{"name":"iconClass","value":"bx bx-door-open","type":"label"}]},{"id":"_help_xWtq5NUHOwql","title":"Similar Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Similar Notes"},{"name":"iconClass","value":"bx bx-bar-chart","type":"label"}]},{"id":"_help_McngOG2jbUWX","title":"Search in note","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Search in note"},{"name":"iconClass","value":"bx bx-search-alt-2","type":"label"}]}]},{"id":"_help_A9Oc6YKKc65v","title":"Keyboard Shortcuts","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Keyboard Shortcuts"},{"name":"iconClass","value":"bx bxs-keyboard","type":"label"}]},{"id":"_help_Wy267RK4M69c","title":"Themes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Themes"},{"name":"iconClass","value":"bx bx-palette","type":"label"}],"children":[{"id":"_help_VbjZvtUek0Ln","title":"Theme Gallery","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Themes/Theme Gallery"},{"name":"iconClass","value":"bx bx-book-reader","type":"label"}]}]},{"id":"_help_mHbBMPDPkVV5","title":"Import & Export","type":"book","attributes":[{"name":"iconClass","value":"bx bx-import","type":"label"}],"children":[{"id":"_help_Oau6X9rCuegd","title":"Markdown","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Import & Export/Markdown"},{"name":"iconClass","value":"bx bxl-markdown","type":"label"}],"children":[{"id":"_help_rJ9grSgoExl9","title":"Supported syntax","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Import & Export/Markdown/Supported syntax"},{"name":"iconClass","value":"bx bx-code-alt","type":"label"}]}]},{"id":"_help_syuSEKf2rUGr","title":"Evernote","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Import & Export/Evernote"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_GnhlmrATVqcH","title":"OneNote","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Import & Export/OneNote"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_rC3pL2aptaRE","title":"Zen mode","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Zen mode"},{"name":"iconClass","value":"bx bxs-yin-yang","type":"label"}]}]},{"id":"_help_s3YCWHBfmYuM","title":"Quick Start","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Quick Start"},{"name":"iconClass","value":"bx bx-run","type":"label"}]},{"id":"_help_i6dbnitykE5D","title":"FAQ","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/FAQ"},{"name":"iconClass","value":"bx bx-question-mark","type":"label"}]},{"id":"_help_KSZ04uQ2D1St","title":"Note Types","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types"},{"name":"iconClass","value":"bx bx-edit","type":"label"}],"children":[{"id":"_help_iPIMuisry3hd","title":"Text","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text"},{"name":"iconClass","value":"bx bx-note","type":"label"}],"children":[{"id":"_help_NwBbFdNZ9h7O","title":"Block quotes & admonitions","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Block quotes & admonitions"},{"name":"iconClass","value":"bx bx-info-circle","type":"label"}]},{"id":"_help_oSuaNgyyKnhu","title":"Bookmarks","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Bookmarks"},{"name":"iconClass","value":"bx bx-bookmark","type":"label"}]},{"id":"_help_veGu4faJErEM","title":"Content language & Right-to-left support","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Content language & Right-to-le"},{"name":"iconClass","value":"bx bx-align-right","type":"label"}]},{"id":"_help_2x0ZAX9ePtzV","title":"Cut to subnote","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Cut to subnote"},{"name":"iconClass","value":"bx bx-cut","type":"label"}]},{"id":"_help_UYuUB1ZekNQU","title":"Developer-specific formatting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Developer-specific formatting"},{"name":"iconClass","value":"bx bx-code-alt","type":"label"}],"children":[{"id":"_help_QxEyIjRBizuC","title":"Code blocks","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Developer-specific formatting/Code blocks"},{"name":"iconClass","value":"bx bx-code","type":"label"}]}]},{"id":"_help_AgjCISero73a","title":"Footnotes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Footnotes"},{"name":"iconClass","value":"bx bx-bracket","type":"label"}]},{"id":"_help_nRhnJkTT8cPs","title":"Formatting toolbar","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Formatting toolbar"},{"name":"iconClass","value":"bx bx-text","type":"label"}]},{"id":"_help_Gr6xFaF6ioJ5","title":"General formatting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/General formatting"},{"name":"iconClass","value":"bx bx-bold","type":"label"}]},{"id":"_help_AxshuNRegLAv","title":"Highlights list","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Highlights list"},{"name":"iconClass","value":"bx bx-highlight","type":"label"}]},{"id":"_help_mT0HEkOsz6i1","title":"Images","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Images"},{"name":"iconClass","value":"bx bx-image-alt","type":"label"}],"children":[{"id":"_help_0Ofbk1aSuVRu","title":"Image references","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Images/Image references"},{"name":"iconClass","value":"bx bxs-file-image","type":"label"}]}]},{"id":"_help_nBAXQFj20hS1","title":"Include Note","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Include Note"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_CohkqWQC1iBv","title":"Insert buttons","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Insert buttons"},{"name":"iconClass","value":"bx bx-plus","type":"label"}]},{"id":"_help_oiVPnW8QfnvS","title":"Keyboard shortcuts","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Keyboard shortcuts"},{"name":"iconClass","value":"bx bxs-keyboard","type":"label"}]},{"id":"_help_QEAPj01N5f7w","title":"Links","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Links"},{"name":"iconClass","value":"bx bx-link-alt","type":"label"}],"children":[{"id":"_help_3IDVtesTQ8ds","title":"External links","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Links/External links"},{"name":"iconClass","value":"bx bx-link-external","type":"label"}]},{"id":"_help_hrZ1D00cLbal","title":"Internal (reference) links","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Links/Internal (reference) links"},{"name":"iconClass","value":"bx bx-link","type":"label"}]}]},{"id":"_help_S6Xx8QIWTV66","title":"Lists","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Lists"},{"name":"iconClass","value":"bx bx-list-ul","type":"label"}]},{"id":"_help_QrtTYPmdd1qq","title":"Markdown-like formatting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Markdown-like formatting"},{"name":"iconClass","value":"bx bxl-markdown","type":"label"}]},{"id":"_help_YfYAtQBcfo5V","title":"Math Equations","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Math Equations"},{"name":"iconClass","value":"bx bx-math","type":"label"}]},{"id":"_help_dEHYtoWWi8ct","title":"Other features","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Other features"},{"name":"iconClass","value":"bx bxs-grid","type":"label"}]},{"id":"_help_gLt3vA97tMcp","title":"Premium features","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Premium features"},{"name":"iconClass","value":"bx bx-star","type":"label"}],"children":[{"id":"_help_ZlN4nump6EbW","title":"Slash Commands","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Premium features/Slash Commands"},{"name":"iconClass","value":"bx bx-menu","type":"label"}]},{"id":"_help_pwc194wlRzcH","title":"Text Snippets","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Premium features/Text Snippets"},{"name":"iconClass","value":"bx bx-align-left","type":"label"}]}]},{"id":"_help_BFvAtE74rbP6","title":"Table of contents","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Table of contents"},{"name":"iconClass","value":"bx bx-heading","type":"label"}]},{"id":"_help_NdowYOC1GFKS","title":"Tables","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Tables"},{"name":"iconClass","value":"bx bx-table","type":"label"}]}]},{"id":"_help_6f9hih2hXXZk","title":"Code","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Code"},{"name":"iconClass","value":"bx bx-code","type":"label"}]},{"id":"_help_m523cpzocqaD","title":"Saved Search","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Saved Search"},{"name":"iconClass","value":"bx bx-file-find","type":"label"}]},{"id":"_help_iRwzGnHPzonm","title":"Relation Map","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Relation Map"},{"name":"iconClass","value":"bx bxs-network-chart","type":"label"}]},{"id":"_help_bdUJEHsAPYQR","title":"Note Map","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Note Map"},{"name":"iconClass","value":"bx bxs-network-chart","type":"label"}]},{"id":"_help_HcABDtFCkbFN","title":"Render Note","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Render Note"},{"name":"iconClass","value":"bx bx-extension","type":"label"}]},{"id":"_help_GTwFsgaA0lCt","title":"Collections","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Collections"},{"name":"iconClass","value":"bx bx-book","type":"label"}],"children":[{"id":"_help_xWbu3jpNWapp","title":"Calendar View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Collections/Calendar View"},{"name":"iconClass","value":"bx bx-calendar","type":"label"}]},{"id":"_help_81SGnPGMk7Xc","title":"Geo Map View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Collections/Geo Map View"},{"name":"iconClass","value":"bx bx-map-alt","type":"label"}]},{"id":"_help_8QqnMzx393bx","title":"Grid View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Collections/Grid View"},{"name":"iconClass","value":"bx bxs-grid","type":"label"}]},{"id":"_help_mULW0Q3VojwY","title":"List View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Collections/List View"},{"name":"iconClass","value":"bx bx-list-ul","type":"label"}]},{"id":"_help_2FvYrpmOXm29","title":"Table View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Collections/Table View"},{"name":"iconClass","value":"bx bx-table","type":"label"}]},{"id":"_help_CtBQqbwXDx1w","title":"Board View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Collections/Board View"},{"name":"iconClass","value":"bx bx-columns","type":"label"}]}]},{"id":"_help_s1aBHPd79XYj","title":"Mermaid Diagrams","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Mermaid Diagrams"},{"name":"iconClass","value":"bx bx-selection","type":"label"}],"children":[{"id":"_help_RH6yLjjWJHof","title":"ELK layout","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Mermaid Diagrams/ELK layout"},{"name":"iconClass","value":"bx bxs-network-chart","type":"label"}]}]},{"id":"_help_grjYqerjn243","title":"Canvas","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Canvas"},{"name":"iconClass","value":"bx bx-pen","type":"label"}]},{"id":"_help_1vHRoWCEjj0L","title":"Web View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Web View"},{"name":"iconClass","value":"bx bx-globe-alt","type":"label"}]},{"id":"_help_gBbsAeiuUxI5","title":"Mind Map","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Mind Map"},{"name":"iconClass","value":"bx bx-sitemap","type":"label"}]},{"id":"_help_W8vYD3Q1zjCR","title":"File","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/File"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_BgmBlOIl72jZ","title":"Troubleshooting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting"},{"name":"iconClass","value":"bx bx-bug","type":"label"}],"children":[{"id":"_help_wy8So3yZZlH9","title":"Reporting issues","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Reporting issues"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_x59R8J8KV5Bp","title":"Anonymized Database","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Anonymized Database"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_qzNzp9LYQyPT","title":"Error logs","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Error logs"},{"name":"iconClass","value":"bx bx-comment-error","type":"label"}],"children":[{"id":"_help_bnyigUA2UK7s","title":"Backend (server) logs","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Error logs/Backend (server) logs"},{"name":"iconClass","value":"bx bx-server","type":"label"}]},{"id":"_help_9yEHzMyFirZR","title":"Frontend logs","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Error logs/Frontend logs"},{"name":"iconClass","value":"bx bx-window-alt","type":"label"}]}]},{"id":"_help_vdlYGAcpXAgc","title":"Synchronization fails with 504 Gateway Timeout","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Synchronization fails with 504"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_s8alTXmpFR61","title":"Refreshing the application","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Refreshing the application"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_pKK96zzmvBGf","title":"Theme development","type":"book","attributes":[{"name":"iconClass","value":"bx bx-palette","type":"label"}],"children":[{"id":"_help_7NfNr5pZpVKV","title":"Creating a custom theme","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Theme development/Creating a custom theme"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_WFGzWeUK6arS","title":"Customize the Next theme","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Theme development/Customize the Next theme"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_WN5z4M8ASACJ","title":"Reference","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Theme development/Reference"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_AlhDUqhENtH7","title":"Custom app-wide CSS","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Theme development/Custom app-wide CSS"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_tC7s2alapj8V","title":"Advanced Usage","type":"book","attributes":[{"name":"iconClass","value":"bx bx-rocket","type":"label"}],"children":[{"id":"_help_zEY4DaJG4YT5","title":"Attributes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Attributes"},{"name":"iconClass","value":"bx bx-list-check","type":"label"}],"children":[{"id":"_help_HI6GBBIduIgv","title":"Labels","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Attributes/Labels"},{"name":"iconClass","value":"bx bx-hash","type":"label"}]},{"id":"_help_Cq5X6iKQop6R","title":"Relations","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Attributes/Relations"},{"name":"iconClass","value":"bx bx-transfer","type":"label"}]},{"id":"_help_bwZpz2ajCEwO","title":"Attribute Inheritance","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Attributes/Attribute Inheritance"},{"name":"iconClass","value":"bx bx-list-plus","type":"label"}]},{"id":"_help_OFXdgB2nNk1F","title":"Promoted Attributes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Attributes/Promoted Attributes"},{"name":"iconClass","value":"bx bx-table","type":"label"}]}]},{"id":"_help_KC1HB96bqqHX","title":"Templates","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Templates"},{"name":"iconClass","value":"bx bx-copy","type":"label"}]},{"id":"_help_BCkXAVs63Ttv","title":"Note Map (Link map, Tree map)","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Note Map (Link map, Tree map)"},{"name":"iconClass","value":"bx bxs-network-chart","type":"label"}]},{"id":"_help_R9pX4DGra2Vt","title":"Sharing","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Sharing"},{"name":"iconClass","value":"bx bx-share-alt","type":"label"}],"children":[{"id":"_help_Qjt68inQ2bRj","title":"Serving directly the content of a note","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Sharing/Serving directly the content o"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_5668rwcirq1t","title":"Advanced Showcases","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Advanced Showcases"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"children":[{"id":"_help_l0tKav7yLHGF","title":"Day Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Advanced Showcases/Day Notes"},{"name":"iconClass","value":"bx bx-calendar","type":"label"}]},{"id":"_help_R7abl2fc6Mxi","title":"Weight Tracker","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Advanced Showcases/Weight Tracker"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_xYjQUYhpbUEW","title":"Task Manager","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Advanced Showcases/Task Manager"},{"name":"iconClass","value":"bx bx-calendar-check","type":"label"}]}]},{"id":"_help_J5Ex1ZrMbyJ6","title":"Custom Request Handler","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Custom Request Handler"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_d3fAXQ2diepH","title":"Custom Resource Providers","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Custom Resource Providers"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_pgxEVkzLl1OP","title":"ETAPI (REST API)","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/ETAPI (REST API)"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"children":[{"id":"_help_9qPsTWBorUhQ","title":"API Reference","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"/etapi/docs"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_47ZrP6FNuoG8","title":"Default Note Title","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Default Note Title"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_wX4HbRucYSDD","title":"Database","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Database"},{"name":"iconClass","value":"bx bx-data","type":"label"}],"children":[{"id":"_help_oyIAJ9PvvwHX","title":"Manually altering the database","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Database/Manually altering the database"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"children":[{"id":"_help_YKWqdJhzi2VY","title":"SQL Console","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Database/Manually altering the database/SQL Console"},{"name":"iconClass","value":"bx bx-data","type":"label"}]}]},{"id":"_help_6tZeKvSHEUiB","title":"Demo Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Database/Demo Notes"},{"name":"iconClass","value":"bx bx-package","type":"label"}]}]},{"id":"_help_Gzjqa934BdH4","title":"Configuration (config.ini or environment variables)","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Configuration (config.ini or e"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"children":[{"id":"_help_c5xB8m4g2IY6","title":"Trilium instance","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Configuration (config.ini or environment variables)/Trilium instance"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_LWtBjFej3wX3","title":"Cross-Origin Resource Sharing (CORS)","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Configuration (config.ini or environment variables)/Cross-Origin Resource Sharing "},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_ivYnonVFBxbQ","title":"Bulk Actions","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Bulk Actions"},{"name":"iconClass","value":"bx bx-list-plus","type":"label"}]},{"id":"_help_4FahAwuGTAwC","title":"Note source","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Note source"},{"name":"iconClass","value":"bx bx-code","type":"label"}]},{"id":"_help_1YeN2MzFUluU","title":"Technologies used","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Technologies used"},{"name":"iconClass","value":"bx bxs-component","type":"label"}],"children":[{"id":"_help_MI26XDLSAlCD","title":"CKEditor","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Technologies used/CKEditor"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_N4IDkixaDG9C","title":"MindElixir","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Technologies used/MindElixir"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_H0mM1lTxF9JI","title":"Excalidraw","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Technologies used/Excalidraw"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_MQHyy2dIFgxS","title":"Leaflet","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Technologies used/Leaflet"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_m1lbrzyKDaRB","title":"Note ID","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Note ID"},{"name":"iconClass","value":"bx bx-hash","type":"label"}]},{"id":"_help_0vTSyvhPTAOz","title":"Internal API","type":"book","attributes":[{"name":"iconClass","value":"bx bx-folder","type":"label"}],"children":[{"id":"_help_z8O2VG4ZZJD7","title":"API Reference","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"/api/docs"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_2mUhVmZK8RF3","title":"Hidden Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Hidden Notes"},{"name":"iconClass","value":"bx bx-hide","type":"label"}]},{"id":"_help_uYF7pmepw27K","title":"Metrics","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Metrics"},{"name":"iconClass","value":"bx bxs-data","type":"label"}],"children":[{"id":"_help_bOP3TB56fL1V","title":"grafana-dashboard.json","type":"doc","attributes":[{"name":"iconClass","value":"bx bx-file","type":"label"}]}]}]},{"id":"_help_LMAv4Uy3Wk6J","title":"AI","type":"book","attributes":[{"name":"iconClass","value":"bx bx-bot","type":"label"}],"children":[{"id":"_help_GBBMSlVSOIGP","title":"Introduction","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/AI/Introduction"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_WkM7gsEUyCXs","title":"AI Provider Information","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/AI/AI Provider Information"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"children":[{"id":"_help_7EdTxPADv95W","title":"Ollama","type":"book","attributes":[{"name":"iconClass","value":"bx bx-folder","type":"label"}],"children":[{"id":"_help_vvUCN7FDkq7G","title":"Installing Ollama","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/AI/AI Provider Information/Ollama/Installing Ollama"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_ZavFigBX9AwP","title":"OpenAI","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/AI/AI Provider Information/OpenAI"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_e0lkirXEiSNc","title":"Anthropic","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/AI/AI Provider Information/Anthropic"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]}]},{"id":"_help_CdNpE2pqjmI6","title":"Scripting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting"},{"name":"iconClass","value":"bx bxs-file-js","type":"label"}],"children":[{"id":"_help_yIhgI5H7A2Sm","title":"Frontend Basics","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_es8OU2GuguFU","title":"Examples","type":"book","attributes":[{"name":"iconClass","value":"bx bx-folder","type":"label"}],"children":[{"id":"_help_TjLYAo3JMO8X","title":"\"New Task\" launcher button","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Examples/New Task launcher button"},{"name":"iconClass","value":"bx bx-task","type":"label"}]},{"id":"_help_7kZPMD0uFwkH","title":"Downloading responses from Google Forms","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Examples/Downloading responses from Goo"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_DL92EjAaXT26","title":"Using promoted attributes to configure scripts","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Examples/Using promoted attributes to c"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_GPERMystNGTB","title":"Events","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Events"},{"name":"iconClass","value":"bx bx-rss","type":"label"}]},{"id":"_help_MgibgPcfeuGz","title":"Custom Widgets","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Custom Widgets"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"children":[{"id":"_help_YNxAqkI5Kg1M","title":"Word count widget","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Custom Widgets/Word count widget"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_SynTBQiBsdYJ","title":"Widget Basics","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Custom Widgets/Widget Basics"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_GLks18SNjxmC","title":"Script API","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Script API"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"children":[{"id":"_help_Q2z6av6JZVWm","title":"Frontend API","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"https://triliumnext.github.io/Notes/Script%20API/interfaces/Frontend_Script_API.Api.html"},{"name":"iconClass","value":"bx bx-folder","type":"label"}],"children":[{"id":"_help_habiZ3HU8Kw8","title":"FNote","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"https://triliumnext.github.io/Notes/Script%20API/classes/Frontend_Script_API.FNote.html"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_MEtfsqa5VwNi","title":"Backend API","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"https://triliumnext.github.io/Notes/Script%20API/interfaces/Backend_Script_API.Api.html"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]}]}] \ No newline at end of file diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Navigation/Quick edit.clone.html b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Navigation/Quick edit.clone.html new file mode 100644 index 000000000..f69c27506 --- /dev/null +++ b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Navigation/Quick edit.clone.html @@ -0,0 +1 @@ +

This is a clone of a note. Go to its primary location.

\ No newline at end of file diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tree/Note tree contextual menu.html b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tree/Note tree contextual menu.html index c8dc3a86b..0488c7d36 100644 --- a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tree/Note tree contextual menu.html +++ b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tree/Note tree contextual menu.html @@ -8,12 +8,12 @@

Interaction

The contextual menu can operate:

    -
  • On a single note, by right clicking it in the note tree.
  • -
  • On multiple notes, by selecting them first. See On a single note, by right clicking it in the note tree.
  • +
  • On multiple notes, by selecting them first. See Multiple selection on how to do so.
      -
    • When right clicking, do note that usually the note being right clicked +
    • When right clicking, do note that usually the note being right clicked is also included in the affected notes, regardless of whether it was selected or not.
    @@ -25,133 +25,146 @@ The ones that do support multiple notes will mention this in the list below.

      -
    • Open in a new tab +
    • Open in a new tab
        -
      • Will open a single note in a new tab.
      • +
      • Will open a single note in a new tab.
    • -
    • Open in a new split +
    • Open in a new split
        -
      • Will open a split to the right with the given note within the current +
      • Will open a split to the right with the given note within the current tab.
    • -
    • Hoist note +
    • Hoist note
    • -
    • Insert note after +
    • Insert note after
        -
      • Allows easy creation of a note with a specified note type.
      • -
      • Templates will +
      • Allows easy creation of a note with a specified note type.
      • +
      • Templates will also be present (if any) at the end of the list.
      • -
      • The note will be added on the same level of hierarchy as the note selected.
      • -
      +
    • The note will be added on the same level of hierarchy as the note selected.
    • +
  • -
  • Insert child note +
  • Insert child note
      -
    • Same as Insert note after, but the note will be created as a child +
    • Same as Insert note after, but the note will be created as a child of the selected note.
  • -
  • Protect subtree +
  • Protect subtree
      -
    • Will mark this note and all of its descendents as protected. See  +
    • Will mark this note and all of its descendents as protected. See  Protected Notes for more information.
  • -
  • Unprotect subtree +
  • Unprotect subtree
      -
    • Will unprotect this note and all of its descendents.
    • +
    • Will unprotect this note and all of its descendents.
  • -
  • Cut +
  • Cut
      -
    • Will place the given notes in clipboard.
    • -
    • Use one of the two paste functions (or the keyboard shortcuts) to move +
    • Will place the given notes in clipboard.
    • +
    • Use one of the two paste functions (or the keyboard shortcuts) to move them to the desired location.
  • -
  • Copy / clone +
  • Copy / clone
      -
    • Will place the given notes in clipboard.
    • -
    • Use one of the two paste functions (or the keyboard shortcuts) to copy +
    • Will place the given notes in clipboard.
    • +
    • Use one of the two paste functions (or the keyboard shortcuts) to copy them to the desired location.
    • -
    • Note that the copy function here works according to the Note that the copy function here works according to the Cloning Notes functionality (i.e. the note itself will be present in two locations at once, and editing it in one place will edit it everywhere).
    • -
    • To simply create a duplicate note that can be modified independently, +
    • To simply create a duplicate note that can be modified independently, look for Duplicate subtree.
  • -
  • Paste into +
  • Paste into
      -
    • If there are any notes in clipboard, they will be pasted as child notes +
    • If there are any notes in clipboard, they will be pasted as child notes to the right-clicked one.
  • -
  • Paste after +
  • Paste after
      -
    • If there are any notes in clipboard, they will be pasted underneath the +
    • If there are any notes in clipboard, they will be pasted underneath the right-clicked one.
  • -
  • Move to… +
  • Move to…
      -
    • Will display a modal to specify where to move the desired notes.
    • +
    • Will display a modal to specify where to move the desired notes.
  • -
  • Clone to… +
  • Clone to…
      -
    • Will display a modal to specify where to clone the +
    • Will display a modal to specify where to clone the desired notes.
  • -
  • Duplicate +
  • Duplicate +
-
  • Delete +
  • Archive/Unarchive
      -
    • Will delete the given notes, asking for confirmation first.
    • -
    • In the dialog, the following options can be configured: -
        -
      • Delete also all clones to ensure that the note will be deleted - everywhere if it has been placed into multiple locations (see Cloning Notes).
      • -
      • Erase notes permanently will ensure that the note cannot be recovered - from Recent Changes.
      • -
      -
    • +
    • Marks a note as archived.
    • +
    • If the note is already archived, it will be unarchived instead.
    • +
    • Multiple notes can be selected as well. However, all the selected notes + must be in the same state (archived or not), otherwise the option will + be disabled.
    -
  • -
  • Import into note -
      -
    • Opens the import dialog and places - the imported notes as child notes of the selected one.
    • -
    -
  • -
  • Export -
      -
    • Opens the export dialog for the selected - notes.
    • -
    -
  • -
  • Search in subtree -
      -
    • Opens a full Search with - it preconfigured to only look into this note and its descendants (the Ancestor field).
    • -
    -
  • + +
  • Delete +
      +
    • Will delete the given notes, asking for confirmation first.
    • +
    • In the dialog, the following options can be configured: +
        +
      • Delete also all clones to ensure that the note will be deleted + everywhere if it has been placed into multiple locations (see Cloning Notes).
      • +
      • Erase notes permanently will ensure that the note cannot be recovered + from Recent Changes.
      • +
      +
    • +
    +
  • +
  • Import into note +
      +
    • Opens the import dialog and places + the imported notes as child notes of the selected one.
    • +
    +
  • +
  • Export +
      +
    • Opens the export dialog for the selected + notes.
    • +
    +
  • +
  • Search in subtree +
      +
    • Opens a full Search with + it preconfigured to only look into this note and its descendants (the Ancestor field).
    • +
    +
  • Advanced options

    @@ -163,60 +176,63 @@

    To access these options, first look for the Advanced option in the contextual menu to reveal a sub-menu with:

      -
    • Apply bulk actions +
    • Apply bulk actions
    • -
    • Edit branch prefix +
    • Edit branch prefix
        -
      • Opens a dialog to assign a name to be able to distinguish clones, +
      • Opens a dialog to assign a name to be able to distinguish clones, see Branch prefix for more information.
    • -
    • Convert to attachment +
    • Convert to attachment
    • -
    • Expand subtree +
    • Expand subtree
        -
      • Expands all the child notes in the Note Tree.
      • +
      • Expands all the child notes in the Note Tree.
    • -
    • Collapse subtree +
    • Collapse subtree
        -
      • Collapses all the child notes in the note tree.
      • +
      • Collapses all the child notes in the note tree.
    • -
    • Sort by… +
    • Sort by…
        -
      • Opens a dialog to sort all the child notes of the selected note.
      • -
      • The sorting is done only once, there is an automatic sorting mechanism +
      • Opens a dialog to sort all the child notes of the selected note.
      • +
      • The sorting is done only once, there is an automatic sorting mechanism as well that can be set using Attributes.
      • -
      • See Sorting Notes for - more information.
      • +
      • See Sorting Notes for + more information.
      -
    • -
    • Copy note path to clipboard -
        -
      • Copies a URL fragment representing the full path to this branch for a - note, such as #root/Hb2E70L7HPuf/4sRFgMZhYFts/2IVuShedRJ3U/LJVMvKXOFv7n.
      • -
      • The URL to manually create Links within - notes, or for note Navigation.
      • + +
      • Copy note path to clipboard +
          +
        • Copies a URL fragment representing the full path to this branch for a + note, such as #root/Hb2E70L7HPuf/4sRFgMZhYFts/2IVuShedRJ3U/LJVMvKXOFv7n.
        • +
        • The URL to manually create Links within + notes, or for note Navigation.
        -
      • -
      • Recent changes in subtree -
          -
        • This will open Recent Changes, - but filtered to only the changes related to this note or one of its descendants.
        • -
        -
      • + +
      • Recent changes in subtree +
          +
        • This will open Recent Changes, + but filtered to only the changes related to this note or one of its descendants.
        • +
        +
      \ No newline at end of file diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Navigation/Quick edit.html b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/UI Elements/Quick edit.html similarity index 100% rename from apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Navigation/Quick edit.html rename to apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/UI Elements/Quick edit.html diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Navigation/Quick edit_image.png b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/UI Elements/Quick edit_image.png similarity index 100% rename from apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Navigation/Quick edit_image.png rename to apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/UI Elements/Quick edit_image.png diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Collections.html b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Collections.html index 7b255c880..8ec44b5ad 100644 --- a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Collections.html +++ b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Collections.html @@ -4,29 +4,31 @@ child notes into one continuous view. This makes it ideal for reading extensive information broken into smaller, manageable segments.

        -
      • Grid View which +
      • Grid View which is the default presentation method for child notes (see Note List), where the notes are displayed as tiles with their title and content being visible.
      • -
      • List View is +
      • List View is similar to Grid View, but it displays the notes one under the other with the content being expandable/collapsible, but also works recursively.

      More specialized collections were introduced, such as the:

        -
      • Calendar View which +
      • Calendar View which displays a week, month or year calendar with the notes being shown as events. New events can be added easily by dragging across the calendar.
      • -
      • Geo Map View which +
      • Geo Map View which displays a geographical map in which the notes are represented as markers/pins on the map. New events can be easily added by pointing on the map.
      • -
      • Table View displays - each note as a row in a table, with Promoted Attributes being - shown as well. This makes it easy to visualize attributes of notes, as - well as making them easily editable.
      • -
      • Board View (Kanban) - displays notes in columns, grouped by the value of a label.
      • +
      • Table View displays + each note as a row in a table, with Promoted Attributes being + shown as well. This makes it easy to visualize attributes of notes, as + well as making them easily editable.
      • +
      • Board View (Kanban) + displays notes in columns, grouped by the value of a label.

      For a quick presentation of all the supported view types, see the child notes of this help page, including screenshots.

      @@ -42,8 +44,8 @@

      Adding a description to a collection

      To add a text before the collection, for example to describe it:

        -
      1. Create a new collection.
      2. -
      3. In the Ribbon, +
      4. Create a new collection.
      5. +
      6. In the Ribbon, go to Basic Properties and change the note type from Collection to Text.

      Now the text will be displayed above while still maintaining the collection @@ -58,15 +60,22 @@

      By default, collections come with a default configuration and sometimes even sample notes. To create a collection completely from scratch:

        -
      1. Create a new note of type Text (or any type).
      2. -
      3. In the Ribbon, +
      4. Create a new note of type Text (or any type).
      5. +
      6. In the Ribbon, go to Basic Properties and select Collection as the note type.
      7. -
      8. Still in the ribbon, go to Collection Properties and select the +
      9. Still in the ribbon, go to Collection Properties and select the desired view type.
      10. -
      11. Consult the help page of the corresponding view type in order to understand +
      12. Consult the help page of the corresponding view type in order to understand how to configure them.
      +

      Archived notes

      +

      By default, archived notes will not be shown in collections. This behaviour + can be changed by going to Collection Properties in the  + Ribbon and checking Show archived notes.

      +

      Archived notes will be generally indicated by being greyed out as opposed + to the normal ones.

      Under the hood

      Collections by themselves are simply notes with no content that rely on the Note List mechanism diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Collections/Board View.html b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Collections/Board View.html index 3c13a81d8..6dc4e1029 100644 --- a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Collections/Board View.html +++ b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Collections/Board View.html @@ -15,53 +15,60 @@ in a hierarchy.

      Interaction with columns

        -
      • Create a new column by pressing Add Column near the last column. +
      • Create a new column by pressing Add Column near the last column.
          -
        • Once pressed, a text box will be displayed to set the name of the column. - Press Enter to confirm.
        • +
        • Once pressed, a text box will be displayed to set the name of the column. + Press Enter to confirm, or Escape to dismiss.
      • -
      • To reorder a column, simply hold the mouse over the title and drag it +
      • To reorder a column, simply hold the mouse over the title and drag it to the desired position.
      • -
      • To delete a column, right click on its title and select Delete column.
      • -
      • To rename a column, click on the note title. +
      • To delete a column, right click on its title and select Delete column.
      • +
      • To rename a column, click on the note title.
          -
        • Press Enter to confirm.
        • -
        • Upon renaming a column, the corresponding status attribute of all its +
        • Press Enter to confirm.
        • +
        • Upon renaming a column, the corresponding status attribute of all its notes will be changed in bulk.
        -
      • -
      • If there are many columns, use the mouse wheel to scroll.
      • + +
      • If there are many columns, use the mouse wheel to scroll.

      Interaction with notes

        -
      • Create a new note in any column by pressing New item +
      • Create a new note in any column by pressing New item
          -
        • Enter the name of the note and press Enter.
        • -
        • Doing so will create a new note. The new note will have an attribute (status label +
        • Enter the name of the note and press Enter or click away. To + dismiss the creation of a new note, simply press Escape or leave + the name empty.
        • +
        • Once created, the new note will have an attribute (status label by default) set to the name of the column.
      • -
      • To change the state of a note, simply drag a note from one column to the +
      • To open the note, simply click on it.
      • +
      • To change the title of the note directly from the board, hover the mouse + over its card and press the edit button on the right.
      • +
      • To change the state of a note, simply drag a note from one column to the other to change its state.
      • -
      • The order of the notes in each column corresponds to their position in +
      • The order of the notes in each column corresponds to their position in the tree.
          -
        • It's possible to reorder notes simply by dragging them to the desired +
        • It's possible to reorder notes simply by dragging them to the desired position within the same columns.
        • -
        • It's also possible to drag notes across columns, at the desired position.
        • +
        • It's also possible to drag notes across columns, at the desired position.
      • -
      • For more options, right click on a note to display a context menu with +
      • For more options, right click on a note to display a context menu with the following options:
          -
        • Open the note in a new tab/split/window or quick edit.
        • -
        • Move the note to any column.
        • -
        • Insert a new note above/below the current one.
        • -
        • Delete the current note.
        • +
        • Open the note in a new tab/split/window or quick edit.
        • +
        • Move the note to any column.
        • +
        • Insert a new note above/below the current one.
        • +
        • Archive/unarchive the current note.
        • +
        • Delete the current note.
      • -
      • If there are many notes within the column, move the mouse over the column +
      • If there are many notes within the column, move the mouse over the column and use the mouse wheel to scroll.

      Configuration

      @@ -77,5 +84,5 @@ class="admonition note">

      Interaction

      Limitations

        -
      • It is not possible yet to use group by a relation, only by label.
      • +
      • It is not possible yet to use group by a relation, only by label.
      \ No newline at end of file diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Collections/Geo Map View.html b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Collections/Geo Map View.html index cbda5b977..9e0d2cf9c 100644 --- a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Collections/Geo Map View.html +++ b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Collections/Geo Map View.html @@ -12,128 +12,138 @@ on an attribute. It is also possible to add new notes at a specific location using the built-in interface.

      Creating a new geo map

      - - - - - - - - - - - - - - - - - - - - -
      1 -
      - -
      -
      Right click on any note on the note tree and select Insert child noteGeo Map (beta).
      2 -
      - -
      -
      By default the map will be empty and will show the entire world.
      - +
      + + + + + + + + + + + + + + + + + + + + +
         
      1 +
      + +
      +
      Right click on any note on the note tree and select Insert child noteGeo Map (beta).
      2 +
      + +
      +
      By default the map will be empty and will show the entire world.
      +

      Repositioning the map

        -
      • Click and drag the map in order to move across the map.
      • -
      • Use the mouse wheel, two-finger gesture on a touchpad or the +/- buttons +
      • Click and drag the map in order to move across the map.
      • +
      • Use the mouse wheel, two-finger gesture on a touchpad or the +/- buttons on the top-left to adjust the zoom.

      The position on the map and the zoom are saved inside the map note and restored when visiting again the note.

      Adding a marker using the map

      Adding a new note using the plus button

      - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      1To create a marker, first navigate to the desired point on the map. Then - press the - button in the Floating buttons (top-right) - area.   -
      -
      If the button is not visible, make sure the button section is visible - by pressing the chevron button ( - ) in the top-right of the map.
      2 - - Once pressed, the map will enter in the insert mode, as illustrated by - the notification.      -
      -
      Simply click the point on the map where to place the marker, or the Escape - key to cancel.
      3 - - Enter the name of the marker/note to be created.
      4 - - Once confirmed, the marker will show up on the map and it will also be - displayed as a child note of the map.
      - +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
         
      1To create a marker, first navigate to the desired point on the map. Then + press the + button in the Floating buttons (top-right) + area.    +
      +
      If the button is not visible, make sure the button section is visible + by pressing the chevron button ( + ) in the top-right of the map.
       
      2 + + Once pressed, the map will enter in the insert mode, as illustrated by + the notification.       +
      +
      Simply click the point on the map where to place the marker, or the Escape + key to cancel.
      3 + + Enter the name of the marker/note to be created.
      4 + + Once confirmed, the marker will show up on the map and it will also be + displayed as a child note of the map.
      +

      Adding a new note using the contextual menu

        -
      1. Right click anywhere on the map, where to place the newly created marker +
      2. Right click anywhere on the map, where to place the newly created marker (and corresponding note).
      3. -
      4. Select Add a marker at this location.
      5. -
      6. Enter the name of the newly created note.
      7. -
      8. The map should be updated with the new marker.
      9. +
      10. Select Add a marker at this location.
      11. +
      12. Enter the name of the newly created note.
      13. +
      14. The map should be updated with the new marker.

      Adding an existing note on note from the note tree

        -
      1. Select the desired note in the Note Tree.
      2. -
      3. Hold the mouse on the note and drag it to the map to the desired location.
      4. -
      5. The map should be updated with the new marker.
      6. +
      7. Select the desired note in the Note Tree.
      8. +
      9. Hold the mouse on the note and drag it to the map to the desired location.
      10. +
      11. The map should be updated with the new marker.

      This works for:

        -
      • Notes that are not part of the geo map, case in which a clone will +
      • Notes that are not part of the geo map, case in which a clone will be created.
      • -
      • Notes that are a child of the geo map but not yet positioned on the map.
      • -
      • Notes that are a child of the geo map and also positioned, case in which +
      • Notes that are a child of the geo map but not yet positioned on the map.
      • +
      • Notes that are a child of the geo map and also positioned, case in which the marker will be relocated to the new position.
      +

      How the location of the markers is stored

      The location of a marker is stored in the #geolocation attribute of the child notes:

      - +

      + +

      This value can be added manually if needed. The value of the attribute is made up of the latitude and longitude separated by a comma.

      Repositioning markers

      @@ -145,16 +155,17 @@ height="278"> page (Ctrl+R ) to cancel it.

      Interaction with the markers

        -
      • Hovering over a marker will display a Note Tooltip with +
      • Hovering over a marker will display a Note Tooltip with the content of the note it belongs to.
          -
        • Clicking on the note title in the tooltip will navigate to the note in +
        • Clicking on the note title in the tooltip will navigate to the note in the current view.
      • -
      • Middle-clicking the marker will open the note in a new tab.
      • -
      • Right-clicking the marker will open a contextual menu (as described below).
      • -
      • If the map is in read-only mode, clicking on a marker will open a  +
      • Middle-clicking the marker will open the note in a new tab.
      • +
      • Right-clicking the marker will open a contextual menu (as described below).
      • +
      • If the map is in read-only mode, clicking on a marker will open a  Quick edit popup for the corresponding note.
      @@ -162,24 +173,24 @@ height="278">

      It's possible to press the right mouse button to display a contextual menu.

        -
      1. If right-clicking an empty section of the map (not on a marker), it allows +
      2. If right-clicking an empty section of the map (not on a marker), it allows to:
          -
        1. Displays the latitude and longitude. Clicking this option will copy them +
        2. Displays the latitude and longitude. Clicking this option will copy them to the clipboard.
        3. -
        4. Open the location using an external application (if the operating system +
        5. Open the location using an external application (if the operating system supports it).
        6. -
        7. Adding a new marker at that location.
        8. +
        9. Adding a new marker at that location.
      3. -
      4. If right-clicking on a marker, it allows to: +
      5. If right-clicking on a marker, it allows to:
          -
        1. Displays the latitude and longitude. Clicking this option will copy them +
        2. Displays the latitude and longitude. Clicking this option will copy them to the clipboard.
        3. -
        4. Open the location using an external application (if the operating system +
        5. Open the location using an external application (if the operating system supports it).
        6. -
        7. Open the note in a new tab, split or window.
        8. -
        9. Remove the marker from the map, which will remove the #geolocation attribute +
        10. Open the note in a new tab, split or window.
        11. +
        12. Remove the marker from the map, which will remove the #geolocation attribute of the note. To add it back again, the coordinates have to be manually added back in.
        @@ -199,209 +210,215 @@ height="278">

        The value of the attribute is made up of the latitude and longitude separated by a comma.

        Adding from Google Maps

        - - - - - - - - - - - - - - - - - - - - - - - - - -
        1 -
        - -
        -
        Go to Google Maps on the web and look for a desired location, right click - on it and a context menu will show up.      -
        -
        Simply click on the first item displaying the coordinates and they will - be copied to clipboard.      -
        -
        Then paste the value inside the text box into the #geolocation attribute - of a child note of the map (don't forget to surround the value with a " character).
        2 -
        - -
        -
        In Trilium, create a child note under the map.
        3 -
        - -
        -
        And then go to Owned Attributes and type #geolocation=", then - paste from the clipboard as-is and then add the ending " character. - Press Enter to confirm and the map should now be updated to contain the - new note.
        - +
        + + + + + + + + + + + + + + + + + + + + + + + + + +
           
        1 +
        + +
        +
        Go to Google Maps on the web and look for a desired location, right click + on it and a context menu will show up.       +
        +
        Simply click on the first item displaying the coordinates and they will + be copied to clipboard.       +
        +
        Then paste the value inside the text box into the #geolocation attribute + of a child note of the map (don't forget to surround the value with a " character).
        2 +
        + +
        +
        In Trilium, create a child note under the map.
        3 +
        + +
        +
        And then go to Owned Attributes and type #geolocation=", then + paste from the clipboard as-is and then add the ending " character. + Press Enter to confirm and the map should now be updated to contain the + new note.
        +

        Adding from OpenStreetMap

        Similarly to the Google Maps approach:

        - - - - - - - - - - - - - - - - - - - - - - - - - -
        1 - - Go to any location on openstreetmap.org and right click to bring up the - context menu. Select the “Show address” item.
        2 - - The address will be visible in the top-left of the screen, in the place - of the search bar.      -
        -
        Select the coordinates and copy them into the clipboard.
        3 - - Simply paste the value inside the text box into the #geolocation attribute - of a child note of the map and then it should be displayed on the map.
        - +
        + + + + + + + + + + + + + + + + + + + + + + + + + +
           
        1 + + Go to any location on openstreetmap.org and right click to bring up the + context menu. Select the “Show address” item.
        2 + + The address will be visible in the top-left of the screen, in the place + of the search bar.       +
        +
        Select the coordinates and copy them into the clipboard.
        3 + + Simply paste the value inside the text box into the #geolocation attribute + of a child note of the map and then it should be displayed on the map.
        +

        Adding GPS tracks (.gpx)

        Trilium has basic support for displaying GPS tracks on the geo map.

        - - - - - - - - - - - - - - - - - - - - - - - - - -
        1 -
        - -
        -
        To add a track, simply drag & drop a .gpx file inside the geo map - in the note tree.
        2 -
        - -
        -
        In order for the file to be recognized as a GPS track, it needs to show - up as application/gpx+xml in the File type field.
        3 -
        - -
        -
        When going back to the map, the track should now be visible.      -
        -
        The start and end points of the track are indicated by the two blue markers.
        - -

        Read-only mode

        -

        When a map is in read-only all editing features will be disabled such - as:

        -
          -
        • The add button in the Floating buttons.
        • -
        • Dragging markers.
        • -
        • Editing from the contextual menu (removing locations or adding new items).
        • -
        -

        To enable read-only mode simply press the Lock icon from the  - Floating buttons. To disable it, press the button again.

        -

        Configuration

        -

        Map Style

        -

        The styling of the map can be adjusted in the Collection Properties tab - in the Ribbon or - manually via the #map:style attribute.

        -

        The geo map comes with two different types of styles:

        -
          -
        • Raster styles -
            -
          • For these styles the map is represented as a grid of images at different - zoom levels. This is the traditional way OpenStreetMap used to work.
          • -
          • Zoom is slightly restricted.
          • -
          • Currently, the only raster theme is the original OpenStreetMap style.
          • +
            + + + + + + + + + + + + + + + + + + + + + + + + + +
               
            1 +
            + +
            +
            To add a track, simply drag & drop a .gpx file inside the geo map + in the note tree.
            2 +
            + +
            +
            In order for the file to be recognized as a GPS track, it needs to show + up as application/gpx+xml in the File type field.
            3 +
            + +
            +
            When going back to the map, the track should now be visible.       +
            +
            The start and end points of the track are indicated by the two blue markers.
            +
            + +

            Read-only mode

            +

            When a map is in read-only all editing features will be disabled such + as:

            +
              +
            • The add button in the Floating buttons.
            • +
            • Dragging markers.
            • +
            • Editing from the contextual menu (removing locations or adding new items).
            • +
            +

            To enable read-only mode simply press the Lock icon from the  + Floating buttons. To disable it, press the button again.

            +

            Configuration

            +

            Map Style

            +

            The styling of the map can be adjusted in the Collection Properties tab + in the Ribbon or + manually via the #map:style attribute.

            +

            The geo map comes with two different types of styles:

            +
              +
            • Raster styles +
                +
              • For these styles the map is represented as a grid of images at different + zoom levels. This is the traditional way OpenStreetMap used to work.
              • +
              • Zoom is slightly restricted.
              • +
              • Currently, the only raster theme is the original OpenStreetMap style.
              -
            • -
            • Vector styles -
                -
              • Vector styles are not represented as images, but as geometrical shapes. - This makes the rendering much smoother, especially when zooming and looking - at the building edges, for example.
              • -
              • The map can be zoomed in much further.
              • -
              • These come both in a light and a dark version.
              • -
              • The vector styles come from VersaTiles, - a free and open-source project providing map tiles based on OpenStreetMap.
              • -
              -
            • -
            - -

            Scale

            -

            Activating this option via the Ribbon or - manually via #map:scale will display an indicator in the bottom-left - of the scale of the map.

            -

            Troubleshooting

            -
            - -
            - -

            Grid-like artifacts on the map

            -

            This occurs if the application is not at 100% zoom which causes the pixels - of the map to not render correctly due to fractional scaling. The only - possible solution is to set the UI zoom at 100% (default keyboard shortcut - is Ctrl+0).

            \ No newline at end of file + +
          • Vector styles +
              +
            • Vector styles are not represented as images, but as geometrical shapes. + This makes the rendering much smoother, especially when zooming and looking + at the building edges, for example.
            • +
            • The map can be zoomed in much further.
            • +
            • These come both in a light and a dark version.
            • +
            • The vector styles come from VersaTiles, + a free and open-source project providing map tiles based on OpenStreetMap.
            • +
            +
          • +
          + +

          Scale

          +

          Activating this option via the Ribbon or + manually via #map:scale will display an indicator in the bottom-left + of the scale of the map.

          +

          Troubleshooting

          +
          + +
          +

          Grid-like artifacts on the map

          +

          This occurs if the application is not at 100% zoom which causes the pixels + of the map to not render correctly due to fractional scaling. The only + possible solution is to set the UI zoom at 100% (default keyboard shortcut + is Ctrl+0).

          \ No newline at end of file diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Collections/Table View.html b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Collections/Table View.html index 3c6e3fc9b..e256b4cb3 100644 --- a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Collections/Table View.html +++ b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Collections/Table View.html @@ -8,31 +8,31 @@

          How it works

          The tabular structure is represented as such:

            -
          • Each child note is a row in the table.
          • -
          • If child rows also have children, they will be displayed under an expander +
          • Each child note is a row in the table.
          • +
          • If child rows also have children, they will be displayed under an expander (nested notes).
          • -
          • Each column is a promoted attribute that +
          • Each column is a promoted attribute that is defined on the Collection note.
              -
            • Actually, both promoted and unpromoted attributes are supported, but it's +
            • Actually, both promoted and unpromoted attributes are supported, but it's a requirement to use a label/relation definition.
            • -
            • The promoted attributes are usually defined as inheritable in order to +
            • The promoted attributes are usually defined as inheritable in order to show up in the child notes, but it's not a requirement.
          • -
          • If there are multiple attribute definitions with the same name, +
          • If there are multiple attribute definitions with the same name, only one will be displayed.

          There are also a few predefined columns:

            -
          • The current item number, identified by the # symbol. +
          • The current item number, identified by the # symbol.
              -
            • This simply counts the note and is affected by sorting.
            • +
            • This simply counts the note and is affected by sorting.
          • -
          • Note ID, +
          • Note ID, representing the unique ID used internally by Trilium
          • -
          • The title of the note.
          • +
          • The title of the note.

          Interaction

          Creating a new table

          @@ -43,17 +43,18 @@ is defined on the Collection note.

          To create a new column, either:

            -
          • Press Add new column at the bottom of the table.
          • -
          • Right click on an existing column and select Add column to the left/right.
          • -
          • Right click on the empty space of the column header and select Label or Relation in +
          • Press Add new column at the bottom of the table.
          • +
          • Right click on an existing column and select Add column to the left/right.
          • +
          • Right click on the empty space of the column header and select Label or Relation in the New column section.

          Adding new rows

          Each row is actually a note that is a child of the Collection note.

          To create a new note, either:

            -
          • Press Add new row at the bottom of the table.
          • -
          • Right click on an existing row and select Insert row above, Insert child note or Insert row below.
          • +
          • Press Add new row at the bottom of the table.
          • +
          • Right click on an existing row and select Insert row above, Insert child note or Insert row below.

          By default it will try to edit the title of the newly created note.

          Alternatively, the note can be created from the Context menu

          There are multiple menus:

            -
          • Right clicking on a column, allows: +
          • Right clicking on a column, allows:
              -
            • Sorting by the selected column and resetting the sort.
            • -
            • Hiding the selected column or adjusting the visibility of every column.
            • -
            • Adding new columns to the left or the right of the column.
            • -
            • Editing the current column.
            • -
            • Deleting the current column.
            • +
            • Sorting by the selected column and resetting the sort.
            • +
            • Hiding the selected column or adjusting the visibility of every column.
            • +
            • Adding new columns to the left or the right of the column.
            • +
            • Editing the current column.
            • +
            • Deleting the current column.
            • +
            +
          • +
          • Right clicking on the space to the right of the columns, allows: +
              +
            • Adjusting the visibility of every column.
            • +
            • Adding new columns.
          • -
          • Right clicking on the space to the right of the columns, allows: +
          • Right clicking on a row, allows:
              -
            • Adjusting the visibility of every column.
            • -
            • Adding new columns.
            • -
            -
          • -
          • Right clicking on a row, allows: -
              -
            • Opening the corresponding note of the row in a new tab, split, window +
            • Opening the corresponding note of the row in a new tab, split, window or quick editing it.
            • -
            • Inserting rows above, below or as a child note.
            • -
            • Deleting the row.
            • +
            • Inserting a new note above or below the selected row. These options are + only enabled if the table is not sorted.
            • +
            • Inserting a new child note for the selected row.
            • +
            • Deleting the row.
          @@ -90,17 +94,18 @@ not only reflect in the table, but also as an attribute of the corresponding note.

            -
          • The editing will respect the type of the promoted attribute, by presenting +
          • The editing will respect the type of the promoted attribute, by presenting a normal text box, a number selector or a date selector for example.
          • -
          • It also possible to change the title of a note.
          • -
          • Editing relations is also possible -
              -
            • Simply click on a relation and it will become editable. Enter the text - to look for a note and click on it.
            • -
            • To remove a relation, remove the title of the note from the text box and - click outside the cell.
            • -
            -
          • +
          • It also possible to change the title of a note.
          • +
          • Editing relations is also possible +
              +
            • Simply click on a relation and it will become editable. Enter the text + to look for a note and click on it.
            • +
            • To remove a relation, remove the title of the note from the text box and + click outside the cell.
            • +
            +

          Editing columns

          It is possible to edit a column by right clicking it and selecting Edit column. This @@ -114,18 +119,19 @@ href="#root/_help_oPVyFC7WL2Lp">Note Tree. However, it is possible to sort the data by the values of a column:

            -
          • To do so, simply click on a column.
          • -
          • To switch between ascending or descending sort, simply click again on +
          • To do so, simply click on a column.
          • +
          • To switch between ascending or descending sort, simply click again on the same column. The arrow next to the column will indicate the direction of the sort.
          • -
          • To disable sorting and fall back to the original order, right click any +
          • To disable sorting and fall back to the original order, right click any column on the header and select Clear sorting.

          Reordering and hiding columns

            -
          • Columns can be reordered by dragging the header of the columns.
          • -
          • Columns can be hidden or shown by right clicking on a column and clicking +
          • Columns can be reordered by dragging the header of the columns.
          • +
          • Columns can be hidden or shown by right clicking on a column and clicking the item corresponding to the column.

          Reordering rows

          @@ -136,10 +142,12 @@ href="#root/_help_oPVyFC7WL2Lp">Note Tree.

          Reordering does have some limitations:

            -
          • If the parent note has #sorted, reordering will be disabled.
          • -
          • If using nested tables, then reordering will also be disabled.
          • -
          • Currently, it's possible to reorder notes even if column sorting is used, - but the result might be inconsistent.
          • +
          • If the parent note has #sorted, reordering will be disabled.
          • +
          • If using nested tables, then reordering will also be disabled.
          • +
          • Currently, it's possible to reorder notes even if column sorting is used, + but the result might be inconsistent.

          Nested trees

          If the child notes of the collection also have their own child notes, @@ -150,27 +158,27 @@ to a certain number of levels or even disable it completely. To do so, either:

            -
          • Go to Collection Properties in the Go to Collection Properties in the Ribbon and look for the Max nesting depth section.
              -
            • To disable nesting, type 0 and press Enter.
            • -
            • To limit to a certain depth, type in the desired number (e.g. 2 to only +
            • To disable nesting, type 0 and press Enter.
            • +
            • To limit to a certain depth, type in the desired number (e.g. 2 to only display children and sub-children).
            • -
            • To re-enable unlimited nesting, remove the number and press Enter.
            • +
            • To re-enable unlimited nesting, remove the number and press Enter.
          • -
          • Manually set maxNestingDepth to the desired value.
          • +
          • Manually set maxNestingDepth to the desired value.

          Limitations:

            -
          • While in this mode, it's not possible to reorder notes.
          • +
          • While in this mode, it's not possible to reorder notes.

          Limitations

            -
          • Multi-value labels and relations are not supported. If a Multi-value labels and relations are not supported. If a Promoted Attributes is defined with a Multi value specificity, they will be ignored.
          • -
          • There is no support to filter the rows by a certain criteria. Consider +
          • There is no support to filter the rows by a certain criteria. Consider using the table view in search for that use case.

          Use in search

          @@ -181,8 +189,8 @@ of the Search.

          However, there are also some limitations:

            -
          • It's not possible to reorder notes.
          • -
          • It's not possible to add a new row.
          • +
          • It's not possible to reorder notes.
          • +
          • It's not possible to add a new row.

          Columns are supported, by being defined as Promoted Attributes to the  diff --git a/apps/server/src/becca/entities/bbranch.ts b/apps/server/src/becca/entities/bbranch.ts index 00e3ec4b7..cd50fe09b 100644 --- a/apps/server/src/becca/entities/bbranch.ts +++ b/apps/server/src/becca/entities/bbranch.ts @@ -137,13 +137,13 @@ class BBranch extends AbstractBeccaEntity { * * @returns true if note has been deleted, false otherwise */ - deleteBranch(deleteId?: string, taskContext?: TaskContext): boolean { + deleteBranch(deleteId?: string, taskContext?: TaskContext<"deleteNotes">): boolean { if (!deleteId) { deleteId = utils.randomString(10); } if (!taskContext) { - taskContext = new TaskContext("no-progress-reporting"); + taskContext = new TaskContext("no-progress-reporting", "deleteNotes", null); } taskContext.increaseProgressCount(); diff --git a/apps/server/src/becca/entities/bnote.ts b/apps/server/src/becca/entities/bnote.ts index 68c82702b..1a724b1b0 100644 --- a/apps/server/src/becca/entities/bnote.ts +++ b/apps/server/src/becca/entities/bnote.ts @@ -1512,7 +1512,7 @@ class BNote extends AbstractBeccaEntity { * * @param deleteId - optional delete identified */ - deleteNote(deleteId: string | null = null, taskContext: TaskContext | null = null) { + deleteNote(deleteId: string | null = null, taskContext: TaskContext<"deleteNotes"> | null = null) { if (this.isDeleted) { return; } @@ -1522,7 +1522,7 @@ class BNote extends AbstractBeccaEntity { } if (!taskContext) { - taskContext = new TaskContext("no-progress-reporting"); + taskContext = new TaskContext("no-progress-reporting", "deleteNotes", null); } // needs to be run before branches and attributes are deleted and thus attached relations disappear diff --git a/apps/server/src/etapi/notes.ts b/apps/server/src/etapi/notes.ts index e7a1c0a1c..07d6a3e68 100644 --- a/apps/server/src/etapi/notes.ts +++ b/apps/server/src/etapi/notes.ts @@ -108,7 +108,7 @@ function register(router: Router) { return res.sendStatus(204); } - note.deleteNote(null, new TaskContext("no-progress-reporting")); + note.deleteNote(null, new TaskContext("no-progress-reporting", "deleteNotes", null)); res.sendStatus(204); }); @@ -153,7 +153,7 @@ function register(router: Router) { throw new eu.EtapiError(400, "UNRECOGNIZED_EXPORT_FORMAT", `Unrecognized export format '${format}', supported values are 'html' (default) or 'markdown'.`); } - const taskContext = new TaskContext("no-progress-reporting"); + const taskContext = new TaskContext("no-progress-reporting", "export", null); // technically a branch is being exported (includes prefix), but it's such a minor difference yet usability pain // (e.g. branchIds are not seen in UI), that we export "note export" instead. @@ -164,7 +164,7 @@ function register(router: Router) { eu.route(router, "post", "/etapi/notes/:noteId/import", (req, res, next) => { const note = eu.getAndCheckNote(req.params.noteId); - const taskContext = new TaskContext("no-progress-reporting"); + const taskContext = new TaskContext("no-progress-reporting", "importNotes", null); zipImportService.importZip(taskContext, req.body, note).then((importedNote) => { res.status(201).json({ diff --git a/apps/server/src/routes/api/branches.ts b/apps/server/src/routes/api/branches.ts index 4cd570fcb..810deaba7 100644 --- a/apps/server/src/routes/api/branches.ts +++ b/apps/server/src/routes/api/branches.ts @@ -237,7 +237,7 @@ function deleteBranch(req: Request) { const eraseNotes = req.query.eraseNotes === "true"; const branch = becca.getBranchOrThrow(req.params.branchId); - const taskContext = TaskContext.getInstance(req.query.taskId as string, "deleteNotes"); + const taskContext = TaskContext.getInstance(req.query.taskId as string, "deleteNotes", null); const deleteId = utils.randomString(10); let noteDeleted; @@ -252,7 +252,7 @@ function deleteBranch(req: Request) { } if (last) { - taskContext.taskSucceeded(); + taskContext.taskSucceeded(null); } return { diff --git a/apps/server/src/routes/api/export.ts b/apps/server/src/routes/api/export.ts index 7433cd552..4bc0c2177 100644 --- a/apps/server/src/routes/api/export.ts +++ b/apps/server/src/routes/api/export.ts @@ -23,7 +23,7 @@ function exportBranch(req: Request, res: Response) { return; } - const taskContext = new TaskContext(taskId, "export"); + const taskContext = new TaskContext(taskId, "export", null); try { if (type === "subtree" && (format === "html" || format === "markdown")) { diff --git a/apps/server/src/routes/api/import.ts b/apps/server/src/routes/api/import.ts index c7253f2d6..273dc1e1d 100644 --- a/apps/server/src/routes/api/import.ts +++ b/apps/server/src/routes/api/import.ts @@ -116,7 +116,7 @@ function importAttachmentsToNote(req: Request) { } const parentNote = becca.getNoteOrThrow(parentNoteId); - const taskContext = TaskContext.getInstance(taskId, "importAttachment", options); + const taskContext = TaskContext.getInstance(taskId, "importNotes", options); // unlike in note import, we let the events run, because a huge number of attachments is not likely diff --git a/apps/server/src/routes/api/llm.ts b/apps/server/src/routes/api/llm.ts index a15660676..78573ceda 100644 --- a/apps/server/src/routes/api/llm.ts +++ b/apps/server/src/routes/api/llm.ts @@ -1,18 +1,9 @@ import type { Request, Response } from "express"; import log from "../../services/log.js"; -import options from "../../services/options.js"; import restChatService from "../../services/llm/rest_chat_service.js"; import chatStorageService from '../../services/llm/chat_storage_service.js'; - -// Define basic interfaces -interface ChatMessage { - role: 'user' | 'assistant' | 'system'; - content: string; - timestamp?: Date; -} - - +import { WebSocketMessage } from "@triliumnext/commons"; /** * @swagger @@ -419,7 +410,7 @@ async function sendMessage(req: Request, res: Response) { */ async function streamMessage(req: Request, res: Response) { log.info("=== Starting streamMessage ==="); - + try { const chatNoteId = req.params.chatNoteId; const { content, useAdvancedContext, showThinking, mentions } = req.body; @@ -434,7 +425,7 @@ async function streamMessage(req: Request, res: Response) { (res as any).triliumResponseHandled = true; return; } - + // Send immediate success response res.status(200).json({ success: true, @@ -442,12 +433,12 @@ async function streamMessage(req: Request, res: Response) { }); // Mark response as handled to prevent further processing (res as any).triliumResponseHandled = true; - + // Start background streaming process after sending response handleStreamingProcess(chatNoteId, content, useAdvancedContext, showThinking, mentions) .catch(error => { log.error(`Background streaming error: ${error.message}`); - + // Send error via WebSocket since HTTP response was already sent import('../../services/ws.js').then(wsModule => { wsModule.default.sendMessageToAllClients({ @@ -460,11 +451,11 @@ async function streamMessage(req: Request, res: Response) { log.error(`Could not send WebSocket error: ${wsError}`); }); }); - + } catch (error) { // Handle any synchronous errors log.error(`Synchronous error in streamMessage: ${error}`); - + if (!res.headersSent) { res.status(500).json({ success: false, @@ -481,21 +472,21 @@ async function streamMessage(req: Request, res: Response) { * This is separate from the HTTP request/response cycle */ async function handleStreamingProcess( - chatNoteId: string, - content: string, - useAdvancedContext: boolean, - showThinking: boolean, + chatNoteId: string, + content: string, + useAdvancedContext: boolean, + showThinking: boolean, mentions: any[] ) { log.info("=== Starting background streaming process ==="); - + // Get or create chat directly from storage let chat = await chatStorageService.getChat(chatNoteId); if (!chat) { chat = await chatStorageService.createChat('New Chat'); log.info(`Created new chat with ID: ${chat.id} for stream request`); } - + // Add the user message to the chat immediately chat.messages.push({ role: 'user', @@ -544,9 +535,9 @@ async function handleStreamingProcess( thinking: showThinking ? 'Initializing streaming LLM response...' : undefined }); - // Instead of calling the complex handleSendMessage service, + // Instead of calling the complex handleSendMessage service, // let's implement streaming directly to avoid response conflicts - + try { // Check if AI is enabled const optionsModule = await import('../../services/options.js'); @@ -570,7 +561,7 @@ async function handleStreamingProcess( // Get selected model const { getSelectedModelConfig } = await import('../../services/llm/config/configuration_helpers.js'); const modelConfig = await getSelectedModelConfig(); - + if (!modelConfig) { throw new Error("No valid AI model configuration found"); } @@ -590,7 +581,7 @@ async function handleStreamingProcess( chatNoteId: chatNoteId }, streamCallback: (data, done, rawChunk) => { - const message = { + const message: WebSocketMessage = { type: 'llm-stream' as const, chatNoteId: chatNoteId, done: done @@ -634,7 +625,7 @@ async function handleStreamingProcess( // Execute the pipeline await pipeline.execute(pipelineInput); - + } catch (error: any) { log.error(`Error in direct streaming: ${error.message}`); wsService.sendMessageToAllClients({ diff --git a/apps/server/src/routes/api/notes.ts b/apps/server/src/routes/api/notes.ts index 8a426dea3..3c6db4054 100644 --- a/apps/server/src/routes/api/notes.ts +++ b/apps/server/src/routes/api/notes.ts @@ -184,7 +184,7 @@ function deleteNote(req: Request) { if (typeof taskId !== "string") { throw new ValidationError("Missing or incorrect type for task ID."); } - const taskContext = TaskContext.getInstance(taskId, "deleteNotes"); + const taskContext = TaskContext.getInstance(taskId, "deleteNotes", null); note.deleteNote(deleteId, taskContext); @@ -193,16 +193,16 @@ function deleteNote(req: Request) { } if (last) { - taskContext.taskSucceeded(); + taskContext.taskSucceeded(null); } } function undeleteNote(req: Request) { - const taskContext = TaskContext.getInstance(utils.randomString(10), "undeleteNotes"); + const taskContext = TaskContext.getInstance(utils.randomString(10), "undeleteNotes", null); noteService.undeleteNote(req.params.noteId, taskContext); - taskContext.taskSucceeded(); + taskContext.taskSucceeded(null); } function sortChildNotes(req: Request) { @@ -226,7 +226,7 @@ function protectNote(req: Request) { noteService.protectNoteRecursively(note, protect, includingSubTree, taskContext); - taskContext.taskSucceeded(); + taskContext.taskSucceeded(null); } function setNoteTypeMime(req: Request) { diff --git a/apps/server/src/routes/api/sync.ts b/apps/server/src/routes/api/sync.ts index 0e4ae2678..5e1c53041 100644 --- a/apps/server/src/routes/api/sync.ts +++ b/apps/server/src/routes/api/sync.ts @@ -12,11 +12,10 @@ import syncOptions from "../../services/sync_options.js"; import utils, { safeExtractMessageAndStackFromError } from "../../services/utils.js"; import ws from "../../services/ws.js"; import type { Request } from "express"; -import type { EntityChange } from "../../services/entity_changes_interface.js"; import ValidationError from "../../errors/validation_error.js"; import consistencyChecksService from "../../services/consistency_checks.js"; import { t } from "i18next"; -import { SyncTestResponse } from "@triliumnext/commons"; +import { SyncTestResponse, type EntityChange } from "@triliumnext/commons"; async function testSync(): Promise { try { diff --git a/apps/server/src/services/cls.ts b/apps/server/src/services/cls.ts index cd213748b..7636be7dd 100644 --- a/apps/server/src/services/cls.ts +++ b/apps/server/src/services/cls.ts @@ -1,5 +1,5 @@ import clsHooked from "cls-hooked"; -import type { EntityChange } from "./entity_changes_interface.js"; +import type { EntityChange } from "@triliumnext/commons"; const namespace = clsHooked.createNamespace("trilium"); type Callback = (...args: any[]) => any; diff --git a/apps/server/src/services/consistency_checks.ts b/apps/server/src/services/consistency_checks.ts index ec7850572..7b4ba72ad 100644 --- a/apps/server/src/services/consistency_checks.ts +++ b/apps/server/src/services/consistency_checks.ts @@ -15,7 +15,7 @@ import eraseService from "../services/erase.js"; import sanitizeAttributeName from "./sanitize_attribute_name.js"; import noteTypesService from "../services/note_types.js"; import type { BranchRow } from "@triliumnext/commons"; -import type { EntityChange } from "./entity_changes_interface.js"; +import type { EntityChange } from "@triliumnext/commons"; import becca_loader from "../becca/becca_loader.js"; const noteTypes = noteTypesService.getNoteTypeNames(); diff --git a/apps/server/src/services/entity_changes.ts b/apps/server/src/services/entity_changes.ts index 66c2613ce..c0a97c7d6 100644 --- a/apps/server/src/services/entity_changes.ts +++ b/apps/server/src/services/entity_changes.ts @@ -6,7 +6,7 @@ import { randomString } from "./utils.js"; import instanceId from "./instance_id.js"; import becca from "../becca/becca.js"; import blobService from "../services/blob.js"; -import type { EntityChange } from "./entity_changes_interface.js"; +import type { EntityChange } from "@triliumnext/commons"; import type { Blob } from "./blob-interface.js"; import eventService from "./events.js"; diff --git a/apps/server/src/services/entity_changes_interface.ts b/apps/server/src/services/entity_changes_interface.ts deleted file mode 100644 index e69eb5c37..000000000 --- a/apps/server/src/services/entity_changes_interface.ts +++ /dev/null @@ -1,27 +0,0 @@ -export interface EntityChange { - id?: number | null; - noteId?: string; - entityName: string; - entityId: string; - entity?: any; - positions?: Record; - hash: string; - utcDateChanged?: string; - utcDateModified?: string; - utcDateCreated?: string; - isSynced: boolean | 1 | 0; - isErased: boolean | 1 | 0; - componentId?: string | null; - changeId?: string | null; - instanceId?: string | null; -} - -export interface EntityRow { - isDeleted?: boolean; - content?: Buffer | string; -} - -export interface EntityChangeRecord { - entityChange: EntityChange; - entity?: EntityRow; -} diff --git a/apps/server/src/services/erase.ts b/apps/server/src/services/erase.ts index d5f4d2b1d..92b28e573 100644 --- a/apps/server/src/services/erase.ts +++ b/apps/server/src/services/erase.ts @@ -5,7 +5,7 @@ import optionService from "./options.js"; import dateUtils from "./date_utils.js"; import sqlInit from "./sql_init.js"; import cls from "./cls.js"; -import type { EntityChange } from "./entity_changes_interface.js"; +import type { EntityChange } from "@triliumnext/commons"; function eraseNotes(noteIdsToErase: string[]) { if (noteIdsToErase.length === 0) { diff --git a/apps/server/src/services/export/opml.ts b/apps/server/src/services/export/opml.ts index 74d2b2c4e..52e60a60f 100644 --- a/apps/server/src/services/export/opml.ts +++ b/apps/server/src/services/export/opml.ts @@ -6,7 +6,7 @@ import type TaskContext from "../task_context.js"; import type BBranch from "../../becca/entities/bbranch.js"; import type { Response } from "express"; -function exportToOpml(taskContext: TaskContext, branch: BBranch, version: string, res: Response) { +function exportToOpml(taskContext: TaskContext<"export">, branch: BBranch, version: string, res: Response) { if (!["1.0", "2.0"].includes(version)) { throw new Error(`Unrecognized OPML version ${version}`); } @@ -77,7 +77,7 @@ function exportToOpml(taskContext: TaskContext, branch: BBranch, version: string `); res.end(); - taskContext.taskSucceeded(); + taskContext.taskSucceeded(null); } function prepareText(text: string) { diff --git a/apps/server/src/services/export/single.ts b/apps/server/src/services/export/single.ts index b626bf919..678fb39e5 100644 --- a/apps/server/src/services/export/single.ts +++ b/apps/server/src/services/export/single.ts @@ -10,7 +10,7 @@ import type BBranch from "../../becca/entities/bbranch.js"; import type { Response } from "express"; import type BNote from "../../becca/entities/bnote.js"; -function exportSingleNote(taskContext: TaskContext, branch: BBranch, format: "html" | "markdown", res: Response) { +function exportSingleNote(taskContext: TaskContext<"export">, branch: BBranch, format: "html" | "markdown", res: Response) { const note = branch.getNote(); if (note.type === "image" || note.type === "file") { @@ -30,7 +30,7 @@ function exportSingleNote(taskContext: TaskContext, branch: BBranch, format: "ht res.send(payload); taskContext.increaseProgressCount(); - taskContext.taskSucceeded(); + taskContext.taskSucceeded(null); } export function mapByNoteType(note: BNote, content: string | Buffer, format: "html" | "markdown") { diff --git a/apps/server/src/services/export/zip.ts b/apps/server/src/services/export/zip.ts index 91d01c8c7..116a841b2 100644 --- a/apps/server/src/services/export/zip.ts +++ b/apps/server/src/services/export/zip.ts @@ -40,7 +40,7 @@ export interface AdvancedExportOptions { customRewriteLinks?: (originalRewriteLinks: RewriteLinksFn, getNoteTargetUrl: (targetNoteId: string, sourceMeta: NoteMeta) => string | null) => RewriteLinksFn; } -async function exportToZip(taskContext: TaskContext, branch: BBranch, format: "html" | "markdown", res: Response | fs.WriteStream, setHeaders = true, zipExportOptions?: AdvancedExportOptions) { +async function exportToZip(taskContext: TaskContext<"export">, branch: BBranch, format: "html" | "markdown", res: Response | fs.WriteStream, setHeaders = true, zipExportOptions?: AdvancedExportOptions) { if (!["html", "markdown"].includes(format)) { throw new ValidationError(`Only 'html' and 'markdown' allowed as export format, '${format}' given`); } @@ -611,7 +611,7 @@ ${markdownContent}`; archive.pipe(res); await archive.finalize(); - taskContext.taskSucceeded(); + taskContext.taskSucceeded(null); } catch (e: unknown) { const message = `Export failed with error: ${e instanceof Error ? e.message : String(e)}`; log.error(message); @@ -627,7 +627,7 @@ ${markdownContent}`; async function exportToZipFile(noteId: string, format: "markdown" | "html", zipFilePath: string, zipExportOptions?: AdvancedExportOptions) { const fileOutputStream = fs.createWriteStream(zipFilePath); - const taskContext = new TaskContext("no-progress-reporting"); + const taskContext = new TaskContext("no-progress-reporting", "export", null); const note = becca.getNote(noteId); diff --git a/apps/server/src/services/import/enex.ts b/apps/server/src/services/import/enex.ts index 4699ca32e..5a13e0960 100644 --- a/apps/server/src/services/import/enex.ts +++ b/apps/server/src/services/import/enex.ts @@ -55,7 +55,7 @@ interface Note { let note: Partial = {}; let resource: Resource; -function importEnex(taskContext: TaskContext, file: File, parentNote: BNote): Promise { +function importEnex(taskContext: TaskContext<"importNotes">, file: File, parentNote: BNote): Promise { const saxStream = sax.createStream(true); const rootNoteTitle = file.originalname.toLowerCase().endsWith(".enex") ? file.originalname.substr(0, file.originalname.length - 5) : file.originalname; diff --git a/apps/server/src/services/import/mime.ts b/apps/server/src/services/import/mime.ts index 479bd3494..0a129ae1e 100644 --- a/apps/server/src/services/import/mime.ts +++ b/apps/server/src/services/import/mime.ts @@ -2,8 +2,7 @@ import mimeTypes from "mime-types"; import path from "path"; -import type { TaskData } from "../task_context_interface.js"; -import type { NoteType } from "@triliumnext/commons"; +import type { NoteType, TaskData } from "@triliumnext/commons"; const CODE_MIME_TYPES = new Set([ "application/json", @@ -92,14 +91,14 @@ function getMime(fileName: string) { return mimeFromExt || mimeTypes.lookup(fileNameLc); } -function getType(options: TaskData, mime: string): NoteType { +function getType(options: TaskData<"importNotes">, mime: string): NoteType { const mimeLc = mime?.toLowerCase(); switch (true) { - case options.textImportedAsText && ["text/html", "text/markdown", "text/x-markdown", "text/mdx"].includes(mimeLc): + case options?.textImportedAsText && ["text/html", "text/markdown", "text/x-markdown", "text/mdx"].includes(mimeLc): return "text"; - case options.codeImportedAsCode && CODE_MIME_TYPES.has(mimeLc): + case options?.codeImportedAsCode && CODE_MIME_TYPES.has(mimeLc): return "code"; case mime.startsWith("image/"): diff --git a/apps/server/src/services/import/opml.ts b/apps/server/src/services/import/opml.ts index 934578c70..130eb8197 100644 --- a/apps/server/src/services/import/opml.ts +++ b/apps/server/src/services/import/opml.ts @@ -28,7 +28,7 @@ interface OpmlOutline { outline: OpmlOutline[]; } -async function importOpml(taskContext: TaskContext, fileBuffer: string | Buffer, parentNote: BNote) { +async function importOpml(taskContext: TaskContext<"importNotes">, fileBuffer: string | Buffer, parentNote: BNote) { const xml = await new Promise(function (resolve, reject) { parseString(fileBuffer, function (err: any, result: OpmlXml) { if (err) { diff --git a/apps/server/src/services/import/single.spec.ts b/apps/server/src/services/import/single.spec.ts index b16124cbb..0d0af35f7 100644 --- a/apps/server/src/services/import/single.spec.ts +++ b/apps/server/src/services/import/single.spec.ts @@ -14,7 +14,7 @@ const scriptDir = dirname(fileURLToPath(import.meta.url)); async function testImport(fileName: string, mimetype: string) { const buffer = fs.readFileSync(path.join(scriptDir, "samples", fileName)); - const taskContext = TaskContext.getInstance("import-mdx", "import", { + const taskContext = TaskContext.getInstance("import-mdx", "importNotes", { textImportedAsText: true, codeImportedAsCode: true }); diff --git a/apps/server/src/services/import/single.ts b/apps/server/src/services/import/single.ts index 7603cd625..ac52a43f4 100644 --- a/apps/server/src/services/import/single.ts +++ b/apps/server/src/services/import/single.ts @@ -14,7 +14,7 @@ import htmlSanitizer from "../html_sanitizer.js"; import type { File } from "./common.js"; import type { NoteType } from "@triliumnext/commons"; -function importSingleFile(taskContext: TaskContext, file: File, parentNote: BNote) { +function importSingleFile(taskContext: TaskContext<"importNotes">, file: File, parentNote: BNote) { const mime = mimeService.getMime(file.originalname) || file.mimetype; if (taskContext?.data?.textImportedAsText) { @@ -42,7 +42,7 @@ function importSingleFile(taskContext: TaskContext, file: File, parentNote: BNot return importFile(taskContext, file, parentNote); } -function importImage(file: File, parentNote: BNote, taskContext: TaskContext) { +function importImage(file: File, parentNote: BNote, taskContext: TaskContext<"importNotes">) { if (typeof file.buffer === "string") { throw new Error("Invalid file content for image."); } @@ -53,7 +53,7 @@ function importImage(file: File, parentNote: BNote, taskContext: TaskContext) { return note; } -function importFile(taskContext: TaskContext, file: File, parentNote: BNote) { +function importFile(taskContext: TaskContext<"importNotes">, file: File, parentNote: BNote) { const originalName = file.originalname; const { note } = noteService.createNewNote({ @@ -72,7 +72,7 @@ function importFile(taskContext: TaskContext, file: File, parentNote: BNote) { return note; } -function importCodeNote(taskContext: TaskContext, file: File, parentNote: BNote) { +function importCodeNote(taskContext: TaskContext<"importNotes">, file: File, parentNote: BNote) { const title = getNoteTitle(file.originalname, !!taskContext.data?.replaceUnderscoresWithSpaces); const content = processStringOrBuffer(file.buffer); const detectedMime = mimeService.getMime(file.originalname) || file.mimetype; @@ -97,7 +97,7 @@ function importCodeNote(taskContext: TaskContext, file: File, parentNote: BNote) return note; } -function importCustomType(taskContext: TaskContext, file: File, parentNote: BNote, type: NoteType, mime: string) { +function importCustomType(taskContext: TaskContext<"importNotes">, file: File, parentNote: BNote, type: NoteType, mime: string) { const title = getNoteTitle(file.originalname, !!taskContext.data?.replaceUnderscoresWithSpaces); const content = processStringOrBuffer(file.buffer); @@ -115,7 +115,7 @@ function importCustomType(taskContext: TaskContext, file: File, parentNote: BNot return note; } -function importPlainText(taskContext: TaskContext, file: File, parentNote: BNote) { +function importPlainText(taskContext: TaskContext<"importNotes">, file: File, parentNote: BNote) { const title = getNoteTitle(file.originalname, !!taskContext.data?.replaceUnderscoresWithSpaces); const plainTextContent = processStringOrBuffer(file.buffer); const htmlContent = convertTextToHtml(plainTextContent); @@ -150,7 +150,7 @@ function convertTextToHtml(text: string) { return text; } -function importMarkdown(taskContext: TaskContext, file: File, parentNote: BNote) { +function importMarkdown(taskContext: TaskContext<"importNotes">, file: File, parentNote: BNote) { const title = getNoteTitle(file.originalname, !!taskContext.data?.replaceUnderscoresWithSpaces); const markdownContent = processStringOrBuffer(file.buffer); @@ -174,7 +174,7 @@ function importMarkdown(taskContext: TaskContext, file: File, parentNote: BNote) return note; } -function importHtml(taskContext: TaskContext, file: File, parentNote: BNote) { +function importHtml(taskContext: TaskContext<"importNotes">, file: File, parentNote: BNote) { let content = processStringOrBuffer(file.buffer); // Try to get title from HTML first, fall back to filename @@ -202,7 +202,7 @@ function importHtml(taskContext: TaskContext, file: File, parentNote: BNote) { return note; } -function importAttachment(taskContext: TaskContext, file: File, parentNote: BNote) { +function importAttachment(taskContext: TaskContext<"importNotes">, file: File, parentNote: BNote) { const mime = mimeService.getMime(file.originalname) || file.mimetype; if (mime.startsWith("image/") && typeof file.buffer !== "string") { diff --git a/apps/server/src/services/import/zip.spec.ts b/apps/server/src/services/import/zip.spec.ts index db2c7ba76..a74c243c4 100644 --- a/apps/server/src/services/import/zip.spec.ts +++ b/apps/server/src/services/import/zip.spec.ts @@ -14,7 +14,7 @@ const scriptDir = dirname(fileURLToPath(import.meta.url)); async function testImport(fileName: string) { const mdxSample = fs.readFileSync(path.join(scriptDir, "samples", fileName)); - const taskContext = TaskContext.getInstance("import-mdx", "import", { + const taskContext = TaskContext.getInstance("import-mdx", "importNotes", { textImportedAsText: true }); diff --git a/apps/server/src/services/import/zip.ts b/apps/server/src/services/import/zip.ts index b2d83bdc6..c1ac90b91 100644 --- a/apps/server/src/services/import/zip.ts +++ b/apps/server/src/services/import/zip.ts @@ -30,7 +30,7 @@ interface ImportZipOpts { preserveIds?: boolean; } -async function importZip(taskContext: TaskContext, fileBuffer: Buffer, importRootNote: BNote, opts?: ImportZipOpts): Promise { +async function importZip(taskContext: TaskContext<"importNotes">, fileBuffer: Buffer, importRootNote: BNote, opts?: ImportZipOpts): Promise { /** maps from original noteId (in ZIP file) to newly generated noteId */ const noteIdMap: Record = {}; /** type maps from original attachmentId (in ZIP file) to newly generated attachmentId */ @@ -174,7 +174,7 @@ async function importZip(taskContext: TaskContext, fileBuffer: Buffer, importRoo return noteId; } - function detectFileTypeAndMime(taskContext: TaskContext, filePath: string) { + function detectFileTypeAndMime(taskContext: TaskContext<"importNotes">, filePath: string) { const mime = mimeService.getMime(filePath) || "application/octet-stream"; const type = mimeService.getType(taskContext.data || {}, mime); diff --git a/apps/server/src/services/llm/chat/handlers/stream_handler.ts b/apps/server/src/services/llm/chat/handlers/stream_handler.ts index 3aeb26d83..1ebca5d5a 100644 --- a/apps/server/src/services/llm/chat/handlers/stream_handler.ts +++ b/apps/server/src/services/llm/chat/handlers/stream_handler.ts @@ -4,7 +4,6 @@ import log from "../../../log.js"; import type { Response } from "express"; import type { StreamChunk } from "../../ai_interface.js"; -import type { LLMStreamMessage } from "../../interfaces/chat_ws_messages.js"; import type { ChatSession } from "../../interfaces/chat_session.js"; /** @@ -46,7 +45,7 @@ export class StreamHandler { type: 'llm-stream', chatNoteId, thinking: 'Preparing response...' - } as LLMStreamMessage); + }); try { // Import the tool handler @@ -66,7 +65,7 @@ export class StreamHandler { type: 'llm-stream', chatNoteId, thinking: 'Analyzing tools needed for this request...' - } as LLMStreamMessage); + }); try { // Execute the tools @@ -82,7 +81,7 @@ export class StreamHandler { tool: toolResult.name, result: toolResult.content.substring(0, 100) + (toolResult.content.length > 100 ? '...' : '') } - } as LLMStreamMessage); + }); } // Make follow-up request with tool results @@ -123,7 +122,7 @@ export class StreamHandler { chatNoteId, error: `Error executing tools: ${toolError instanceof Error ? toolError.message : 'Unknown error'}`, done: true - } as LLMStreamMessage); + }); } } else if (response.stream) { // Handle standard streaming through the stream() method @@ -152,7 +151,7 @@ export class StreamHandler { chatNoteId, content: messageContent, done: true - } as LLMStreamMessage); + }); log.info(`Complete response sent`); @@ -174,14 +173,14 @@ export class StreamHandler { type: 'llm-stream', chatNoteId, error: `Error generating response: ${streamingError instanceof Error ? streamingError.message : 'Unknown error'}` - } as LLMStreamMessage); + }); // Signal completion wsService.sendMessageToAllClients({ type: 'llm-stream', chatNoteId, done: true - } as LLMStreamMessage); + }); } } @@ -218,7 +217,7 @@ export class StreamHandler { done: !!chunk.done, // Include done flag with each chunk // Include any raw data from the provider that might contain thinking/tool info ...(chunk.raw ? { raw: chunk.raw } : {}) - } as LLMStreamMessage); + }); // Log the first chunk (useful for debugging) if (messageContent.length === chunk.text.length) { @@ -232,7 +231,7 @@ export class StreamHandler { type: 'llm-stream', chatNoteId, thinking: chunk.raw.thinking - } as LLMStreamMessage); + }); } // If the provider indicates tool execution, relay that @@ -241,7 +240,7 @@ export class StreamHandler { type: 'llm-stream', chatNoteId, toolExecution: chunk.raw.toolExecution - } as LLMStreamMessage); + }); } // Handle direct tool_calls in the response (for OpenAI) @@ -252,7 +251,7 @@ export class StreamHandler { wsService.sendMessageToAllClients({ type: 'tool_execution_start', chatNoteId - } as LLMStreamMessage); + }); // Process each tool call for (const toolCall of chunk.tool_calls) { @@ -277,7 +276,7 @@ export class StreamHandler { toolCallId: toolCall.id, args: args } - } as LLMStreamMessage); + }); } } @@ -337,7 +336,7 @@ export class StreamHandler { type: 'llm-stream', chatNoteId, done: true - } as LLMStreamMessage); + }); } // Store the full response in the session @@ -360,7 +359,7 @@ export class StreamHandler { chatNoteId, error: `Error during streaming: ${streamError instanceof Error ? streamError.message : 'Unknown error'}`, done: true - } as LLMStreamMessage); + }); throw streamError; } diff --git a/apps/server/src/services/llm/chat/index.ts b/apps/server/src/services/llm/chat/index.ts index 79b587a09..622f65374 100644 --- a/apps/server/src/services/llm/chat/index.ts +++ b/apps/server/src/services/llm/chat/index.ts @@ -7,7 +7,6 @@ import { ToolHandler } from './handlers/tool_handler.js'; import { StreamHandler } from './handlers/stream_handler.js'; import * as messageFormatter from './utils/message_formatter.js'; import type { ChatSession, ChatMessage, NoteSource } from '../interfaces/chat_session.js'; -import type { LLMStreamMessage } from '../interfaces/chat_ws_messages.js'; // Export components export { @@ -22,6 +21,5 @@ export { export type { ChatSession, ChatMessage, - NoteSource, - LLMStreamMessage + NoteSource }; diff --git a/apps/server/src/services/llm/chat/rest_chat_service.ts b/apps/server/src/services/llm/chat/rest_chat_service.ts index 5bf57c042..45af7e944 100644 --- a/apps/server/src/services/llm/chat/rest_chat_service.ts +++ b/apps/server/src/services/llm/chat/rest_chat_service.ts @@ -4,18 +4,15 @@ */ import log from "../../log.js"; import type { Request, Response } from "express"; -import type { Message, ChatCompletionOptions } from "../ai_interface.js"; +import type { Message } from "../ai_interface.js"; import aiServiceManager from "../ai_service_manager.js"; import { ChatPipeline } from "../pipeline/chat_pipeline.js"; import type { ChatPipelineInput } from "../pipeline/interfaces.js"; import options from "../../options.js"; import { ToolHandler } from "./handlers/tool_handler.js"; -import type { LLMStreamMessage } from "../interfaces/chat_ws_messages.js"; import chatStorageService from '../chat_storage_service.js'; -import { - isAIEnabled, - getSelectedModelConfig, -} from '../config/configuration_helpers.js'; +import { getSelectedModelConfig } from '../config/configuration_helpers.js'; +import { WebSocketMessage } from "@triliumnext/commons"; /** * Simplified service to handle chat API interactions @@ -79,7 +76,7 @@ class RestChatService { throw new Error("Database is not initialized"); } - // Get or create AI service - will throw meaningful error if not possible + // Get or create AI service - will throw meaningful error if not possible await aiServiceManager.getOrCreateAnyService(); // Load or create chat directly from storage @@ -204,7 +201,7 @@ class RestChatService { accumulatedContentRef: { value: string }, chat: { id: string; messages: Message[]; title: string } ) { - const message: LLMStreamMessage = { + const message: WebSocketMessage = { type: 'llm-stream', chatNoteId: chatNoteId, done: done @@ -237,7 +234,7 @@ class RestChatService { // Send WebSocket message wsService.sendMessageToAllClients(message); - + // When streaming is complete, save the accumulated content to the chat note if (done) { try { @@ -248,7 +245,7 @@ class RestChatService { role: 'assistant', content: accumulatedContentRef.value }); - + // Save the updated chat back to storage await chatStorageService.updateChat(chat.id, chat.messages, chat.title); log.info(`Saved streaming assistant response: ${accumulatedContentRef.value.length} characters`); @@ -257,7 +254,7 @@ class RestChatService { // Log error but don't break the response flow log.error(`Error saving streaming response: ${error}`); } - + // Note: For WebSocket-only streaming, we don't end the HTTP response here // since it was already handled by the calling endpoint } diff --git a/apps/server/src/services/llm/interfaces/chat_ws_messages.ts b/apps/server/src/services/llm/interfaces/chat_ws_messages.ts deleted file mode 100644 index f75d399f4..000000000 --- a/apps/server/src/services/llm/interfaces/chat_ws_messages.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Interfaces for WebSocket LLM streaming messages - */ - -/** - * Interface for WebSocket LLM streaming messages - */ -export interface LLMStreamMessage { - type: 'llm-stream' | 'tool_execution_start' | 'tool_result' | 'tool_execution_error' | 'tool_completion_processing'; - chatNoteId: string; - content?: string; - thinking?: string; - toolExecution?: { - action?: string; - tool?: string; - toolCallId?: string; - result?: string | Record; - error?: string; - args?: Record; - }; - done?: boolean; - error?: string; - raw?: unknown; -} diff --git a/apps/server/src/services/notes.ts b/apps/server/src/services/notes.ts index e225cdb52..3ecf98e0a 100644 --- a/apps/server/src/services/notes.ts +++ b/apps/server/src/services/notes.ts @@ -296,7 +296,7 @@ function createNewNoteWithTarget(target: "into" | "after" | "before", targetBran } } -function protectNoteRecursively(note: BNote, protect: boolean, includingSubTree: boolean, taskContext: TaskContext) { +function protectNoteRecursively(note: BNote, protect: boolean, includingSubTree: boolean, taskContext: TaskContext<"protectNotes">) { protectNote(note, protect); taskContext.increaseProgressCount(); @@ -765,7 +765,7 @@ function updateNoteData(noteId: string, content: string, attachments: Attachment } } -function undeleteNote(noteId: string, taskContext: TaskContext) { +function undeleteNote(noteId: string, taskContext: TaskContext<"undeleteNotes">) { const noteRow = sql.getRow("SELECT * FROM notes WHERE noteId = ?", [noteId]); if (!noteRow.isDeleted || !noteRow.deleteId) { @@ -785,7 +785,7 @@ function undeleteNote(noteId: string, taskContext: TaskContext) { } } -function undeleteBranch(branchId: string, deleteId: string, taskContext: TaskContext) { +function undeleteBranch(branchId: string, deleteId: string, taskContext: TaskContext<"undeleteNotes">) { const branchRow = sql.getRow("SELECT * FROM branches WHERE branchId = ?", [branchId]); if (!branchRow.isDeleted) { diff --git a/apps/server/src/services/sql_init.ts b/apps/server/src/services/sql_init.ts index 541e487a0..926d61bba 100644 --- a/apps/server/src/services/sql_init.ts +++ b/apps/server/src/services/sql_init.ts @@ -122,7 +122,7 @@ async function createInitialDatabase(skipDemoDb?: boolean) { log.info("Importing demo content ..."); - const dummyTaskContext = new TaskContext("no-progress-reporting", "import", false); + const dummyTaskContext = new TaskContext("no-progress-reporting", "importNotes", null); if (demoFile) { await zipImportService.importZip(dummyTaskContext, demoFile, rootNote); diff --git a/apps/server/src/services/sync.ts b/apps/server/src/services/sync.ts index a26fabf82..ef3bd6cba 100644 --- a/apps/server/src/services/sync.ts +++ b/apps/server/src/services/sync.ts @@ -17,7 +17,7 @@ import ws from "./ws.js"; import entityChangesService from "./entity_changes.js"; import entityConstructor from "../becca/entity_constructor.js"; import becca from "../becca/becca.js"; -import type { EntityChange, EntityChangeRecord, EntityRow } from "./entity_changes_interface.js"; +import type { EntityChange, EntityChangeRecord, EntityRow } from "@triliumnext/commons"; import type { CookieJar, ExecOpts } from "./request_interface.js"; import setupService from "./setup.js"; import consistency_checks from "./consistency_checks.js"; diff --git a/apps/server/src/services/sync_update.ts b/apps/server/src/services/sync_update.ts index a22ba6717..9d4ff5c4c 100644 --- a/apps/server/src/services/sync_update.ts +++ b/apps/server/src/services/sync_update.ts @@ -4,7 +4,7 @@ import entityChangesService from "./entity_changes.js"; import eventService from "./events.js"; import entityConstructor from "../becca/entity_constructor.js"; import ws from "./ws.js"; -import type { EntityChange, EntityChangeRecord, EntityRow } from "./entity_changes_interface.js"; +import type { EntityChange, EntityChangeRecord, EntityRow } from "@triliumnext/commons"; interface UpdateContext { alreadyErased: number; diff --git a/apps/server/src/services/task_context.ts b/apps/server/src/services/task_context.ts index f83154147..79122895b 100644 --- a/apps/server/src/services/task_context.ts +++ b/apps/server/src/services/task_context.ts @@ -1,20 +1,20 @@ "use strict"; -import type { TaskData } from "./task_context_interface.js"; +import type { TaskData, TaskResult, TaskType, WebSocketMessage } from "@triliumnext/commons"; import ws from "./ws.js"; // taskId => TaskContext -const taskContexts: Record = {}; +const taskContexts: Record> = {}; -class TaskContext { +class TaskContext { private taskId: string; - private taskType: string | null; + private taskType: TaskType; private progressCount: number; private lastSentCountTs: number; - data: TaskData | null; + data: TaskData; noteDeletionHandlerTriggered: boolean; - constructor(taskId: string, taskType: string | null = null, data: {} | null = {}) { + constructor(taskId: string, taskType: T, data: TaskData) { this.taskId = taskId; this.taskType = taskType; this.data = data; @@ -31,7 +31,7 @@ class TaskContext { this.increaseProgressCount(); } - static getInstance(taskId: string, taskType: string, data: {} | null = null): TaskContext { + static getInstance(taskId: string, taskType: T, data: TaskData): TaskContext { if (!taskContexts[taskId]) { taskContexts[taskId] = new TaskContext(taskId, taskType, data); } @@ -51,7 +51,7 @@ class TaskContext { taskType: this.taskType, data: this.data, progressCount: this.progressCount - }); + } as WebSocketMessage); } } @@ -61,18 +61,18 @@ class TaskContext { taskId: this.taskId, taskType: this.taskType, data: this.data, - message: message - }); + message + } as WebSocketMessage); } - taskSucceeded(result?: string | Record) { + taskSucceeded(result: TaskResult) { ws.sendMessageToAllClients({ type: "taskSucceeded", taskId: this.taskId, taskType: this.taskType, data: this.data, - result: result - }); + result + } as WebSocketMessage); } } diff --git a/apps/server/src/services/task_context_interface.ts b/apps/server/src/services/task_context_interface.ts deleted file mode 100644 index 3c359d742..000000000 --- a/apps/server/src/services/task_context_interface.ts +++ /dev/null @@ -1,7 +0,0 @@ -export interface TaskData { - safeImport?: boolean; - textImportedAsText?: boolean; - codeImportedAsCode?: boolean; - shrinkImages?: boolean; - replaceUnderscoresWithSpaces?: boolean; -} diff --git a/apps/server/src/services/ws.ts b/apps/server/src/services/ws.ts index c37cf7550..9dfcbc019 100644 --- a/apps/server/src/services/ws.ts +++ b/apps/server/src/services/ws.ts @@ -1,5 +1,5 @@ import { WebSocketServer as WebSocketServer, WebSocket } from "ws"; -import { isDev, isElectron, randomString } from "./utils.js"; +import { isElectron, randomString } from "./utils.js"; import log from "./log.js"; import sql from "./sql.js"; import cls from "./cls.js"; @@ -10,56 +10,10 @@ import becca from "../becca/becca.js"; import AbstractBeccaEntity from "../becca/entities/abstract_becca_entity.js"; import type { IncomingMessage, Server as HttpServer } from "http"; -import type { EntityChange } from "./entity_changes_interface.js"; +import { WebSocketMessage, type EntityChange } from "@triliumnext/commons"; let webSocketServer!: WebSocketServer; -let lastSyncedPush: number | null = null; - -interface Message { - type: string; - data?: { - lastSyncedPush?: number | null; - entityChanges?: any[]; - shrinkImages?: boolean; - } | null; - lastSyncedPush?: number | null; - - progressCount?: number; - taskId?: string; - taskType?: string | null; - message?: string; - reason?: string; - result?: string | Record; - - script?: string; - params?: any[]; - noteId?: string; - messages?: string[]; - startNoteId?: string; - currentNoteId?: string; - entityType?: string; - entityId?: string; - originEntityName?: "notes"; - originEntityId?: string | null; - lastModifiedMs?: number; - filePath?: string; - - // LLM streaming specific fields - chatNoteId?: string; - content?: string; - thinking?: string; - toolExecution?: { - action?: string; - tool?: string; - toolCallId?: string; - result?: string | Record; - error?: string; - args?: Record; - }; - done?: boolean; - error?: string; - raw?: unknown; -} +let lastSyncedPush: number; type SessionParser = (req: IncomingMessage, params: {}, cb: () => void) => void; function init(httpServer: HttpServer, sessionParser: SessionParser) { @@ -106,7 +60,7 @@ Stack: ${message.stack}`); }); } -function sendMessage(client: WebSocket, message: Message) { +function sendMessage(client: WebSocket, message: WebSocketMessage) { const jsonStr = JSON.stringify(message); if (client.readyState === WebSocket.OPEN) { @@ -114,7 +68,7 @@ function sendMessage(client: WebSocket, message: Message) { } } -function sendMessageToAllClients(message: Message) { +function sendMessageToAllClients(message: WebSocketMessage) { const jsonStr = JSON.stringify(message); if (webSocketServer) { diff --git a/docs/User Guide/!!!meta.json b/docs/User Guide/!!!meta.json index 7a1d0831b..2a90f721a 100644 --- a/docs/User Guide/!!!meta.json +++ b/docs/User Guide/!!!meta.json @@ -1728,6 +1728,13 @@ "value": "bx bx-menu", "isInheritable": false, "position": 10 + }, + { + "type": "relation", + "name": "internalLink", + "value": "MKmLg5x6xkor", + "isInheritable": false, + "position": 200 } ], "format": "markdown", diff --git a/docs/User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tree/Note tree contextual menu.md b/docs/User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tree/Note tree contextual menu.md index 2df2536cf..5ecb4bbd3 100644 --- a/docs/User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tree/Note tree contextual menu.md +++ b/docs/User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tree/Note tree contextual menu.md @@ -52,6 +52,10 @@ The contextual menu can operate: * Creates a copy of the note and its descendants. * This process is different from Cloning Notes since the duplicated note can be edited independently from the original. * An alternative to this, if done regularly, would be Templates. +* **Archive/Unarchive** + * Marks a note as [archived](../../Notes/Archived%20Notes.md). + * If the note is already archived, it will be unarchived instead. + * Multiple notes can be selected as well. However, all the selected notes must be in the same state (archived or not), otherwise the option will be disabled. * **Delete** * Will delete the given notes, asking for confirmation first. * In the dialog, the following options can be configured: diff --git a/docs/User Guide/User Guide/Note Types/Collections.md b/docs/User Guide/User Guide/Note Types/Collections.md index 81f177fd5..03bc5140c 100644 --- a/docs/User Guide/User Guide/Note Types/Collections.md +++ b/docs/User Guide/User Guide/Note Types/Collections.md @@ -47,6 +47,12 @@ By default, collections come with a default configuration and sometimes even sam 3. Still in the ribbon, go to _Collection Properties_ and select the desired view type. 4. Consult the help page of the corresponding view type in order to understand how to configure them. +## Archived notes + +By default, archived notes will not be shown in collections. This behaviour can be changed by going to _Collection Properties_ in the Ribbon and checking _Show archived notes_. + +Archived notes will be generally indicated by being greyed out as opposed to the normal ones. + ## Under the hood Collections by themselves are simply notes with no content that rely on the Note List mechanism (the one that lists the children notes at the bottom of a note) to display information. diff --git a/docs/User Guide/User Guide/Note Types/Collections/Board View.md b/docs/User Guide/User Guide/Note Types/Collections/Board View.md index 6a631bd75..4b8bcbdbc 100644 --- a/docs/User Guide/User Guide/Note Types/Collections/Board View.md +++ b/docs/User Guide/User Guide/Note Types/Collections/Board View.md @@ -12,7 +12,7 @@ Notes are displayed recursively, so even the child notes of the child notes will ## Interaction with columns * Create a new column by pressing _Add Column_ near the last column. - * Once pressed, a text box will be displayed to set the name of the column. Press Enter to confirm. + * Once pressed, a text box will be displayed to set the name of the column. Press Enter to confirm, or Escape to dismiss. * To reorder a column, simply hold the mouse over the title and drag it to the desired position. * To delete a column, right click on its title and select _Delete column_. * To rename a column, click on the note title. @@ -23,8 +23,10 @@ Notes are displayed recursively, so even the child notes of the child notes will ## Interaction with notes * Create a new note in any column by pressing _New item_ - * Enter the name of the note and press _Enter_. - * Doing so will create a new note. The new note will have an attribute (`status` label by default) set to the name of the column. + * Enter the name of the note and press Enter or click away. To dismiss the creation of a new note, simply press Escape or leave the name empty. + * Once created, the new note will have an attribute (`status` label by default) set to the name of the column. +* To open the note, simply click on it. +* To change the title of the note directly from the board, hover the mouse over its card and press the edit button on the right. * To change the state of a note, simply drag a note from one column to the other to change its state. * The order of the notes in each column corresponds to their position in the tree. * It's possible to reorder notes simply by dragging them to the desired position within the same columns. @@ -33,6 +35,7 @@ Notes are displayed recursively, so even the child notes of the child notes will * Open the note in a new tab/split/window or quick edit. * Move the note to any column. * Insert a new note above/below the current one. + * Archive/unarchive the current note. * Delete the current note. * If there are many notes within the column, move the mouse over the column and use the mouse wheel to scroll. diff --git a/docs/User Guide/User Guide/Note Types/Collections/Geo Map View.md b/docs/User Guide/User Guide/Note Types/Collections/Geo Map View.md index ae0be91ce..a060d385e 100644 --- a/docs/User Guide/User Guide/Note Types/Collections/Geo Map View.md +++ b/docs/User Guide/User Guide/Note Types/Collections/Geo Map View.md @@ -26,8 +26,8 @@ The position on the map and the zoom are saved inside the map note and restored | | | | | --- | --- | --- | -| 1 | To create a marker, first navigate to the desired point on the map. Then press the ![](10_Geo%20Map%20View_image.png) button in the [Floating buttons](../../Basic%20Concepts%20and%20Features/UI%20Elements/Floating%20buttons.md) (top-right) area.  

          If the button is not visible, make sure the button section is visible by pressing the chevron button (![](17_Geo%20Map%20View_image.png)) in the top-right of the map. | | -| 2 | | Once pressed, the map will enter in the insert mode, as illustrated by the notification.     

          Simply click the point on the map where to place the marker, or the Escape key to cancel. | +| 1 | To create a marker, first navigate to the desired point on the map. Then press the ![](10_Geo%20Map%20View_image.png) button in the [Floating buttons](../../Basic%20Concepts%20and%20Features/UI%20Elements/Floating%20buttons.md) (top-right) area.   

          If the button is not visible, make sure the button section is visible by pressing the chevron button (![](17_Geo%20Map%20View_image.png)) in the top-right of the map. | | +| 2 | | Once pressed, the map will enter in the insert mode, as illustrated by the notification.      

          Simply click the point on the map where to place the marker, or the Escape key to cancel. | | 3 | | Enter the name of the marker/note to be created. | | 4 | | Once confirmed, the marker will show up on the map and it will also be displayed as a child note of the map. | @@ -50,6 +50,9 @@ This works for: * Notes that are a child of the geo map but not yet positioned on the map. * Notes that are a child of the geo map and also positioned, case in which the marker will be relocated to the new position. +> [!NOTE] +> Dragging existing notes only works if the map is in editing mode. See the _Read-only_ section for more information. + ## How the location of the markers is stored The location of a marker is stored in the `#geolocation` attribute of the child notes: @@ -106,7 +109,7 @@ The value of the attribute is made up of the latitude and longitude separated by | | | | | --- | --- | --- | -| 1 |

          | Go to Google Maps on the web and look for a desired location, right click on it and a context menu will show up.     

          Simply click on the first item displaying the coordinates and they will be copied to clipboard.     

          Then paste the value inside the text box into the `#geolocation` attribute of a child note of the map (don't forget to surround the value with a `"` character). | +| 1 |
          | Go to Google Maps on the web and look for a desired location, right click on it and a context menu will show up.      

          Simply click on the first item displaying the coordinates and they will be copied to clipboard.      

          Then paste the value inside the text box into the `#geolocation` attribute of a child note of the map (don't forget to surround the value with a `"` character). | | 2 |
          | In Trilium, create a child note under the map. | | 3 |
          | And then go to Owned Attributes and type `#geolocation="`, then paste from the clipboard as-is and then add the ending `"` character. Press Enter to confirm and the map should now be updated to contain the new note. | @@ -117,7 +120,7 @@ Similarly to the Google Maps approach: | | | | | --- | --- | --- | | 1 | | Go to any location on openstreetmap.org and right click to bring up the context menu. Select the “Show address” item. | -| 2 | | The address will be visible in the top-left of the screen, in the place of the search bar.     

          Select the coordinates and copy them into the clipboard. | +| 2 | | The address will be visible in the top-left of the screen, in the place of the search bar.      

          Select the coordinates and copy them into the clipboard. | | 3 | | Simply paste the value inside the text box into the `#geolocation` attribute of a child note of the map and then it should be displayed on the map. | ## Adding GPS tracks (.gpx) @@ -128,7 +131,7 @@ Trilium has basic support for displaying GPS tracks on the geo map. | --- | --- | --- | | 1 |
          | To add a track, simply drag & drop a .gpx file inside the geo map in the note tree. | | 2 |
          | In order for the file to be recognized as a GPS track, it needs to show up as `application/gpx+xml` in the _File type_ field. | -| 3 |
          | When going back to the map, the track should now be visible.     

          The start and end points of the track are indicated by the two blue markers. | +| 3 |
          | When going back to the map, the track should now be visible.      

          The start and end points of the track are indicated by the two blue markers. | > [!NOTE] > The starting point of the track will be displayed as a marker, with the name of the note underneath. The start marker will also respect the icon and the `color` of the note. The end marker is displayed with a distinct icon. diff --git a/docs/User Guide/User Guide/Note Types/Collections/Table View.md b/docs/User Guide/User Guide/Note Types/Collections/Table View.md index 0454b0a93..78774734a 100644 --- a/docs/User Guide/User Guide/Note Types/Collections/Table View.md +++ b/docs/User Guide/User Guide/Note Types/Collections/Table View.md @@ -65,7 +65,8 @@ There are multiple menus: * Adding new columns. * Right clicking on a row, allows: * Opening the corresponding note of the row in a new tab, split, window or quick editing it. - * Inserting rows above, below or as a child note. + * Inserting a new note above or below the selected row. These options are only enabled if the table is not sorted. + * Inserting a new child note for the selected row. * Deleting the row. ### Editing data diff --git a/packages/commons/src/index.ts b/packages/commons/src/index.ts index 432990bc0..ef60f29be 100644 --- a/packages/commons/src/index.ts +++ b/packages/commons/src/index.ts @@ -8,3 +8,5 @@ export * from "./lib/mime_type.js"; export * from "./lib/bulk_actions.js"; export * from "./lib/server_api.js"; export * from "./lib/shared_constants.js"; +export * from "./lib/ws_api.js"; +export * from "./lib/attribute_names.js"; diff --git a/packages/commons/src/lib/attribute_names.ts b/packages/commons/src/lib/attribute_names.ts new file mode 100644 index 000000000..8b8de89c1 --- /dev/null +++ b/packages/commons/src/lib/attribute_names.ts @@ -0,0 +1,58 @@ +/** + * A listing of all the labels used by the system (i.e. not user-defined). Labels defined here have a data type which is not enforced, but offers type safety. + */ +type Labels = { + color: string; + iconClass: string; + workspaceIconClass: string; + executeDescription: string; + executeTitle: string; + limit: string; // should be probably be number + calendarRoot: boolean; + workspaceCalendarRoot: boolean; + archived: boolean; + sorted: boolean; + template: boolean; + autoReadOnlyDisabled: boolean; + language: string; + originalFileName: string; + pageUrl: string; + + // Search + searchString: string; + ancestorDepth: string; + orderBy: string; + orderDirection: string; + + // Collection-specific + viewType: string; + status: string; + pageSize: number; + geolocation: string; + readOnly: boolean; + expanded: boolean; + "calendar:hideWeekends": boolean; + "calendar:weekNumbers": boolean; + "calendar:view": string; + "map:style": string; + "map:scale": boolean; + "board:groupBy": string; + maxNestingDepth: number; + includeArchived: boolean; +} + +/** + * A listing of all relations used by the system (i.e. not user-defined). Unlike labels, relations + * always point to a note ID, so no specific data type is necessary. + */ +type Relations = [ + "searchScript", + "ancestor" +]; + +export type LabelNames = keyof Labels; +export type RelationNames = Relations[number]; + +export type FilterLabelsByType = { + [K in keyof Labels]: Labels[K] extends U ? K : never; +}[keyof Labels]; diff --git a/packages/commons/src/lib/ws_api.ts b/packages/commons/src/lib/ws_api.ts new file mode 100644 index 000000000..67beb0b42 --- /dev/null +++ b/packages/commons/src/lib/ws_api.ts @@ -0,0 +1,158 @@ +export interface EntityChange { + id?: number | null; + noteId?: string; + entityName: string; + entityId: string; + entity?: any; + positions?: Record; + hash: string; + utcDateChanged?: string; + utcDateModified?: string; + utcDateCreated?: string; + isSynced: boolean | 1 | 0; + isErased: boolean | 1 | 0; + componentId?: string | null; + changeId?: string | null; + instanceId?: string | null; +} + +export interface EntityRow { + isDeleted?: boolean; + content?: Buffer | string; +} + +export interface EntityChangeRecord { + entityChange: EntityChange; + entity?: EntityRow; +} + +type TaskDataDefinitions = { + empty: null, + deleteNotes: null, + undeleteNotes: null, + export: null, + protectNotes: { + protect: boolean; + } + importNotes: { + textImportedAsText?: boolean; + codeImportedAsCode?: boolean; + replaceUnderscoresWithSpaces?: boolean; + shrinkImages?: boolean; + safeImport?: boolean; + } | null, + importAttachments: null +} + +type TaskResultDefinitions = { + empty: null, + deleteNotes: null, + undeleteNotes: null, + export: null, + protectNotes: null, + importNotes: { + parentNoteId?: string; + importedNoteId?: string + }; + importAttachments: { + parentNoteId?: string; + importedNoteId?: string + }; +} + +export type TaskType = keyof TaskDataDefinitions | keyof TaskResultDefinitions; +export type TaskData = TaskDataDefinitions[T]; +export type TaskResult = TaskResultDefinitions[T]; + +type TaskDefinition = { + type: "taskProgressCount", + taskId: string; + taskType: T; + data: TaskData, + progressCount: number +} | { + type: "taskError", + taskId: string; + taskType: T; + data: TaskData, + message: string; +} | { + type: "taskSucceeded", + taskId: string; + taskType: T; + data: TaskData, + result: TaskResult; +} + +export interface OpenedFileUpdateStatus { + entityType: string; + entityId: string; + lastModifiedMs?: number; + filePath: string; +} + +type AllTaskDefinitions = + | TaskDefinition<"empty"> + | TaskDefinition<"deleteNotes"> + | TaskDefinition<"undeleteNotes"> + | TaskDefinition<"export"> + | TaskDefinition<"protectNotes"> + | TaskDefinition<"importNotes"> + | TaskDefinition<"importAttachments">; + +export type WebSocketMessage = AllTaskDefinitions | { + type: "ping" +} | { + type: "frontend-update", + data: { + lastSyncedPush: number, + entityChanges: EntityChange[] + } +} | { + type: "openNote", + noteId: string +} | OpenedFileUpdateStatus & { + type: "openedFileUpdated" +} | { + type: "protectedSessionLogin" +} | { + type: "protectedSessionLogout" +} | { + type: "toast", + message: string; +} | { + type: "api-log-messages", + noteId: string, + messages: string[] +} | { + type: "execute-script"; + script: string; + params: unknown[]; + startNoteId?: string; + currentNoteId: string; + originEntityName: string; + originEntityId?: string | null; +} | { + type: "reload-frontend"; + reason: string; +} | { + type: "sync-pull-in-progress" | "sync-push-in-progress" | "sync-finished" | "sync-failed"; + lastSyncedPush: number; +} | { + type: "consistency-checks-failed" +} | { + type: "llm-stream", + chatNoteId: string; + done?: boolean; + error?: string; + thinking?: string; + content?: string; + toolExecution?: { + action?: string; + tool?: string; + toolCallId?: string; + result?: string | Record; + error?: string; + args?: Record; + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1475e2ebb..00c3b4989 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15149,8 +15149,6 @@ snapshots: '@ckeditor/ckeditor5-ui': 46.1.0 '@ckeditor/ckeditor5-undo': 46.1.0 ckeditor5: 46.1.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-export-inline-styles@46.1.0': dependencies: @@ -17298,7 +17296,7 @@ snapshots: '@jridgewell/source-map@0.3.10': dependencies: '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.29 + '@jridgewell/trace-mapping': 0.3.31 '@jridgewell/source-map@0.3.6': dependencies: @@ -19438,7 +19436,6 @@ snapshots: '@types/node@24.3.0': dependencies: undici-types: 7.10.0 - optional: true '@types/parse-json@4.0.2': {} @@ -24353,7 +24350,7 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 22.18.1 + '@types/node': 24.3.0 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -29474,8 +29471,7 @@ snapshots: undici-types@6.21.0: {} - undici-types@7.10.0: - optional: true + undici-types@7.10.0: {} undici@6.21.3: {}