mirror of
https://github.com/zadam/trilium.git
synced 2025-11-26 02:24:23 +01:00
Merge branch 'TriliumNext:main' into main
This commit is contained in:
commit
0bcc02dfab
@ -40,7 +40,7 @@
|
||||
"@types/express": "5.0.5",
|
||||
"@types/node": "24.10.1",
|
||||
"@types/yargs": "17.0.35",
|
||||
"@vitest/coverage-v8": "4.0.12",
|
||||
"@vitest/coverage-v8": "4.0.13",
|
||||
"eslint": "9.39.1",
|
||||
"eslint-plugin-simple-import-sort": "12.1.1",
|
||||
"esm": "3.2.25",
|
||||
|
||||
@ -445,6 +445,7 @@ type EventMappings = {
|
||||
error: string;
|
||||
};
|
||||
searchRefreshed: { ntxId?: string | null };
|
||||
textEditorRefreshed: { ntxId?: string | null, editor: CKTextEditor };
|
||||
hoistedNoteChanged: {
|
||||
noteId: string;
|
||||
ntxId: string | null;
|
||||
|
||||
@ -44,6 +44,7 @@ import UploadAttachmentsDialog from "../widgets/dialogs/upload_attachments.js";
|
||||
import utils from "../services/utils.js";
|
||||
import WatchedFileUpdateStatusWidget from "../widgets/watched_file_update_status.js";
|
||||
import NoteDetail from "../widgets/NoteDetail.jsx";
|
||||
import RightPanelWidget from "../widgets/sidebar/RightPanelWidget.jsx";
|
||||
|
||||
export default class DesktopLayout {
|
||||
|
||||
|
||||
@ -22,16 +22,8 @@ import RevisionsDialog from "../widgets/dialogs/revisions.js";
|
||||
import DeleteNotesDialog from "../widgets/dialogs/delete_notes.js";
|
||||
import InfoDialog from "../widgets/dialogs/info.js";
|
||||
import IncorrectCpuArchDialog from "../widgets/dialogs/incorrect_cpu_arch.js";
|
||||
import PopupEditorDialog from "../widgets/dialogs/popup_editor.js";
|
||||
import FlexContainer from "../widgets/containers/flex_container.js";
|
||||
import NoteIconWidget from "../widgets/note_icon";
|
||||
import PromotedAttributesWidget from "../widgets/promoted_attributes.js";
|
||||
import CallToActionDialog from "../widgets/dialogs/call_to_action.jsx";
|
||||
import NoteTitleWidget from "../widgets/note_title.jsx";
|
||||
import FormattingToolbar from "../widgets/ribbon/FormattingToolbar.js";
|
||||
import NoteList from "../widgets/collections/NoteList.jsx";
|
||||
import NoteDetail from "../widgets/NoteDetail.jsx";
|
||||
import StandaloneRibbonAdapter from "../widgets/ribbon/components/StandaloneRibbonAdapter.jsx";
|
||||
import PopupEditorDialog from "../widgets/dialogs/PopupEditor.jsx";
|
||||
|
||||
export function applyModals(rootContainer: RootContainer) {
|
||||
rootContainer
|
||||
@ -57,16 +49,6 @@ export function applyModals(rootContainer: RootContainer) {
|
||||
.child(<ConfirmDialog />)
|
||||
.child(<PromptDialog />)
|
||||
.child(<IncorrectCpuArchDialog />)
|
||||
.child(new PopupEditorDialog()
|
||||
.child(new FlexContainer("row")
|
||||
.class("title-row")
|
||||
.css("align-items", "center")
|
||||
.cssBlock(".title-row > * { margin: 5px; }")
|
||||
.child(<NoteIconWidget />)
|
||||
.child(<NoteTitleWidget />))
|
||||
.child(<StandaloneRibbonAdapter component={FormattingToolbar} />)
|
||||
.child(new PromotedAttributesWidget())
|
||||
.child(<NoteDetail />)
|
||||
.child(<NoteList media="screen" displayOnlyCollections />))
|
||||
.child(<PopupEditorDialog />)
|
||||
.child(<CallToActionDialog />);
|
||||
}
|
||||
|
||||
@ -165,16 +165,19 @@ class ContextMenu {
|
||||
let $group = $parent; // The current group or parent element to which items are being appended
|
||||
let shouldStartNewGroup = false; // If true, the next item will start a new group
|
||||
let shouldResetGroup = false; // If true, the next item will be the last one from the group
|
||||
let prevItemKind: string = "";
|
||||
|
||||
for (let index = 0; index < items.length; index++) {
|
||||
const item = items[index];
|
||||
const itemKind = ("kind" in item) ? item.kind : "";
|
||||
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the current item is a header, start a new group. This group will contain the
|
||||
// header and the next item that follows the header.
|
||||
if ("kind" in item && item.kind === "header") {
|
||||
if (itemKind === "header") {
|
||||
if (multicolumn && !shouldResetGroup) {
|
||||
shouldStartNewGroup = true;
|
||||
}
|
||||
@ -200,19 +203,23 @@ class ContextMenu {
|
||||
shouldStartNewGroup = false;
|
||||
}
|
||||
|
||||
if ("kind" in item && item.kind === "separator") {
|
||||
if (itemKind === "separator") {
|
||||
if (prevItemKind === "separator") {
|
||||
// Skip consecutive separators
|
||||
continue;
|
||||
}
|
||||
$group.append($("<div>").addClass("dropdown-divider"));
|
||||
shouldResetGroup = true; // End the group after the next item
|
||||
} else if ("kind" in item && item.kind === "header") {
|
||||
$group.append($("<h6>").addClass("dropdown-header").text(item.title));
|
||||
} else if (itemKind === "header") {
|
||||
$group.append($("<h6>").addClass("dropdown-header").text((item as MenuHeader).title));
|
||||
shouldResetGroup = true;
|
||||
} else {
|
||||
if ("kind" in item && item.kind === "custom") {
|
||||
if (itemKind === "custom") {
|
||||
// Custom menu item
|
||||
$group.append(this.createCustomMenuItem(item));
|
||||
$group.append(this.createCustomMenuItem(item as CustomMenuItem));
|
||||
} else {
|
||||
// Standard menu item
|
||||
$group.append(this.createMenuItem(item));
|
||||
$group.append(this.createMenuItem(item as MenuCommandItem<any>));
|
||||
}
|
||||
|
||||
// After adding a menu item, if the previous item was a separator or header,
|
||||
@ -222,6 +229,9 @@ class ContextMenu {
|
||||
shouldResetGroup = false;
|
||||
};
|
||||
}
|
||||
|
||||
prevItemKind = itemKind;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -244,16 +244,12 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
|
||||
|
||||
{ kind: "separator"},
|
||||
|
||||
{
|
||||
(notOptionsOrHelp && selectedNotes.length === 1) ? {
|
||||
kind: "custom",
|
||||
componentFn: () => {
|
||||
if (notOptionsOrHelp && selectedNotes.length === 1) {
|
||||
return NoteColorPicker({note});
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return NoteColorPicker({note});
|
||||
}
|
||||
},
|
||||
} : null,
|
||||
|
||||
{ kind: "separator" },
|
||||
|
||||
|
||||
@ -28,7 +28,7 @@ async function getActionsForScope(scope: string) {
|
||||
return actions.filter((action) => action.scope === scope);
|
||||
}
|
||||
|
||||
async function setupActionsForElement(scope: string, $el: JQuery<HTMLElement>, component: Component) {
|
||||
async function setupActionsForElement(scope: string, $el: JQuery<HTMLElement>, component: Component, ntxId: string | null | undefined) {
|
||||
if (!$el[0]) return [];
|
||||
|
||||
const actions = await getActionsForScope(scope);
|
||||
@ -36,7 +36,9 @@ async function setupActionsForElement(scope: string, $el: JQuery<HTMLElement>, c
|
||||
|
||||
for (const action of actions) {
|
||||
for (const shortcut of action.effectiveShortcuts ?? []) {
|
||||
const binding = shortcutService.bindElShortcut($el, shortcut, () => component.triggerCommand(action.actionName, { ntxId: appContext.tabManager.activeNtxId }));
|
||||
const binding = shortcutService.bindElShortcut($el, shortcut, () => {
|
||||
component.triggerCommand(action.actionName, { ntxId });
|
||||
});
|
||||
if (binding) {
|
||||
bindings.push(binding);
|
||||
}
|
||||
|
||||
@ -2591,7 +2591,7 @@ iframe.print-iframe {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.scrolling-container > .note-detail.full-height,
|
||||
.note-detail.full-height,
|
||||
.scrolling-container > .note-list-widget.full-height {
|
||||
position: relative;
|
||||
flex-grow: 1;
|
||||
|
||||
@ -2091,5 +2091,10 @@
|
||||
"auto-read-only-note": "这条笔记以只读模式显示便于快速加载。",
|
||||
"auto-read-only-learn-more": "了解更多",
|
||||
"edit-note": "编辑笔记"
|
||||
},
|
||||
"note-color": {
|
||||
"clear-color": "清除笔记颜色",
|
||||
"set-color": "设置笔记颜色",
|
||||
"set-custom-color": "设置自定义笔记颜色"
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,7 +39,10 @@
|
||||
"help_on_tree_prefix": "Aide sur le préfixe de l'arbre",
|
||||
"prefix": "Préfixe : ",
|
||||
"save": "Sauvegarder",
|
||||
"branch_prefix_saved": "Le préfixe de la branche a été enregistré."
|
||||
"branch_prefix_saved": "Le préfixe de la branche a été enregistré.",
|
||||
"edit_branch_prefix_multiple": "Modifier le préfixe de branche pour {{count}} branches",
|
||||
"branch_prefix_saved_multiple": "Le préfixe de la branche a été sauvegardé pour {{count}} branches.",
|
||||
"affected_branches": "Branches impactées ({{count}}):"
|
||||
},
|
||||
"bulk_actions": {
|
||||
"bulk_actions": "Actions groupées",
|
||||
|
||||
@ -1930,7 +1930,7 @@
|
||||
"search-for": "「{{term}}」を検索",
|
||||
"create-note": "子ノート「{{term}}」を作成してリンクする",
|
||||
"insert-external-link": "「{{term}}」への外部リンクを挿入",
|
||||
"clear-text-field": "テキストフィールドを消去",
|
||||
"clear-text-field": "テキストフィールドをクリア",
|
||||
"show-recent-notes": "最近のノートを表示",
|
||||
"full-text-search": "全文検索"
|
||||
},
|
||||
@ -2091,5 +2091,10 @@
|
||||
"auto-read-only-note": "このノートは読み込みを高速化するために読み取り専用モードで表示されています。",
|
||||
"auto-read-only-learn-more": "さらに詳しく",
|
||||
"edit-note": "ノートを編集"
|
||||
},
|
||||
"note-color": {
|
||||
"clear-color": "ノートの色をクリア",
|
||||
"set-color": "ノートの色を設定",
|
||||
"set-custom-color": "ノートの色をカスタム設定"
|
||||
}
|
||||
}
|
||||
|
||||
@ -2091,5 +2091,10 @@
|
||||
"auto-read-only-note": "此筆記以唯讀模式顯示以加快載入速度。",
|
||||
"auto-read-only-learn-more": "了解更多",
|
||||
"edit-note": "編輯筆記"
|
||||
},
|
||||
"note-color": {
|
||||
"clear-color": "清除筆記顏色",
|
||||
"set-color": "設定筆記顏色",
|
||||
"set-custom-color": "設定自訂筆記顏色"
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,19 +2,13 @@ import { allViewTypes, ViewModeMedia, ViewModeProps, ViewTypeOptions } from "./i
|
||||
import { useNoteContext, useNoteLabel, useNoteLabelBoolean, useTriliumEvent } from "../react/hooks";
|
||||
import FNote from "../../entities/fnote";
|
||||
import "./NoteList.css";
|
||||
import { ListView, GridView } from "./legacy/ListOrGridView";
|
||||
import { useEffect, useRef, useState } from "preact/hooks";
|
||||
import GeoView from "./geomap";
|
||||
import ViewModeStorage from "./view_mode_storage";
|
||||
import CalendarView from "./calendar";
|
||||
import TableView from "./table";
|
||||
import BoardView from "./board";
|
||||
import { subscribeToMessages, unsubscribeToMessage as unsubscribeFromMessage } from "../../services/ws";
|
||||
import { WebSocketMessage } from "@triliumnext/commons";
|
||||
import froca from "../../services/froca";
|
||||
import PresentationView from "./presentation";
|
||||
import { ListPrintView } from "./legacy/ListPrintView";
|
||||
|
||||
import { lazy, Suspense } from "preact/compat";
|
||||
import { VNode } from "preact";
|
||||
interface NoteListProps {
|
||||
note: FNote | null | undefined;
|
||||
notePath: string | null | undefined;
|
||||
@ -29,6 +23,33 @@ interface NoteListProps {
|
||||
onProgressChanged?(progress: number): void;
|
||||
}
|
||||
|
||||
type LazyLoadedComponent = ((props: ViewModeProps<any>) => VNode<any> | undefined);
|
||||
const ViewComponents: Record<ViewTypeOptions, { normal: LazyLoadedComponent, print?: LazyLoadedComponent }> = {
|
||||
list: {
|
||||
normal: lazy(() => import("./legacy/ListOrGridView.js").then(i => i.ListView)),
|
||||
print: lazy(() => import("./legacy/ListPrintView.js").then(i => i.ListPrintView))
|
||||
},
|
||||
grid: {
|
||||
normal: lazy(() => import("./legacy/ListOrGridView.js").then(i => i.GridView)),
|
||||
},
|
||||
geoMap: {
|
||||
normal: lazy(() => import("./geomap/index.js")),
|
||||
},
|
||||
calendar: {
|
||||
normal: lazy(() => import("./calendar/index.js"))
|
||||
},
|
||||
table: {
|
||||
normal: lazy(() => import("./table/index.js")),
|
||||
print: lazy(() => import("./table/TablePrintView.js"))
|
||||
},
|
||||
board: {
|
||||
normal: lazy(() => import("./board/index.js"))
|
||||
},
|
||||
presentation: {
|
||||
normal: lazy(() => import("./presentation/index.js"))
|
||||
}
|
||||
}
|
||||
|
||||
export default function NoteList(props: Pick<NoteListProps, "displayOnlyCollections" | "media" | "onReady" | "onProgressChanged">) {
|
||||
const { note, noteContext, notePath, ntxId } = useNoteContext();
|
||||
const viewType = useNoteViewType(note);
|
||||
@ -93,40 +114,23 @@ export function CustomNoteList({ note, viewType, isEnabled: shouldEnable, notePa
|
||||
}
|
||||
}
|
||||
|
||||
const ComponentToRender = viewType && props && isEnabled && (
|
||||
props.media === "print" ? ViewComponents[viewType].print : ViewComponents[viewType].normal
|
||||
);
|
||||
|
||||
return (
|
||||
<div ref={widgetRef} className={`note-list-widget component ${isFullHeight && isEnabled ? "full-height" : ""}`}>
|
||||
{props && isEnabled && (
|
||||
{ComponentToRender && props && (
|
||||
<div className="note-list-widget-content">
|
||||
{getComponentByViewType(viewType, props)}
|
||||
<Suspense fallback="">
|
||||
<ComponentToRender {...props} />
|
||||
</Suspense>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function getComponentByViewType(viewType: ViewTypeOptions, props: ViewModeProps<any>) {
|
||||
switch (viewType) {
|
||||
case "list":
|
||||
if (props.media !== "print") {
|
||||
return <ListView {...props} />;
|
||||
} else {
|
||||
return <ListPrintView {...props} />;
|
||||
}
|
||||
case "grid":
|
||||
return <GridView {...props} />;
|
||||
case "geoMap":
|
||||
return <GeoView {...props} />;
|
||||
case "calendar":
|
||||
return <CalendarView {...props} />
|
||||
case "table":
|
||||
return <TableView {...props} />
|
||||
case "board":
|
||||
return <BoardView {...props} />
|
||||
case "presentation":
|
||||
return <PresentationView {...props} />
|
||||
}
|
||||
}
|
||||
|
||||
export function useNoteViewType(note?: FNote | null): ViewTypeOptions | undefined {
|
||||
const [ viewType ] = useNoteLabel(note, "viewType");
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
position: relative;
|
||||
height: 100%;
|
||||
user-select: none;
|
||||
overflow-x: auto;
|
||||
|
||||
--card-font-size: 0.9em;
|
||||
--card-line-height: 1.2;
|
||||
|
||||
20
apps/client/src/widgets/collections/table/TablePrintView.css
Normal file
20
apps/client/src/widgets/collections/table/TablePrintView.css
Normal file
@ -0,0 +1,20 @@
|
||||
.table-print-view .tabulator-print-table table,
|
||||
.table-print-view .tabulator-print-table th,
|
||||
.table-print-view .tabulator-print-table tr,
|
||||
.table-print-view .tabulator-print-table td {
|
||||
border: 1px solid black;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.table-print-view .tabulator-print-table th {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.table-print-view .tabulator-print-table th,
|
||||
.table-print-view .tabulator-print-table td {
|
||||
padding: 0.25rem 0.5rem;
|
||||
}
|
||||
|
||||
.table-print-view .tabulator-print-table td[aria-checked] svg path {
|
||||
fill: currentColor;
|
||||
}
|
||||
49
apps/client/src/widgets/collections/table/TablePrintView.tsx
Normal file
49
apps/client/src/widgets/collections/table/TablePrintView.tsx
Normal file
@ -0,0 +1,49 @@
|
||||
import { useEffect, useRef, useState } from "preact/hooks";
|
||||
import { ViewModeProps } from "../interface";
|
||||
import useData, { TableConfig } from "./data";
|
||||
import { ExportModule, FormatModule, Tabulator as VanillaTabulator} from 'tabulator-tables';
|
||||
import Tabulator from "./tabulator";
|
||||
import { RawHtmlBlock } from "../../react/RawHtml";
|
||||
import "./TablePrintView.css";
|
||||
|
||||
export default function TablePrintView({ note, noteIds, viewConfig, onReady }: ViewModeProps<TableConfig>) {
|
||||
const tabulatorRef = useRef<VanillaTabulator>(null);
|
||||
const { columnDefs, rowData, hasChildren } = useData(note, noteIds, viewConfig, undefined, () => {});
|
||||
const [ html, setHtml ] = useState<string>();
|
||||
|
||||
useEffect(() => {
|
||||
if (!html) return;
|
||||
onReady?.();
|
||||
}, [ html ]);
|
||||
|
||||
return rowData && (
|
||||
<>
|
||||
<h1>{note.title}</h1>
|
||||
|
||||
<div className="table-print-view">
|
||||
|
||||
{!html ? (
|
||||
<Tabulator
|
||||
tabulatorRef={tabulatorRef}
|
||||
className="table-print-view-container"
|
||||
modules={[ ExportModule, FormatModule ]}
|
||||
columns={columnDefs ?? []}
|
||||
data={rowData}
|
||||
index="branchId"
|
||||
dataTree={hasChildren}
|
||||
printAsHtml={true}
|
||||
printStyled={false}
|
||||
onReady={() => {
|
||||
const tabulator = tabulatorRef.current;
|
||||
if (!tabulator) return;
|
||||
setHtml(tabulator.getHtml());
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<RawHtmlBlock html={html} />
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
|
||||
)
|
||||
}
|
||||
77
apps/client/src/widgets/collections/table/data.tsx
Normal file
77
apps/client/src/widgets/collections/table/data.tsx
Normal file
@ -0,0 +1,77 @@
|
||||
import type { ColumnDefinition } from "tabulator-tables";
|
||||
import FNote from "../../../entities/fnote";
|
||||
import { useNoteLabelBoolean, useNoteLabelInt, useTriliumEvent } from "../../react/hooks";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
import getAttributeDefinitionInformation, { buildRowDefinitions, TableData } from "./rows";
|
||||
import froca from "../../../services/froca";
|
||||
import { buildColumnDefinitions } from "./columns";
|
||||
import attributes from "../../../services/attributes";
|
||||
import { RefObject } from "preact";
|
||||
|
||||
export interface TableConfig {
|
||||
tableData: {
|
||||
columns?: ColumnDefinition[];
|
||||
};
|
||||
}
|
||||
|
||||
export default function useData(note: FNote, noteIds: string[], viewConfig: TableConfig | undefined, newAttributePosition: RefObject<number | undefined> | undefined, resetNewAttributePosition: () => void) {
|
||||
const [ maxDepth ] = useNoteLabelInt(note, "maxNestingDepth");
|
||||
const [ includeArchived ] = useNoteLabelBoolean(note, "includeArchived");
|
||||
|
||||
const [ columnDefs, setColumnDefs ] = useState<ColumnDefinition[]>();
|
||||
const [ rowData, setRowData ] = useState<TableData[]>();
|
||||
const [ hasChildren, setHasChildren ] = useState<boolean>();
|
||||
const [ isSorted ] = useNoteLabelBoolean(note, "sorted");
|
||||
const [ movableRows, setMovableRows ] = useState(false);
|
||||
|
||||
async function refresh() {
|
||||
const info = getAttributeDefinitionInformation(note);
|
||||
|
||||
// Ensure all note IDs are loaded.
|
||||
await froca.getNotes(noteIds);
|
||||
|
||||
const { definitions: rowData, hasSubtree: hasChildren, rowNumber } = await buildRowDefinitions(note, info, includeArchived, maxDepth);
|
||||
const columnDefs = buildColumnDefinitions({
|
||||
info,
|
||||
movableRows,
|
||||
existingColumnData: viewConfig?.tableData?.columns,
|
||||
rowNumberHint: rowNumber,
|
||||
position: newAttributePosition?.current ?? undefined
|
||||
});
|
||||
setColumnDefs(columnDefs);
|
||||
setRowData(rowData);
|
||||
setHasChildren(hasChildren);
|
||||
resetNewAttributePosition();
|
||||
}
|
||||
|
||||
useEffect(() => { refresh() }, [ note, noteIds, maxDepth, movableRows ]);
|
||||
|
||||
useTriliumEvent("entitiesReloaded", ({ loadResults}) => {
|
||||
if (glob.device === "print") return;
|
||||
|
||||
// React to column changes.
|
||||
if (loadResults.getAttributeRows().find(attr =>
|
||||
attr.type === "label" &&
|
||||
(attr.name?.startsWith("label:") || attr.name?.startsWith("relation:")) &&
|
||||
attributes.isAffecting(attr, note))) {
|
||||
refresh();
|
||||
return;
|
||||
}
|
||||
|
||||
// React to external row updates.
|
||||
if (loadResults.getBranchRows().some(branch => branch.parentNoteId === note.noteId || noteIds.includes(branch.parentNoteId ?? ""))
|
||||
|| loadResults.getNoteIds().some(noteId => noteIds.includes(noteId))
|
||||
|| loadResults.getAttributeRows().some(attr => noteIds.includes(attr.noteId!))
|
||||
|| loadResults.getAttributeRows().some(attr => attr.name === "archived" && attr.noteId && noteIds.includes(attr.noteId))) {
|
||||
refresh();
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
// Identify if movable rows.
|
||||
useEffect(() => {
|
||||
setMovableRows(!isSorted && note.type !== "search" && !hasChildren);
|
||||
}, [ isSorted, note, hasChildren ]);
|
||||
|
||||
return { columnDefs, rowData, movableRows, hasChildren };
|
||||
}
|
||||
@ -1,10 +1,9 @@
|
||||
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||
import { ViewModeProps } from "../interface";
|
||||
import { buildColumnDefinitions } from "./columns";
|
||||
import getAttributeDefinitionInformation, { buildRowDefinitions, TableData } from "./rows";
|
||||
import { useLegacyWidget, useNoteLabelBoolean, useNoteLabelInt, useTriliumEvent } from "../../react/hooks";
|
||||
import { TableData } from "./rows";
|
||||
import { useLegacyWidget } from "../../react/hooks";
|
||||
import Tabulator from "./tabulator";
|
||||
import { Tabulator as VanillaTabulator, SortModule, FormatModule, InteractionModule, EditModule, ResizeColumnsModule, FrozenColumnsModule, PersistenceModule, MoveColumnsModule, MoveRowsModule, ColumnDefinition, DataTreeModule, Options, RowComponent} from 'tabulator-tables';
|
||||
import { Tabulator as VanillaTabulator, SortModule, FormatModule, InteractionModule, EditModule, ResizeColumnsModule, FrozenColumnsModule, PersistenceModule, MoveColumnsModule, MoveRowsModule, DataTreeModule, Options, RowComponent} from 'tabulator-tables';
|
||||
import { useContextMenu } from "./context_menu";
|
||||
import { ParentComponent } from "../../react/react_utils";
|
||||
import FNote from "../../../entities/fnote";
|
||||
@ -14,16 +13,8 @@ import "./index.css";
|
||||
import useRowTableEditing from "./row_editing";
|
||||
import useColTableEditing from "./col_editing";
|
||||
import AttributeDetailWidget from "../../attribute_widgets/attribute_detail";
|
||||
import attributes from "../../../services/attributes";
|
||||
import { RefObject } from "preact";
|
||||
import SpacedUpdate from "../../../services/spaced_update";
|
||||
import froca from "../../../services/froca";
|
||||
|
||||
interface TableConfig {
|
||||
tableData: {
|
||||
columns?: ColumnDefinition[];
|
||||
};
|
||||
}
|
||||
import useData, { TableConfig } from "./data";
|
||||
|
||||
export default function TableView({ note, noteIds, notePath, viewConfig, saveConfig }: ViewModeProps<TableConfig>) {
|
||||
const tabulatorRef = useRef<VanillaTabulator>(null);
|
||||
@ -118,67 +109,7 @@ function usePersistence(viewConfig: TableConfig | null | undefined, saveConfig:
|
||||
return () => {
|
||||
spacedUpdate.updateNowIfNecessary();
|
||||
};
|
||||
}, [ viewConfig, saveConfig ])
|
||||
}, [ viewConfig, saveConfig ]);
|
||||
|
||||
return persistenceProps;
|
||||
}
|
||||
|
||||
function useData(note: FNote, noteIds: string[], viewConfig: TableConfig | undefined, newAttributePosition: RefObject<number | undefined>, resetNewAttributePosition: () => void) {
|
||||
const [ maxDepth ] = useNoteLabelInt(note, "maxNestingDepth") ?? -1;
|
||||
const [ includeArchived ] = useNoteLabelBoolean(note, "includeArchived");
|
||||
|
||||
const [ columnDefs, setColumnDefs ] = useState<ColumnDefinition[]>();
|
||||
const [ rowData, setRowData ] = useState<TableData[]>();
|
||||
const [ hasChildren, setHasChildren ] = useState<boolean>();
|
||||
const [ isSorted ] = useNoteLabelBoolean(note, "sorted");
|
||||
const [ movableRows, setMovableRows ] = useState(false);
|
||||
|
||||
async function refresh() {
|
||||
const info = getAttributeDefinitionInformation(note);
|
||||
|
||||
// Ensure all note IDs are loaded.
|
||||
await froca.getNotes(noteIds);
|
||||
|
||||
const { definitions: rowData, hasSubtree: hasChildren, rowNumber } = await buildRowDefinitions(note, info, includeArchived, maxDepth);
|
||||
const columnDefs = buildColumnDefinitions({
|
||||
info,
|
||||
movableRows,
|
||||
existingColumnData: viewConfig?.tableData?.columns,
|
||||
rowNumberHint: rowNumber,
|
||||
position: newAttributePosition.current ?? undefined
|
||||
});
|
||||
setColumnDefs(columnDefs);
|
||||
setRowData(rowData);
|
||||
setHasChildren(hasChildren);
|
||||
resetNewAttributePosition();
|
||||
}
|
||||
|
||||
useEffect(() => { refresh() }, [ note, noteIds, maxDepth, movableRows ]);
|
||||
|
||||
useTriliumEvent("entitiesReloaded", ({ loadResults}) => {
|
||||
// React to column changes.
|
||||
if (loadResults.getAttributeRows().find(attr =>
|
||||
attr.type === "label" &&
|
||||
(attr.name?.startsWith("label:") || attr.name?.startsWith("relation:")) &&
|
||||
attributes.isAffecting(attr, note))) {
|
||||
refresh();
|
||||
return;
|
||||
}
|
||||
|
||||
// React to external row updates.
|
||||
if (loadResults.getBranchRows().some(branch => branch.parentNoteId === note.noteId || noteIds.includes(branch.parentNoteId ?? ""))
|
||||
|| loadResults.getNoteIds().some(noteId => noteIds.includes(noteId))
|
||||
|| loadResults.getAttributeRows().some(attr => noteIds.includes(attr.noteId!))
|
||||
|| loadResults.getAttributeRows().some(attr => attr.name === "archived" && attr.noteId && noteIds.includes(attr.noteId))) {
|
||||
refresh();
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
// Identify if movable rows.
|
||||
useEffect(() => {
|
||||
setMovableRows(!isSorted && note.type !== "search" && !hasChildren);
|
||||
}, [ isSorted, note, hasChildren ]);
|
||||
|
||||
return { columnDefs, rowData, movableRows, hasChildren };
|
||||
}
|
||||
|
||||
@ -14,9 +14,10 @@ interface TableProps<T> extends Omit<Options, "data" | "footerElement" | "index"
|
||||
events?: Partial<EventCallBackMethods>;
|
||||
index: keyof T;
|
||||
footerElement?: string | HTMLElement | JSX.Element;
|
||||
onReady?: () => void;
|
||||
}
|
||||
|
||||
export default function Tabulator<T>({ className, columns, data, modules, tabulatorRef: externalTabulatorRef, footerElement, events, index, dataTree, ...restProps }: TableProps<T>) {
|
||||
export default function Tabulator<T>({ className, columns, data, modules, tabulatorRef: externalTabulatorRef, footerElement, events, index, dataTree, onReady, ...restProps }: TableProps<T>) {
|
||||
const parentComponent = useContext(ParentComponent);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const tabulatorRef = useRef<VanillaTabulator>(null);
|
||||
@ -43,6 +44,7 @@ export default function Tabulator<T>({ className, columns, data, modules, tabula
|
||||
tabulator.on("tableBuilt", () => {
|
||||
tabulatorRef.current = tabulator;
|
||||
externalTabulatorRef.current = tabulator;
|
||||
onReady?.();
|
||||
});
|
||||
|
||||
return () => tabulator.destroy();
|
||||
|
||||
@ -5,6 +5,7 @@ import type { EventData, EventNames } from "../../components/app_context.js";
|
||||
|
||||
export default class RightPaneContainer extends FlexContainer<RightPanelWidget> {
|
||||
private rightPaneHidden: boolean;
|
||||
private firstRender: boolean;
|
||||
|
||||
constructor() {
|
||||
super("column");
|
||||
@ -14,6 +15,7 @@ export default class RightPaneContainer extends FlexContainer<RightPanelWidget>
|
||||
this.collapsible();
|
||||
|
||||
this.rightPaneHidden = false;
|
||||
this.firstRender = true;
|
||||
}
|
||||
|
||||
isEnabled() {
|
||||
@ -41,10 +43,11 @@ export default class RightPaneContainer extends FlexContainer<RightPanelWidget>
|
||||
const oldToggle = !this.isHiddenInt();
|
||||
const newToggle = this.isEnabled();
|
||||
|
||||
if (oldToggle !== newToggle) {
|
||||
if (oldToggle !== newToggle || this.firstRender) {
|
||||
this.toggleInt(newToggle);
|
||||
|
||||
splitService.setupRightPaneResizer();
|
||||
this.firstRender = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
64
apps/client/src/widgets/dialogs/PopupEditor.css
Normal file
64
apps/client/src/widgets/dialogs/PopupEditor.css
Normal file
@ -0,0 +1,64 @@
|
||||
/** Reduce the z-index of modals so that ckeditor popups are properly shown on top of it. */
|
||||
body.popup-editor-open > .modal-backdrop { z-index: 998; }
|
||||
body.popup-editor-open .popup-editor-dialog { z-index: 999; }
|
||||
body.popup-editor-open .ck-clipboard-drop-target-line { z-index: 1000; }
|
||||
|
||||
body.desktop .modal.popup-editor-dialog .modal-dialog {
|
||||
max-width: 75vw;
|
||||
}
|
||||
|
||||
.modal.popup-editor-dialog .modal-header .modal-title {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.modal.popup-editor-dialog .modal-header .title-row {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal.popup-editor-dialog .modal-header .title-row > * {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.modal.popup-editor-dialog .modal-body {
|
||||
padding: 0;
|
||||
height: 75vh;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.modal.popup-editor-dialog .note-detail-editable-text {
|
||||
padding: 0 1em;
|
||||
}
|
||||
|
||||
.modal.popup-editor-dialog .title-row,
|
||||
.modal.popup-editor-dialog .modal-title,
|
||||
.modal.popup-editor-dialog .note-icon-widget {
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.modal.popup-editor-dialog .note-icon-widget {
|
||||
width: 32px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.modal.popup-editor-dialog .note-icon-widget button.note-icon,
|
||||
.modal.popup-editor-dialog .note-title-widget input.note-title {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.modal.popup-editor-dialog .classic-toolbar-widget {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
inset-inline-start: 0;
|
||||
inset-inline-end: 0;
|
||||
background: var(--modal-background-color);
|
||||
z-index: 998;
|
||||
}
|
||||
|
||||
.modal.popup-editor-dialog .note-detail-file {
|
||||
padding: 0;
|
||||
}
|
||||
85
apps/client/src/widgets/dialogs/PopupEditor.tsx
Normal file
85
apps/client/src/widgets/dialogs/PopupEditor.tsx
Normal file
@ -0,0 +1,85 @@
|
||||
import { useContext, useEffect, useRef, useState } from "preact/hooks";
|
||||
import Modal from "../react/Modal";
|
||||
import "./PopupEditor.css";
|
||||
import { useNoteContext, useTriliumEvent } from "../react/hooks";
|
||||
import NoteTitleWidget from "../note_title";
|
||||
import NoteIcon from "../note_icon";
|
||||
import NoteContext from "../../components/note_context";
|
||||
import { NoteContextContext, ParentComponent } from "../react/react_utils";
|
||||
import NoteDetail from "../NoteDetail";
|
||||
import { ComponentChildren } from "preact";
|
||||
import NoteList from "../collections/NoteList";
|
||||
import StandaloneRibbonAdapter from "../ribbon/components/StandaloneRibbonAdapter";
|
||||
import FormattingToolbar from "../ribbon/FormattingToolbar";
|
||||
|
||||
export default function PopupEditor() {
|
||||
const [ shown, setShown ] = useState(false);
|
||||
const parentComponent = useContext(ParentComponent);
|
||||
const [ noteContext, setNoteContext ] = useState(new NoteContext("_popup-editor"));
|
||||
|
||||
useTriliumEvent("openInPopup", async ({ noteIdOrPath }) => {
|
||||
const noteContext = new NoteContext("_popup-editor");
|
||||
await noteContext.setNote(noteIdOrPath, {
|
||||
viewScope: {
|
||||
readOnlyTemporarilyDisabled: true
|
||||
}
|
||||
});
|
||||
|
||||
setNoteContext(noteContext);
|
||||
setShown(true);
|
||||
});
|
||||
|
||||
// Add a global class to be able to handle issues with z-index due to rendering in a popup.
|
||||
useEffect(() => {
|
||||
document.body.classList.toggle("popup-editor-open", shown);
|
||||
}, [shown]);
|
||||
|
||||
return (
|
||||
<NoteContextContext.Provider value={noteContext}>
|
||||
<DialogWrapper>
|
||||
<Modal
|
||||
title={<TitleRow />}
|
||||
className="popup-editor-dialog"
|
||||
size="lg"
|
||||
show={shown}
|
||||
onShown={() => {
|
||||
parentComponent?.handleEvent("focusOnDetail", { ntxId: noteContext.ntxId });
|
||||
}}
|
||||
onHidden={() => setShown(false)}
|
||||
>
|
||||
<StandaloneRibbonAdapter component={FormattingToolbar} />
|
||||
<NoteDetail />
|
||||
<NoteList media="screen" displayOnlyCollections />
|
||||
</Modal>
|
||||
</DialogWrapper>
|
||||
</NoteContextContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function DialogWrapper({ children }: { children: ComponentChildren }) {
|
||||
const { note } = useNoteContext();
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
const [ hasTint, setHasTint ] = useState(false);
|
||||
|
||||
// Apply the tinted-dialog class only if the custom color CSS class specifies a hue
|
||||
useEffect(() => {
|
||||
if (!wrapperRef.current) return;
|
||||
const customHue = getComputedStyle(wrapperRef.current).getPropertyValue("--custom-color-hue");
|
||||
setHasTint(!!customHue);
|
||||
}, [ note ]);
|
||||
|
||||
return (
|
||||
<div ref={wrapperRef} class={`quick-edit-dialog-wrapper ${note?.getColorClass() ?? ""} ${hasTint ? "tinted-quick-edit-dialog" : ""}`}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function TitleRow() {
|
||||
return (
|
||||
<div className="title-row">
|
||||
<NoteIcon />
|
||||
<NoteTitleWidget />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -1,187 +0,0 @@
|
||||
import type { EventNames, EventData } from "../../components/app_context.js";
|
||||
import NoteContext from "../../components/note_context.js";
|
||||
import { openDialog } from "../../services/dialog.js";
|
||||
import BasicWidget, { ReactWrappedWidget } from "../basic_widget.js";
|
||||
import Container from "../containers/container.js";
|
||||
|
||||
const TPL = /*html*/`\
|
||||
<div class="popup-editor-dialog modal fade mx-auto" tabindex="-1" role="dialog">
|
||||
<style>
|
||||
/** Reduce the z-index of modals so that ckeditor popups are properly shown on top of it. */
|
||||
body.popup-editor-open > .modal-backdrop { z-index: 998; }
|
||||
body.popup-editor-open .popup-editor-dialog { z-index: 999; }
|
||||
body.popup-editor-open .ck-clipboard-drop-target-line { z-index: 1000; }
|
||||
|
||||
body.desktop .modal.popup-editor-dialog .modal-dialog {
|
||||
max-width: 75vw;
|
||||
}
|
||||
|
||||
.modal.popup-editor-dialog .modal-header .modal-title {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.modal.popup-editor-dialog .modal-body {
|
||||
padding: 0;
|
||||
height: 75vh;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.modal.popup-editor-dialog .note-detail-editable-text {
|
||||
padding: 0 1em;
|
||||
}
|
||||
|
||||
.modal.popup-editor-dialog .title-row,
|
||||
.modal.popup-editor-dialog .modal-title,
|
||||
.modal.popup-editor-dialog .note-icon-widget {
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.modal.popup-editor-dialog .note-icon-widget {
|
||||
width: 32px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.modal.popup-editor-dialog .note-icon-widget button.note-icon,
|
||||
.modal.popup-editor-dialog .note-title-widget input.note-title {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.modal.popup-editor-dialog .classic-toolbar-widget {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
inset-inline-start: 0;
|
||||
inset-inline-end: 0;
|
||||
background: var(--modal-background-color);
|
||||
z-index: 998;
|
||||
}
|
||||
|
||||
.modal.popup-editor-dialog .note-detail-file {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="quick-edit-dialog-wrapper">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<div class="modal-title">
|
||||
<!-- This is where the first child will be injected -->
|
||||
</div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<!-- This is where all but the first child will be injected. -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
export default class PopupEditorDialog extends Container<BasicWidget> {
|
||||
|
||||
private noteContext: NoteContext;
|
||||
private $modalHeader!: JQuery<HTMLElement>;
|
||||
private $modalBody!: JQuery<HTMLElement>;
|
||||
private $wrapper!: JQuery<HTMLDivElement>;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.noteContext = new NoteContext("_popup-editor");
|
||||
}
|
||||
|
||||
doRender() {
|
||||
// This will populate this.$widget with the content of the children.
|
||||
super.doRender();
|
||||
|
||||
// Now we wrap it in the modal.
|
||||
const $newWidget = $(TPL);
|
||||
this.$modalHeader = $newWidget.find(".modal-title");
|
||||
this.$modalBody = $newWidget.find(".modal-body");
|
||||
this.$wrapper = $newWidget.find(".quick-edit-dialog-wrapper");
|
||||
|
||||
const children = this.$widget.children();
|
||||
this.$modalHeader.append(children[0]);
|
||||
this.$modalBody.append(children.slice(1));
|
||||
this.$widget = $newWidget;
|
||||
this.setVisibility(false);
|
||||
}
|
||||
|
||||
async openInPopupEvent({ noteIdOrPath }: EventData<"openInPopup">) {
|
||||
const $dialog = await openDialog(this.$widget, false, {
|
||||
focus: false
|
||||
});
|
||||
|
||||
await this.noteContext.setNote(noteIdOrPath, {
|
||||
viewScope: {
|
||||
readOnlyTemporarilyDisabled: true
|
||||
}
|
||||
});
|
||||
|
||||
const colorClass = this.noteContext.note?.getColorClass();
|
||||
const wrapperElement = this.$wrapper.get(0)!;
|
||||
|
||||
if (colorClass) {
|
||||
wrapperElement.className = "quick-edit-dialog-wrapper " + colorClass;
|
||||
} else {
|
||||
wrapperElement.className = "quick-edit-dialog-wrapper";
|
||||
}
|
||||
|
||||
const customHue = getComputedStyle(wrapperElement).getPropertyValue("--custom-color-hue");
|
||||
if (customHue) {
|
||||
/* Apply the tinted-dialog class only if the custom color CSS class specifies a hue */
|
||||
wrapperElement.classList.add("tinted-quick-edit-dialog");
|
||||
}
|
||||
|
||||
const activeEl = document.activeElement;
|
||||
if (activeEl && "blur" in activeEl) {
|
||||
(activeEl as HTMLElement).blur();
|
||||
}
|
||||
|
||||
$dialog.on("shown.bs.modal", async () => {
|
||||
await this.handleEventInChildren("activeContextChanged", { noteContext: this.noteContext });
|
||||
this.setVisibility(true);
|
||||
await this.handleEventInChildren("focusOnDetail", { ntxId: this.noteContext.ntxId });
|
||||
});
|
||||
$dialog.on("hidden.bs.modal", () => {
|
||||
const $typeWidgetEl = $dialog.find(".note-detail-printable");
|
||||
if ($typeWidgetEl.length) {
|
||||
const typeWidget = glob.getComponentByEl($typeWidgetEl[0]) as ReactWrappedWidget;
|
||||
typeWidget.cleanup();
|
||||
}
|
||||
|
||||
this.setVisibility(false);
|
||||
});
|
||||
}
|
||||
|
||||
setVisibility(visible: boolean) {
|
||||
const $bodyItems = this.$modalBody.find("> div");
|
||||
if (visible) {
|
||||
$bodyItems.fadeIn();
|
||||
this.$modalHeader.children().show();
|
||||
document.body.classList.add("popup-editor-open");
|
||||
|
||||
} else {
|
||||
$bodyItems.hide();
|
||||
this.$modalHeader.children().hide();
|
||||
document.body.classList.remove("popup-editor-open");
|
||||
}
|
||||
}
|
||||
|
||||
handleEventInChildren<T extends EventNames>(name: T, data: EventData<T>): Promise<unknown[] | unknown> | null {
|
||||
// Avoid events related to the current tab interfere with our popup.
|
||||
if (["noteSwitched", "noteSwitchedAndActivated", "exportAsPdf", "printActiveNote"].includes(name)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// Avoid not showing recent notes when creating a new empty tab.
|
||||
if ("noteContext" in data && data.noteContext.ntxId !== "_popup-editor") {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return super.handleEventInChildren(name, data);
|
||||
}
|
||||
|
||||
}
|
||||
@ -2,7 +2,7 @@ import { CSSProperties } from "preact/compat";
|
||||
import { DragData } from "../note_tree";
|
||||
import { FilterLabelsByType, KeyboardActionNames, OptionNames, RelationNames } from "@triliumnext/commons";
|
||||
import { MutableRef, useCallback, useContext, useDebugValue, useEffect, useLayoutEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||
import { ParentComponent, refToJQuerySelector } from "./react_utils";
|
||||
import { NoteContextContext, ParentComponent, refToJQuerySelector } from "./react_utils";
|
||||
import { RefObject, VNode } from "preact";
|
||||
import { Tooltip } from "bootstrap";
|
||||
import { ViewMode, ViewScope } from "../../services/link";
|
||||
@ -257,18 +257,29 @@ export function useUniqueName(prefix?: string) {
|
||||
}
|
||||
|
||||
export function useNoteContext() {
|
||||
const [ noteContext, setNoteContext ] = useState<NoteContext>();
|
||||
const noteContextContext = useContext(NoteContextContext);
|
||||
const [ noteContext, setNoteContext ] = useState<NoteContext | undefined>(noteContextContext ?? undefined);
|
||||
const [ notePath, setNotePath ] = useState<string | null | undefined>();
|
||||
const [ note, setNote ] = useState<FNote | null | undefined>();
|
||||
const [ , setViewScope ] = useState<ViewScope>();
|
||||
const [ isReadOnlyTemporarilyDisabled, setIsReadOnlyTemporarilyDisabled ] = useState<boolean | null | undefined>(noteContext?.viewScope?.isReadOnly);
|
||||
const [ refreshCounter, setRefreshCounter ] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (!noteContextContext) return;
|
||||
setNoteContext(noteContextContext);
|
||||
setNote(noteContextContext.note);
|
||||
setNotePath(noteContextContext.notePath);
|
||||
setViewScope(noteContextContext.viewScope);
|
||||
setIsReadOnlyTemporarilyDisabled(noteContextContext?.viewScope?.readOnlyTemporarilyDisabled);
|
||||
}, [ noteContextContext ]);
|
||||
|
||||
useEffect(() => {
|
||||
setNote(noteContext?.note);
|
||||
}, [ notePath ]);
|
||||
|
||||
useTriliumEvents([ "setNoteContext", "activeContextChanged", "noteSwitchedAndActivated", "noteSwitched" ], ({ noteContext }) => {
|
||||
if (noteContextContext) return;
|
||||
setNoteContext(noteContext);
|
||||
setNotePath(noteContext.notePath);
|
||||
setViewScope(noteContext.viewScope);
|
||||
@ -282,6 +293,7 @@ export function useNoteContext() {
|
||||
}
|
||||
});
|
||||
useTriliumEvent("readOnlyTemporarilyDisabled", ({ noteContext: eventNoteContext }) => {
|
||||
if (noteContextContext) return;
|
||||
if (eventNoteContext.ntxId === noteContext?.ntxId) {
|
||||
setIsReadOnlyTemporarilyDisabled(eventNoteContext?.viewScope?.readOnlyTemporarilyDisabled);
|
||||
}
|
||||
@ -760,18 +772,18 @@ export function useResizeObserver(ref: RefObject<HTMLElement>, callback: () => v
|
||||
}, [ callback, ref ]);
|
||||
}
|
||||
|
||||
export function useKeyboardShortcuts(scope: "code-detail" | "text-detail", containerRef: RefObject<HTMLElement>, parentComponent: Component | undefined) {
|
||||
export function useKeyboardShortcuts(scope: "code-detail" | "text-detail", containerRef: RefObject<HTMLElement>, parentComponent: Component | undefined, ntxId: string | null | undefined) {
|
||||
useEffect(() => {
|
||||
if (!parentComponent) return;
|
||||
const $container = refToJQuerySelector(containerRef);
|
||||
const bindingPromise = keyboard_actions.setupActionsForElement(scope, $container, parentComponent);
|
||||
const bindingPromise = keyboard_actions.setupActionsForElement(scope, $container, parentComponent, ntxId);
|
||||
return async () => {
|
||||
const bindings = await bindingPromise;
|
||||
for (const binding of bindings) {
|
||||
removeIndividualBinding(binding);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
}, [ scope, containerRef, parentComponent, ntxId ]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
import { ComponentChild, createContext, render, type JSX, type RefObject } from "preact";
|
||||
import Component from "../../components/component";
|
||||
import NoteContext from "../../components/note_context";
|
||||
|
||||
export const ParentComponent = createContext<Component | null>(null);
|
||||
|
||||
export const NoteContextContext = createContext<NoteContext | null>(null);
|
||||
|
||||
/**
|
||||
* Takes in a React ref and returns a corresponding JQuery selector.
|
||||
*
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { useTriliumOption } from "../react/hooks";
|
||||
import { useRef } from "preact/hooks";
|
||||
import { useTriliumEvent, useTriliumOption } from "../react/hooks";
|
||||
import { TabContext } from "./ribbon-interface";
|
||||
|
||||
/**
|
||||
@ -9,11 +10,28 @@ import { TabContext } from "./ribbon-interface";
|
||||
* The ribbon item is active by default for text notes, as long as they are not in read-only mode.
|
||||
*
|
||||
* ! The toolbar is not only used in the ribbon, but also in the quick edit feature.
|
||||
* * The mobile toolbar is handled separately (see `MobileEditorToolbar`).
|
||||
*/
|
||||
export default function FormattingToolbar({ hidden }: TabContext) {
|
||||
export default function FormattingToolbar({ hidden, ntxId }: TabContext) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [ textNoteEditorType ] = useTriliumOption("textNoteEditorType");
|
||||
|
||||
// Attach the toolbar from the CKEditor.
|
||||
useTriliumEvent("textEditorRefreshed", ({ ntxId: eventNtxId, editor }) => {
|
||||
if (eventNtxId !== ntxId || !containerRef.current) return;
|
||||
const toolbar = editor.ui.view.toolbar?.element;
|
||||
|
||||
if (toolbar) {
|
||||
containerRef.current.replaceChildren(toolbar);
|
||||
} else {
|
||||
containerRef.current.replaceChildren();
|
||||
}
|
||||
});
|
||||
|
||||
return (textNoteEditorType === "ckeditor-classic" &&
|
||||
<div className={`classic-toolbar-widget ${hidden ? "hidden-ext" : ""}`} />
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={`classic-toolbar-widget ${hidden ? "hidden-ext" : ""}`}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
@ -49,7 +49,7 @@ function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: Not
|
||||
const canBeConvertedToAttachment = note?.isEligibleForConversionToAttachment();
|
||||
const isSearchable = ["text", "code", "book", "mindMap", "doc"].includes(note.type);
|
||||
const isInOptionsOrHelp = note?.noteId.startsWith("_options") || note?.noteId.startsWith("_help");
|
||||
const isPrintable = ["text", "code"].includes(note.type) || (note.type === "book" && ["presentation", "list"].includes(note.getLabelValue("viewType") ?? ""));
|
||||
const isPrintable = ["text", "code"].includes(note.type) || (note.type === "book" && ["presentation", "list", "table"].includes(note.getLabelValue("viewType") ?? ""));
|
||||
const isElectron = getIsElectron();
|
||||
const isMac = getIsMac();
|
||||
const hasSource = ["text", "code", "relationMap", "mermaid", "canvas", "mindMap", "aiChat"].includes(note.type);
|
||||
|
||||
33
apps/client/src/widgets/sidebar/RightPanelWidget.tsx
Normal file
33
apps/client/src/widgets/sidebar/RightPanelWidget.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import { useContext, useRef } from "preact/hooks";
|
||||
import { ParentComponent } from "../react/react_utils";
|
||||
import { ComponentChildren } from "preact";
|
||||
|
||||
interface RightPanelWidgetProps {
|
||||
title: string;
|
||||
children: ComponentChildren;
|
||||
buttons?: ComponentChildren;
|
||||
}
|
||||
|
||||
export default function RightPanelWidget({ title, buttons, children }: RightPanelWidgetProps) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const parentComponent = useContext(ParentComponent);
|
||||
|
||||
if (parentComponent) {
|
||||
parentComponent.initialized = Promise.resolve();
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={containerRef} class="card widget" style={{contain: "none"}}>
|
||||
<div class="card-header">
|
||||
<div class="card-header-title">{title}</div>
|
||||
<div class="card-header-buttons">{buttons}</div>
|
||||
</div>
|
||||
|
||||
<div id={parentComponent?.componentId} class="body-wrapper">
|
||||
<div class="card-body">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -199,6 +199,7 @@ export default class TocWidget extends RightPanelWidget {
|
||||
* For document note types, we obtain the content directly from the DOM since it allows us to obtain processed data without
|
||||
* requesting data twice. However, when immediately navigating to a new note the new document is not yet attached to the hierarchy,
|
||||
* resulting in an empty TOC. The fix is to simply wait for it to pop up.
|
||||
* TODO: Use a better method that is not prone to unnecessary delays and race conditions.
|
||||
*/
|
||||
setTimeout(async () => {
|
||||
const $contentEl = await this.noteContext?.getContentElement();
|
||||
@ -209,7 +210,7 @@ export default class TocWidget extends RightPanelWidget {
|
||||
} else {
|
||||
console.warn("Unable to get content element for doctype");
|
||||
}
|
||||
}, 10);
|
||||
}, 250);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -100,7 +100,7 @@ export function EditableCode({ note, ntxId, noteContext, debounceUpdate, parentC
|
||||
}
|
||||
});
|
||||
|
||||
useKeyboardShortcuts("code-detail", containerRef, parentComponent);
|
||||
useKeyboardShortcuts("code-detail", containerRef, parentComponent, ntxId);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@ -163,7 +163,12 @@ function useResizer(containerRef: RefObject<HTMLDivElement>, noteId: string, svg
|
||||
pan: zoomInstance.getPan(),
|
||||
zoom: zoomInstance.getZoom()
|
||||
}
|
||||
zoomInstance.destroy();
|
||||
try {
|
||||
zoomInstance.destroy();
|
||||
} catch (e) {
|
||||
// Sometimes crashes with "Matrix is not invertible" which can cause havoc such as breaking the popup editor from ever showing up again.
|
||||
console.warn(e);
|
||||
}
|
||||
};
|
||||
}, [ svg ]);
|
||||
|
||||
|
||||
@ -20,7 +20,6 @@ export interface CKEditorApi {
|
||||
}
|
||||
|
||||
interface CKEditorWithWatchdogProps extends Pick<HTMLProps<HTMLDivElement>, "className" | "tabIndex"> {
|
||||
content: string | undefined;
|
||||
contentLanguage: string | null | undefined;
|
||||
isClassicEditor?: boolean;
|
||||
watchdogRef: RefObject<EditorWatchdog>;
|
||||
@ -35,14 +34,14 @@ interface CKEditorWithWatchdogProps extends Pick<HTMLProps<HTMLDivElement>, "cla
|
||||
containerRef?: RefObject<HTMLDivElement>;
|
||||
}
|
||||
|
||||
export default function CKEditorWithWatchdog({ containerRef: externalContainerRef, content, contentLanguage, className, tabIndex, isClassicEditor, watchdogRef: externalWatchdogRef, watchdogConfig, onNotificationWarning, onWatchdogStateChange, onChange, onEditorInitialized, editorApi, templates }: CKEditorWithWatchdogProps) {
|
||||
export default function CKEditorWithWatchdog({ containerRef: externalContainerRef, contentLanguage, className, tabIndex, isClassicEditor, watchdogRef: externalWatchdogRef, watchdogConfig, onNotificationWarning, onWatchdogStateChange, onChange, onEditorInitialized, editorApi, templates }: CKEditorWithWatchdogProps) {
|
||||
const containerRef = useSyncedRef<HTMLDivElement>(externalContainerRef, null);
|
||||
const watchdogRef = useRef<EditorWatchdog>(null);
|
||||
const [ uiLanguage ] = useTriliumOption("locale");
|
||||
const [ editor, setEditor ] = useState<CKTextEditor>();
|
||||
const { parentComponent } = useNoteContext();
|
||||
const { parentComponent, ntxId } = useNoteContext();
|
||||
|
||||
useKeyboardShortcuts("text-detail", containerRef, parentComponent);
|
||||
useKeyboardShortcuts("text-detail", containerRef, parentComponent, ntxId);
|
||||
|
||||
useImperativeHandle(editorApi, () => ({
|
||||
hasSelection() {
|
||||
@ -185,9 +184,6 @@ export default function CKEditorWithWatchdog({ containerRef: externalContainerRe
|
||||
return () => watchdog.destroy();
|
||||
}, [ contentLanguage, templates, uiLanguage ]);
|
||||
|
||||
// React to content changes.
|
||||
useEffect(() => editor?.setData(content ?? ""), [ editor, content ]);
|
||||
|
||||
// React to notification warning callback.
|
||||
useEffect(() => {
|
||||
if (!onNotificationWarning || !editor) return;
|
||||
|
||||
@ -2,12 +2,11 @@ import { useEffect, useRef, useState } from "preact/hooks";
|
||||
import dialog from "../../../services/dialog";
|
||||
import toast from "../../../services/toast";
|
||||
import utils, { hasTouchBar, isMobile } from "../../../services/utils";
|
||||
import { useEditorSpacedUpdate, useKeyboardShortcuts, useLegacyImperativeHandlers, useNoteLabel, useTriliumEvent, useTriliumOption, useTriliumOptionBool } from "../../react/hooks";
|
||||
import { useEditorSpacedUpdate, useLegacyImperativeHandlers, useNoteLabel, useTriliumEvent, useTriliumOption, useTriliumOptionBool } from "../../react/hooks";
|
||||
import { TypeWidgetProps } from "../type_widget";
|
||||
import CKEditorWithWatchdog, { CKEditorApi } from "./CKEditorWithWatchdog";
|
||||
import "./EditableText.css";
|
||||
import { CKTextEditor, ClassicEditor, EditorWatchdog, TemplateDefinition } from "@triliumnext/ckeditor5";
|
||||
import Component from "../../../components/component";
|
||||
import { CKTextEditor, EditorWatchdog, TemplateDefinition } from "@triliumnext/ckeditor5";
|
||||
import options from "../../../services/options";
|
||||
import { loadIncludedNote, refreshIncludedNote, setupImageOpening } from "./utils";
|
||||
import getTemplates, { updateTemplateCache } from "./snippets.js";
|
||||
@ -27,7 +26,7 @@ import { deferred } from "@triliumnext/commons";
|
||||
*/
|
||||
export default function EditableText({ note, parentComponent, ntxId, noteContext }: TypeWidgetProps) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [ content, setContent ] = useState<string>();
|
||||
const contentRef = useRef<string>("");
|
||||
const watchdogRef = useRef<EditorWatchdog>(null);
|
||||
const editorApiRef = useRef<CKEditorApi>(null);
|
||||
const refreshTouchBarRef = useRef<() => void>(null);
|
||||
@ -55,7 +54,8 @@ export default function EditableText({ note, parentComponent, ntxId, noteContext
|
||||
};
|
||||
},
|
||||
onContentChange(newContent) {
|
||||
setContent(newContent);
|
||||
contentRef.current = newContent;
|
||||
watchdogRef.current?.editor?.setData(newContent);
|
||||
}
|
||||
});
|
||||
const templates = useTemplates();
|
||||
@ -215,7 +215,6 @@ export default function EditableText({ note, parentComponent, ntxId, noteContext
|
||||
containerRef={containerRef}
|
||||
className={`note-detail-editable-text-editor use-tn-links ${codeBlockWordWrap ? "word-wrap" : ""}`}
|
||||
tabIndex={300}
|
||||
content={content}
|
||||
contentLanguage={language}
|
||||
isClassicEditor={isClassicEditor}
|
||||
editorApi={editorApiRef}
|
||||
@ -233,12 +232,6 @@ export default function EditableText({ note, parentComponent, ntxId, noteContext
|
||||
onWatchdogStateChange={onWatchdogStateChange}
|
||||
onChange={() => spacedUpdate.scheduleUpdate()}
|
||||
onEditorInitialized={(editor) => {
|
||||
console.log("Editor has been initialized!", parentComponent, editor);
|
||||
|
||||
if (isClassicEditor) {
|
||||
setupClassicEditor(editor, parentComponent);
|
||||
}
|
||||
|
||||
if (hasTouchBar) {
|
||||
const handler = () => refreshTouchBarRef.current?.();
|
||||
for (const event of [ "bold", "italic", "underline", "paragraph", "heading" ]) {
|
||||
@ -251,6 +244,8 @@ export default function EditableText({ note, parentComponent, ntxId, noteContext
|
||||
}
|
||||
|
||||
initialized.current.resolve();
|
||||
editor.setData(contentRef.current ?? "");
|
||||
parentComponent?.triggerEvent("textEditorRefreshed", { ntxId, editor });
|
||||
}}
|
||||
/>}
|
||||
|
||||
@ -302,55 +297,6 @@ function onNotificationWarning(data, evt) {
|
||||
evt.stop();
|
||||
}
|
||||
|
||||
function setupClassicEditor(editor: CKTextEditor, parentComponent: Component | undefined) {
|
||||
if (!parentComponent) return;
|
||||
const $classicToolbarWidget = findClassicToolbar(parentComponent);
|
||||
|
||||
$classicToolbarWidget.empty();
|
||||
if ($classicToolbarWidget.length) {
|
||||
const toolbarView = (editor as ClassicEditor).ui.view.toolbar;
|
||||
if (toolbarView.element) {
|
||||
$classicToolbarWidget[0].appendChild(toolbarView.element);
|
||||
}
|
||||
}
|
||||
|
||||
if (utils.isMobile()) {
|
||||
$classicToolbarWidget.addClass("visible");
|
||||
|
||||
// Reposition all dropdowns to point upwards instead of downwards.
|
||||
// See https://ckeditor.com/docs/ckeditor5/latest/examples/framework/bottom-toolbar-editor.html for more info.
|
||||
const toolbarView = (editor as ClassicEditor).ui.view.toolbar;
|
||||
for (const item of toolbarView.items) {
|
||||
if (!("panelView" in item)) continue;
|
||||
|
||||
item.on("change:isOpen", () => {
|
||||
if (!("isOpen" in item) || !item.isOpen) return;
|
||||
|
||||
// @ts-ignore
|
||||
item.panelView.position = item.panelView.position.replace("s", "n");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function findClassicToolbar(parentComponent: Component): JQuery<HTMLElement> {
|
||||
const $widget = $(parentComponent.$widget);
|
||||
|
||||
if (!utils.isMobile()) {
|
||||
const $parentSplit = $widget.parents(".note-split.type-text");
|
||||
|
||||
if ($parentSplit.length) {
|
||||
// The editor is in a normal tab.
|
||||
return $parentSplit.find("> .ribbon-container .classic-toolbar-widget");
|
||||
} else {
|
||||
// The editor is in a popup.
|
||||
return $widget.closest(".modal-body").find(".classic-toolbar-widget");
|
||||
}
|
||||
} else {
|
||||
return $("body").find(".classic-toolbar-widget");
|
||||
}
|
||||
}
|
||||
|
||||
function EditableTextTouchBar({ watchdogRef, refreshTouchBarRef }: { watchdogRef: RefObject<EditorWatchdog | null>, refreshTouchBarRef: RefObject<() => void> }) {
|
||||
const [ headingSelectedIndex, setHeadingSelectedIndex ] = useState<number>();
|
||||
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import { MutableRef, useCallback, useEffect, useRef, useState } from "preact/hooks";
|
||||
import { useNoteContext } from "../../react/hooks";
|
||||
import { useNoteContext, useTriliumEvent } from "../../react/hooks";
|
||||
import "./mobile_editor_toolbar.css";
|
||||
import { isIOS } from "../../../services/utils";
|
||||
import { CKTextEditor, ClassicEditor } from "@triliumnext/ckeditor5";
|
||||
|
||||
/**
|
||||
* Handles the editing toolbar for CKEditor in mobile mode. The toolbar acts as a floating bar, with two different mechanism:
|
||||
@ -10,12 +11,12 @@ import { isIOS } from "../../../services/utils";
|
||||
* - On Android, the viewport change makes the keyboard resize the content area, all we have to do is to hide the tab bar and global menu (handled in the global style).
|
||||
*/
|
||||
export default function MobileEditorToolbar() {
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
const { note, noteContext } = useNoteContext();
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const { note, noteContext, ntxId } = useNoteContext();
|
||||
const [ shouldDisplay, setShouldDisplay ] = useState(false);
|
||||
const [ dropdownActive, setDropdownActive ] = useState(false);
|
||||
|
||||
usePositioningOniOS(wrapperRef);
|
||||
usePositioningOniOS(containerRef);
|
||||
|
||||
useEffect(() => {
|
||||
noteContext?.isReadOnly().then(isReadOnly => {
|
||||
@ -23,15 +24,28 @@ export default function MobileEditorToolbar() {
|
||||
});
|
||||
}, [ note ]);
|
||||
|
||||
// Attach the toolbar from the CKEditor.
|
||||
useTriliumEvent("textEditorRefreshed", ({ ntxId: eventNtxId, editor }) => {
|
||||
if (eventNtxId !== ntxId || !containerRef.current) return;
|
||||
const toolbar = editor.ui.view.toolbar?.element;
|
||||
|
||||
repositionDropdowns(editor);
|
||||
if (toolbar) {
|
||||
containerRef.current.replaceChildren(toolbar);
|
||||
} else {
|
||||
containerRef.current.replaceChildren();
|
||||
}
|
||||
});
|
||||
|
||||
// Observe when a dropdown is expanded to apply a style that allows the dropdown to be visible, since we can't have the element both visible and the toolbar scrollable.
|
||||
useEffect(() => {
|
||||
if (!wrapperRef.current) return;
|
||||
if (!containerRef.current) return;
|
||||
|
||||
const observer = new MutationObserver(e => {
|
||||
setDropdownActive(e.map((e) => (e.target as any).ariaExpanded === "true").reduce((acc, e) => acc && e));
|
||||
});
|
||||
|
||||
observer.observe(wrapperRef.current, {
|
||||
observer.observe(containerRef.current, {
|
||||
attributeFilter: ["aria-expanded"],
|
||||
subtree: true
|
||||
});
|
||||
@ -41,7 +55,7 @@ export default function MobileEditorToolbar() {
|
||||
|
||||
return (
|
||||
<div className={`classic-toolbar-outer-container ${!shouldDisplay ? "hidden-ext" : "visible"} ${isIOS() ? "ios" : ""}`}>
|
||||
<div ref={wrapperRef} className={`classic-toolbar-widget ${dropdownActive ? "dropdown-active" : ""}`}></div>
|
||||
<div ref={containerRef} className={`classic-toolbar-widget ${dropdownActive ? "dropdown-active" : ""}`}></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -50,7 +64,7 @@ function usePositioningOniOS(wrapperRef: MutableRef<HTMLDivElement | null>) {
|
||||
const adjustPosition = useCallback(() => {
|
||||
if (!wrapperRef.current) return;
|
||||
let bottom = window.innerHeight - (window.visualViewport?.height || 0);
|
||||
wrapperRef.current.style.bottom = `${bottom}px`;
|
||||
wrapperRef.current.style.bottom = `${bottom}px`;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@ -65,3 +79,22 @@ function usePositioningOniOS(wrapperRef: MutableRef<HTMLDivElement | null>) {
|
||||
};
|
||||
}, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reposition all dropdowns to point upwards instead of downwards.
|
||||
* See https://ckeditor.com/docs/ckeditor5/latest/examples/framework/bottom-toolbar-editor.html for more info.
|
||||
* @param editor
|
||||
*/
|
||||
function repositionDropdowns(editor: CKTextEditor) {
|
||||
const toolbarView = (editor as ClassicEditor).ui.view.toolbar;
|
||||
for (const item of toolbarView.items) {
|
||||
if (!("panelView" in item)) continue;
|
||||
|
||||
item.on("change:isOpen", () => {
|
||||
if (!("isOpen" in item) || !item.isOpen) return;
|
||||
|
||||
// @ts-ignore
|
||||
item.panelView.position = item.panelView.position.replace("s", "n");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@electron/remote": "2.1.3",
|
||||
"better-sqlite3": "12.4.1",
|
||||
"better-sqlite3": "12.4.6",
|
||||
"electron-debug": "4.1.0",
|
||||
"electron-dl": "4.0.0",
|
||||
"electron-squirrel-startup": "1.0.1",
|
||||
|
||||
@ -4,8 +4,8 @@
|
||||
"description": "Standalone tool to dump contents of Trilium document.db file into a directory tree of notes",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"better-sqlite3": "12.4.1",
|
||||
"mime-types": "3.0.1",
|
||||
"better-sqlite3": "12.4.6",
|
||||
"mime-types": "3.0.2",
|
||||
"sanitize-filename": "1.6.3",
|
||||
"tsx": "4.20.6",
|
||||
"yargs": "18.0.0"
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
"description": "Desktop version of Trilium which imports the demo database (presented to new users at start-up) or the user guide and other documentation and saves the modifications for committing.",
|
||||
"dependencies": {
|
||||
"archiver": "7.0.1",
|
||||
"better-sqlite3": "12.4.1"
|
||||
"better-sqlite3": "12.4.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@triliumnext/client": "workspace:*",
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"better-sqlite3": "12.4.1"
|
||||
"better-sqlite3": "12.4.6"
|
||||
}
|
||||
}
|
||||
@ -25,12 +25,12 @@
|
||||
"docker-start-rootless-alpine": "pnpm docker-build-rootless-alpine && docker run -p 8081:8080 triliumnext-rootless-alpine"
|
||||
},
|
||||
"dependencies": {
|
||||
"better-sqlite3": "12.4.1",
|
||||
"better-sqlite3": "12.4.6",
|
||||
"html-to-text": "9.0.5",
|
||||
"node-html-parser": "7.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@anthropic-ai/sdk": "0.70.0",
|
||||
"@anthropic-ai/sdk": "0.70.1",
|
||||
"@braintree/sanitize-url": "7.1.1",
|
||||
"@electron/remote": "2.1.3",
|
||||
"@preact/preset-vite": "2.10.2",
|
||||
@ -86,7 +86,7 @@
|
||||
"escape-html": "1.0.3",
|
||||
"express": "5.1.0",
|
||||
"express-http-proxy": "2.1.2",
|
||||
"express-openid-connect": "2.19.2",
|
||||
"express-openid-connect": "2.19.3",
|
||||
"express-rate-limit": "8.2.1",
|
||||
"express-session": "1.18.2",
|
||||
"file-uri-to-path": "2.0.0",
|
||||
@ -104,7 +104,7 @@
|
||||
"is-svg": "6.1.0",
|
||||
"jimp": "1.6.0",
|
||||
"marked": "17.0.1",
|
||||
"mime-types": "3.0.1",
|
||||
"mime-types": "3.0.2",
|
||||
"multer": "2.0.2",
|
||||
"normalize-strings": "1.1.1",
|
||||
"ollama": "0.6.3",
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
<figcaption>Screenshot of the note contextual menu indicating the “Export as PDF”
|
||||
option.</figcaption>
|
||||
</figure>
|
||||
|
||||
<h2>Printing</h2>
|
||||
<p>This feature allows printing of notes. It works on both the desktop client,
|
||||
but also on the web.</p>
|
||||
@ -59,9 +60,9 @@ class="admonition note">
|
||||
orientation, size. However, there are a few <a class="reference-link"
|
||||
href="#root/_help_zEY4DaJG4YT5">Attributes</a> to adjust some of the settings:</p>
|
||||
<ul>
|
||||
<li data-list-item-id="e05b1bc3a57c550c493c8b1030c301673">To print in landscape mode instead of portrait (useful for big diagrams
|
||||
<li>To print in landscape mode instead of portrait (useful for big diagrams
|
||||
or slides), add <code>#printLandscape</code>.</li>
|
||||
<li data-list-item-id="e6d7f6bb720e1f94994aa178881885dbd">By default, the resulting PDF will be in Letter format. It is possible
|
||||
<li>By default, the resulting PDF will be in Letter format. It is possible
|
||||
to adjust it to another page size via the <code>#printPageSize</code> attribute,
|
||||
with one of the following values: <code>A0</code>, <code>A1</code>, <code>A2</code>, <code>A3</code>, <code>A4</code>, <code>A5</code>, <code>A6</code>, <code>Legal</code>, <code>Letter</code>, <code>Tabloid</code>, <code>Ledger</code>.</li>
|
||||
</ul>
|
||||
@ -71,12 +72,11 @@ class="admonition note">
|
||||
</aside>
|
||||
<h2>Printing multiple notes</h2>
|
||||
<p>Since v0.100.0, it is possible to print more than one note at the time
|
||||
by using <a class="reference-link" href="#root/pOsGYCXsbNQG/_help_GTwFsgaA0lCt">Collections</a>:</p>
|
||||
by using <a class="reference-link" href="#root/_help_GTwFsgaA0lCt">Collections</a>:</p>
|
||||
<ol>
|
||||
<li data-list-item-id="e1caaf943b13fd4764f93c58ea5f4f0c4">First create a collection.</li>
|
||||
<li data-list-item-id="e3593024c9c69c3d26295d1e0152c813d">Configure it to use <a class="reference-link" href="#root/pOsGYCXsbNQG/GTwFsgaA0lCt/_help_mULW0Q3VojwY">List View</a>.</li>
|
||||
<li
|
||||
data-list-item-id="ebeea878f04af6f1da53fc0e8a80caf2d">Print the collection note normally.</li>
|
||||
<li>First create a collection.</li>
|
||||
<li>Configure it to use <a class="reference-link" href="#root/_help_mULW0Q3VojwY">List View</a>.</li>
|
||||
<li>Print the collection note normally.</li>
|
||||
</ol>
|
||||
<p>The resulting collection will contain all the children of the collection,
|
||||
while maintaining the hierarchy.</p>
|
||||
@ -86,9 +86,9 @@ class="admonition note">
|
||||
href="#root/_help_4TIF1oA4VQRO">Options</a> and assigning a key combination
|
||||
for:</p>
|
||||
<ul>
|
||||
<li class="ck-list-marker-italic" data-list-item-id="e9595278e625ee8de30a6e88fb00d48e3"><em>Print Active Note</em>
|
||||
<li><em>Print Active Note</em>
|
||||
</li>
|
||||
<li class="ck-list-marker-italic" data-list-item-id="e981d4cf371e1ff69416a796d88e88709"><em>Export Active Note as PDF</em>
|
||||
<li><em>Export Active Note as PDF</em>
|
||||
</li>
|
||||
</ul>
|
||||
<h2>Constraints & limitations</h2>
|
||||
@ -96,28 +96,39 @@ class="admonition note">
|
||||
supported when printing, in which case the <em>Print</em> and <em>Export as PDF</em> options
|
||||
will be disabled.</p>
|
||||
<ul>
|
||||
<li data-list-item-id="e82f01875cc03dcdab5328121654d815c">For <a class="reference-link" href="#root/_help_6f9hih2hXXZk">Code</a> notes:
|
||||
<li>For <a class="reference-link" href="#root/_help_6f9hih2hXXZk">Code</a> notes:
|
||||
<ul>
|
||||
<li data-list-item-id="eea76e6bf545a3b54270ff86a74ca0d8d">Line numbers are not printed.</li>
|
||||
<li data-list-item-id="edea65d8d3dedd354431e1e3a5dcd2e08">Syntax highlighting is enabled, however a default theme (Visual Studio)
|
||||
<li>Line numbers are not printed.</li>
|
||||
<li>Syntax highlighting is enabled, however a default theme (Visual Studio)
|
||||
is enforced.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li data-list-item-id="ec32cca86e4b0e2f75a2f1a06d2219e0b">For <a class="reference-link" href="#root/_help_GTwFsgaA0lCt">Collections</a>:
|
||||
<li>For <a class="reference-link" href="#root/_help_GTwFsgaA0lCt">Collections</a>,
|
||||
the following are supported:
|
||||
<ul>
|
||||
<li data-list-item-id="e0e1fc82e1141d3f4a609699e228ccc73"><a class="reference-link" href="#root/pOsGYCXsbNQG/GTwFsgaA0lCt/_help_mULW0Q3VojwY">List View</a> is
|
||||
supported, allowing to print multiple notes at once while preserving hierarchy
|
||||
(similar to a book).</li>
|
||||
<li data-list-item-id="ee114b8468eaf24bce451f5ec4bda3da4"><a class="reference-link" href="#root/_help_zP3PMqaG71Ct">Presentation</a> is
|
||||
also supported, where each slide/subnote is displayed.</li>
|
||||
<li data-list-item-id="e4efe886c3ca1d19a49196340d9e1f6c8">The rest of the collections are not supported, but we plan to add support
|
||||
<li><a class="reference-link" href="#root/_help_mULW0Q3VojwY">List View</a>, allowing
|
||||
to print multiple notes at once while preserving hierarchy (similar to
|
||||
a book).</li>
|
||||
<li><a class="reference-link" href="#root/_help_zP3PMqaG71Ct">Presentation</a>,
|
||||
where each slide/sub-note is displayed.</li>
|
||||
<li><a class="reference-link" href="#root/_help_2FvYrpmOXm29">Table</a>, where the
|
||||
table is rendered in a print-friendly way.
|
||||
<ul>
|
||||
<li>Tables that are too complex (especially if they have multiple columns)
|
||||
might not fit properly, however tables with a large number of rows are
|
||||
supported thanks to pagination.</li>
|
||||
<li>Consider printing in landscape mode, or using <code>#printLandscape</code> if
|
||||
exporting to PDF.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>The rest of the collections are not supported, but we plan to add support
|
||||
for all the collection types at some point.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li data-list-item-id="ee721d0145486818bd914a26594699cbd">Using <a class="reference-link" href="#root/_help_AlhDUqhENtH7">Custom app-wide CSS</a> for
|
||||
<li>Using <a class="reference-link" href="#root/_help_AlhDUqhENtH7">Custom app-wide CSS</a> for
|
||||
printing is not longer supported, due to a more stable but isolated mechanism.
|
||||
<ul>
|
||||
<li data-list-item-id="e2e1228d8d62cbdc8e96a7cbc9655c2ca">We plan to introduce a new mechanism specifically for a print CSS.</li>
|
||||
<li>We plan to introduce a new mechanism specifically for a print CSS.</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
@ -128,10 +139,10 @@ class="admonition note">
|
||||
printing.</p>
|
||||
<p>To do so:</p>
|
||||
<ul>
|
||||
<li data-list-item-id="ea90c233190428f0aacfcca4abe2f6b18">Create a CSS <a href="#root/_help_6f9hih2hXXZk">code note</a>.</li>
|
||||
<li data-list-item-id="ec0756dfa1ce83087dd2c9bcc289d234b">On the note being printed, apply the <code>~printCss</code> relation to
|
||||
<li>Create a CSS <a href="#root/_help_6f9hih2hXXZk">code note</a>.</li>
|
||||
<li>On the note being printed, apply the <code>~printCss</code> relation to
|
||||
point to the newly created CSS code note.</li>
|
||||
<li data-list-item-id="ed05b40a29e6b442327bf439286096ac6">To apply the CSS to multiple notes, consider using <a href="#root/_help_bwZpz2ajCEwO">inheritable attributes</a> or
|
||||
<li>To apply the CSS to multiple notes, consider using <a href="#root/_help_bwZpz2ajCEwO">inheritable attributes</a> or
|
||||
<a
|
||||
class="reference-link" href="#root/_help_KC1HB96bqqHX">Templates</a>.</li>
|
||||
</ul>
|
||||
@ -142,13 +153,12 @@ class="admonition note">
|
||||
}</code></pre>
|
||||
<p>To remark:</p>
|
||||
<ul>
|
||||
<li data-list-item-id="ec7fa7fb43c85ba65185b42a9ed590da7">Multiple CSS notes can be add by using multiple <code>~printCss</code> relations.</li>
|
||||
<li
|
||||
data-list-item-id="e1db64e345bbaf53151b84a69ff91376f">If the note pointing to the <code>printCss</code> doesn't have the right
|
||||
<li>Multiple CSS notes can be add by using multiple <code>~printCss</code> relations.</li>
|
||||
<li>If the note pointing to the <code>printCss</code> doesn't have the right
|
||||
note type or mime type, it will be ignored.</li>
|
||||
<li data-list-item-id="e8b2d24c4a6781c5516d0551f51b3947b">If migrating from a previous version where <a class="reference-link"
|
||||
href="#root/_help_AlhDUqhENtH7">Custom app-wide CSS</a>, there's no need for <code>@media print {</code> since
|
||||
the style-sheet is used only for printing.</li>
|
||||
<li>If migrating from a previous version where <a class="reference-link"
|
||||
href="#root/_help_AlhDUqhENtH7">Custom app-wide CSS</a>, there's no need for <code>@media print {</code> since
|
||||
the style-sheet is used only for printing.</li>
|
||||
</ul>
|
||||
<h2>Under the hood</h2>
|
||||
<p>Both printing and exporting as PDF use the same mechanism: a note is rendered
|
||||
|
||||
11
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Collections/List View.html
generated
vendored
11
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Collections/List View.html
generated
vendored
@ -12,22 +12,21 @@
|
||||
as a single continuous document.</p>
|
||||
<h2>Interaction</h2>
|
||||
<ul>
|
||||
<li data-list-item-id="ee85c9dce1f91b700d8f13bdc9500bc62">Each note can be expanded or collapsed by clicking on the arrow to the
|
||||
<li>Each note can be expanded or collapsed by clicking on the arrow to the
|
||||
left of the title.</li>
|
||||
<li data-list-item-id="e84faa71c2b0bf22a09490b35134f2687">In the <a class="reference-link" href="#root/_help_BlN9DFI679QC">Ribbon</a>,
|
||||
<li>In the <a class="reference-link" href="#root/_help_BlN9DFI679QC">Ribbon</a>,
|
||||
in the <em>Collection</em> tab there are options to expand and to collapse
|
||||
all notes easily.</li>
|
||||
</ul>
|
||||
<h2>Printing and exporting to PDF</h2>
|
||||
<p>Since v0.100.0, list collections can be <a href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/BFs8mudNFgCS/_help_NRnIZmSMc5sj">printed or exported to PDF</a>.</p>
|
||||
<p>Since v0.100.0, list collections can be <a href="#root/_help_NRnIZmSMc5sj">printed or exported to PDF</a>.</p>
|
||||
<p>A printed list collection will print all the notes in the collection,
|
||||
in the right order and preserving the full hierarchy.</p>
|
||||
<p>If exported to PDF within the desktop application, there is additional
|
||||
functionality:</p>
|
||||
<ul>
|
||||
<li data-list-item-id="ec4b9a29dd7f601d1415b3ca9fa414fde">The table of contents of the PDF will reflect the structure of the notes.</li>
|
||||
<li
|
||||
data-list-item-id="ef5fa5e9c68e7cbdf9a5e468b406e298a">Reference and inline links to other notes within the same hierarchy will
|
||||
<li>The table of contents of the PDF will reflect the structure of the notes.</li>
|
||||
<li>Reference and inline links to other notes within the same hierarchy will
|
||||
be functional (will jump to the corresponding page). If a link refers to
|
||||
a note that is not in the printed hierarchy, it will be unlinked.</li>
|
||||
</ul>
|
||||
@ -1,12 +1,13 @@
|
||||
<ul>
|
||||
<li><code>doRender</code> must not be overridden, instead <code>doRenderBody()</code> has
|
||||
<li data-list-item-id="ea7daf7caa74a0f97f6f17625eacc6125"><code>doRender</code> must not be overridden, instead <code>doRenderBody()</code> has
|
||||
to be overridden.
|
||||
<ul>
|
||||
<li><code>doRenderBody</code> can optionally be <code>async</code>.</li>
|
||||
<li data-list-item-id="ef0f078ef265c54da5ff012f12cf7adee"><code>doRenderBody</code> can optionally be <code>async</code>.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><code>parentWidget()</code> must be set to <code>“rightPane”</code>.</li>
|
||||
<li><code>widgetTitle()</code> getter can optionally be overriden, otherwise
|
||||
<li data-list-item-id="e8b97337d5d8d708f5555b6f78fc1a993"><code>parentWidget()</code> must be set to <code>“rightPane”</code>.</li>
|
||||
<li
|
||||
data-list-item-id="eccb7323be12f3facd371512a19582705"><code>widgetTitle()</code> getter can optionally be overriden, otherwise
|
||||
the widget will be displayed as “Untitled widget”.</li>
|
||||
</ul><pre><code class="language-text-x-trilium-auto">const template = `<div>Hi</div>`;
|
||||
|
||||
@ -23,12 +24,13 @@ class ToDoListWidget extends api.RightPanelWidget {
|
||||
}
|
||||
|
||||
async refreshWithNote(note) {
|
||||
this.toggleInt(false);
|
||||
this.triggerCommand("reEvaluateRightPaneVisibility");
|
||||
this.toggleInt(true);
|
||||
this.triggerCommand("reEvaluateRightPaneVisibility");
|
||||
// Do something when the note changes.
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new ToDoListWidget();</code></pre>
|
||||
<p>The implementation is in <code>src/public/app/widgets/right_panel_widget.js</code>.</p>
|
||||
<p>The implementation is in <code>src/public/app/widgets/right_panel_widget.js</code>.</p>
|
||||
<h2>Conditionally changing visibility</h2>
|
||||
<p>In <code>refreshWithNote</code>:</p><pre><code class="language-text-x-trilium-auto">const visible = true; // replace with your own visibility logic
|
||||
this.toggleInt(visible);
|
||||
this.triggerCommand("reEvaluateRightPaneVisibility");</code></pre>
|
||||
@ -73,8 +73,7 @@ const EXTENSION_TO_MIME = new Map<string, string>([
|
||||
[".ts", "text/x-typescript"],
|
||||
[".excalidraw", "application/json"],
|
||||
[".mermaid", "text/vnd.mermaid"],
|
||||
[".mmd", "text/vnd.mermaid"],
|
||||
[".mp4", "video/mp4"] // https://github.com/jshttp/mime-types/issues/138
|
||||
[".mmd", "text/vnd.mermaid"]
|
||||
]);
|
||||
|
||||
/** @returns false if MIME is not detected */
|
||||
|
||||
@ -82,7 +82,7 @@ interface ExportAsPdfOpts {
|
||||
electron.ipcMain.on("print-note", async (e, { notePath }: PrintOpts) => {
|
||||
const browserWindow = await getBrowserWindowForPrinting(e, notePath, "printing");
|
||||
browserWindow.webContents.print({}, (success, failureReason) => {
|
||||
if (!success) {
|
||||
if (!success && failureReason !== "Print job canceled") {
|
||||
electron.dialog.showErrorBox(t("pdf.unable-to-print"), failureReason);
|
||||
}
|
||||
e.sender.send("print-done");
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
"typescript": "5.9.3",
|
||||
"user-agent-data-types": "0.4.2",
|
||||
"vite": "7.2.4",
|
||||
"vitest": "4.0.12"
|
||||
"vitest": "4.0.13"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "preact"
|
||||
|
||||
@ -51,7 +51,8 @@
|
||||
"mermaid_description": "Créez des diagrammes tels que des organigrammes, des diagrammes de classes et de séquences, des diagrammes de Gantt et bien d'autres, en utilisant la syntaxe Mermaid.",
|
||||
"mindmap_title": "Carte mentale",
|
||||
"mindmap_description": "Organisez vos pensées visuellement ou faites une séance de brainstorming.",
|
||||
"others_list": "et autres : <0>carte de notes</0>, <1>carte de relations</1>, <2>recherches enregistrées</2>, <3>note de rendu</3> et <4>vues Web</4>."
|
||||
"others_list": "et autres : <0>carte de notes</0>, <1>carte de relations</1>, <2>recherches enregistrées</2>, <3>note de rendu</3> et <4>vues Web</4>.",
|
||||
"title": "Plusieurs façons de représenter vos informations"
|
||||
},
|
||||
"faq": {
|
||||
"database_question": "Où sont les données stockées?",
|
||||
@ -168,7 +169,9 @@
|
||||
"board_title": "Tableau de bord",
|
||||
"board_description": "Organisez vos tâches ou l'état de vos projets dans un tableau Kanban avec un moyen simple de créer de nouveaux éléments et colonnes et de modifier simplement leur état en les faisant glisser sur le tableau.",
|
||||
"geomap_title": "Géocarte",
|
||||
"geomap_description": "Planifiez vos vacances ou marquez vos points d'intérêt directement sur une carte géographique grâce à des marqueurs personnalisables. Affichez les traces GPX enregistrées pour suivre vos itinéraires."
|
||||
"geomap_description": "Planifiez vos vacances ou marquez vos points d'intérêt directement sur une carte géographique grâce à des marqueurs personnalisables. Affichez les traces GPX enregistrées pour suivre vos itinéraires.",
|
||||
"title": "Collections",
|
||||
"presentation_title": "Présentation"
|
||||
},
|
||||
"download_now": {
|
||||
"text": "Télécharger maintenant. ",
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
# Documentation
|
||||
There are multiple types of documentation for Trilium:<img class="image-style-align-right" src="api/images/CJFtZbAX4Otj/Documentation_image.png" width="205" height="162">
|
||||
There are multiple types of documentation for Trilium:<img class="image-style-align-right" src="api/images/gKWerx46R13O/Documentation_image.png" width="205" height="162">
|
||||
|
||||
* The _User Guide_ represents the user-facing documentation. This documentation can be browsed by users directly from within Trilium, by pressing <kbd>F1</kbd>.
|
||||
* The _Developer's Guide_ represents a set of Markdown documents that present the internals of Trilium, for developers.
|
||||
|
||||
53
docs/User Guide/!!!meta.json
vendored
53
docs/User Guide/!!!meta.json
vendored
@ -4062,66 +4062,80 @@
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "4TIF1oA4VQRO",
|
||||
"value": "GTwFsgaA0lCt",
|
||||
"isInheritable": false,
|
||||
"position": 40
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "KSZ04uQ2D1St",
|
||||
"value": "mULW0Q3VojwY",
|
||||
"isInheritable": false,
|
||||
"position": 50
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "6f9hih2hXXZk",
|
||||
"value": "4TIF1oA4VQRO",
|
||||
"isInheritable": false,
|
||||
"position": 60
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "GTwFsgaA0lCt",
|
||||
"value": "KSZ04uQ2D1St",
|
||||
"isInheritable": false,
|
||||
"position": 70
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "zP3PMqaG71Ct",
|
||||
"value": "6f9hih2hXXZk",
|
||||
"isInheritable": false,
|
||||
"position": 80
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "AlhDUqhENtH7",
|
||||
"value": "zP3PMqaG71Ct",
|
||||
"isInheritable": false,
|
||||
"position": 90
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "bwZpz2ajCEwO",
|
||||
"value": "2FvYrpmOXm29",
|
||||
"isInheritable": false,
|
||||
"position": 100
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "KC1HB96bqqHX",
|
||||
"value": "AlhDUqhENtH7",
|
||||
"isInheritable": false,
|
||||
"position": 110
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "0ESUbbAxVnoK",
|
||||
"value": "bwZpz2ajCEwO",
|
||||
"isInheritable": false,
|
||||
"position": 120
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "KC1HB96bqqHX",
|
||||
"isInheritable": false,
|
||||
"position": 130
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "0ESUbbAxVnoK",
|
||||
"isInheritable": false,
|
||||
"position": 140
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "iconClass",
|
||||
@ -4135,13 +4149,6 @@
|
||||
"value": "printing-and-pdf-export",
|
||||
"isInheritable": false,
|
||||
"position": 110
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "mULW0Q3VojwY",
|
||||
"isInheritable": false,
|
||||
"position": 130
|
||||
}
|
||||
],
|
||||
"format": "markdown",
|
||||
@ -10472,6 +10479,13 @@
|
||||
"isInheritable": false,
|
||||
"position": 20
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "NRnIZmSMc5sj",
|
||||
"isInheritable": false,
|
||||
"position": 30
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "iconClass",
|
||||
@ -10485,13 +10499,6 @@
|
||||
"value": "list",
|
||||
"isInheritable": false,
|
||||
"position": 30
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "NRnIZmSMc5sj",
|
||||
"isInheritable": false,
|
||||
"position": 40
|
||||
}
|
||||
],
|
||||
"format": "markdown",
|
||||
|
||||
@ -73,9 +73,12 @@ Not all <a class="reference-link" href="../../Note%20Types.md">Note Types</a>
|
||||
* For <a class="reference-link" href="../../Note%20Types/Code.md">Code</a> notes:
|
||||
* Line numbers are not printed.
|
||||
* Syntax highlighting is enabled, however a default theme (Visual Studio) is enforced.
|
||||
* For <a class="reference-link" href="../../Collections.md">Collections</a>:
|
||||
* <a class="reference-link" href="../../Collections/List%20View.md">List View</a> is supported, allowing to print multiple notes at once while preserving hierarchy (similar to a book).
|
||||
* <a class="reference-link" href="../../Collections/Presentation.md">Presentation</a> is also supported, where each slide/subnote is displayed.
|
||||
* For <a class="reference-link" href="../../Collections.md">Collections</a>, the following are supported:
|
||||
* <a class="reference-link" href="../../Collections/List%20View.md">List View</a>, allowing to print multiple notes at once while preserving hierarchy (similar to a book).
|
||||
* <a class="reference-link" href="../../Collections/Presentation.md">Presentation</a>, where each slide/sub-note is displayed.
|
||||
* <a class="reference-link" href="../../Collections/Table.md">Table</a>, where the table is rendered in a print-friendly way.
|
||||
* Tables that are too complex (especially if they have multiple columns) might not fit properly, however tables with a large number of rows are supported thanks to pagination.
|
||||
* Consider printing in landscape mode, or using `#printLandscape` if exporting to PDF.
|
||||
* The rest of the collections are not supported, but we plan to add support for all the collection types at some point.
|
||||
* Using <a class="reference-link" href="../../Theme%20development/Custom%20app-wide%20CSS.md">Custom app-wide CSS</a> for printing is not longer supported, due to a more stable but isolated mechanism.
|
||||
* We plan to introduce a new mechanism specifically for a print CSS.
|
||||
|
||||
@ -20,14 +20,21 @@ class ToDoListWidget extends api.RightPanelWidget {
|
||||
}
|
||||
|
||||
async refreshWithNote(note) {
|
||||
this.toggleInt(false);
|
||||
this.triggerCommand("reEvaluateRightPaneVisibility");
|
||||
this.toggleInt(true);
|
||||
this.triggerCommand("reEvaluateRightPaneVisibility");
|
||||
// Do something when the note changes.
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new ToDoListWidget();
|
||||
```
|
||||
|
||||
The implementation is in `src/public/app/widgets/right_panel_widget.js`.
|
||||
The implementation is in `src/public/app/widgets/right_panel_widget.js`.
|
||||
|
||||
## Conditionally changing visibility
|
||||
|
||||
In `refreshWithNote`:
|
||||
|
||||
```
|
||||
const visible = true; // replace with your own visibility logic
|
||||
this.toggleInt(visible);
|
||||
this.triggerCommand("reEvaluateRightPaneVisibility");
|
||||
```
|
||||
@ -44,9 +44,9 @@
|
||||
"@triliumnext/server": "workspace:*",
|
||||
"@types/express": "5.0.5",
|
||||
"@types/node": "24.10.1",
|
||||
"@vitest/browser-webdriverio": "4.0.12",
|
||||
"@vitest/coverage-v8": "4.0.12",
|
||||
"@vitest/ui": "4.0.12",
|
||||
"@vitest/browser-webdriverio": "4.0.13",
|
||||
"@vitest/coverage-v8": "4.0.13",
|
||||
"@vitest/ui": "4.0.13",
|
||||
"chalk": "5.6.2",
|
||||
"cross-env": "10.1.0",
|
||||
"dpdm": "3.14.0",
|
||||
@ -68,7 +68,7 @@
|
||||
"upath": "2.0.1",
|
||||
"vite": "7.2.4",
|
||||
"vite-plugin-dts": "~4.5.0",
|
||||
"vitest": "4.0.12"
|
||||
"vitest": "4.0.13"
|
||||
},
|
||||
"license": "AGPL-3.0-only",
|
||||
"author": {
|
||||
|
||||
@ -26,19 +26,19 @@
|
||||
"@ckeditor/ckeditor5-package-tools": "5.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "~8.47.0",
|
||||
"@typescript-eslint/parser": "8.47.0",
|
||||
"@vitest/browser": "4.0.12",
|
||||
"@vitest/coverage-istanbul": "4.0.12",
|
||||
"@vitest/browser": "4.0.13",
|
||||
"@vitest/coverage-istanbul": "4.0.13",
|
||||
"ckeditor5": "47.2.0",
|
||||
"eslint": "9.39.1",
|
||||
"eslint-config-ckeditor5": ">=9.1.0",
|
||||
"http-server": "14.1.1",
|
||||
"lint-staged": "16.2.7",
|
||||
"stylelint": "16.25.0",
|
||||
"stylelint": "16.26.0",
|
||||
"stylelint-config-ckeditor5": ">=9.1.0",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "5.9.3",
|
||||
"vite-plugin-svgo": "~2.0.0",
|
||||
"vitest": "4.0.12",
|
||||
"vitest": "4.0.13",
|
||||
"webdriverio": "9.20.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@ -27,19 +27,19 @@
|
||||
"@ckeditor/ckeditor5-package-tools": "5.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "~8.47.0",
|
||||
"@typescript-eslint/parser": "8.47.0",
|
||||
"@vitest/browser": "4.0.12",
|
||||
"@vitest/coverage-istanbul": "4.0.12",
|
||||
"@vitest/browser": "4.0.13",
|
||||
"@vitest/coverage-istanbul": "4.0.13",
|
||||
"ckeditor5": "47.2.0",
|
||||
"eslint": "9.39.1",
|
||||
"eslint-config-ckeditor5": ">=9.1.0",
|
||||
"http-server": "14.1.1",
|
||||
"lint-staged": "16.2.7",
|
||||
"stylelint": "16.25.0",
|
||||
"stylelint": "16.26.0",
|
||||
"stylelint-config-ckeditor5": ">=9.1.0",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "5.9.3",
|
||||
"vite-plugin-svgo": "~2.0.0",
|
||||
"vitest": "4.0.12",
|
||||
"vitest": "4.0.13",
|
||||
"webdriverio": "9.20.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@ -29,19 +29,19 @@
|
||||
"@ckeditor/ckeditor5-package-tools": "5.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "~8.47.0",
|
||||
"@typescript-eslint/parser": "8.47.0",
|
||||
"@vitest/browser": "4.0.12",
|
||||
"@vitest/coverage-istanbul": "4.0.12",
|
||||
"@vitest/browser": "4.0.13",
|
||||
"@vitest/coverage-istanbul": "4.0.13",
|
||||
"ckeditor5": "47.2.0",
|
||||
"eslint": "9.39.1",
|
||||
"eslint-config-ckeditor5": ">=9.1.0",
|
||||
"http-server": "14.1.1",
|
||||
"lint-staged": "16.2.7",
|
||||
"stylelint": "16.25.0",
|
||||
"stylelint": "16.26.0",
|
||||
"stylelint-config-ckeditor5": ">=9.1.0",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "5.9.3",
|
||||
"vite-plugin-svgo": "~2.0.0",
|
||||
"vitest": "4.0.12",
|
||||
"vitest": "4.0.13",
|
||||
"webdriverio": "9.20.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@ -30,19 +30,19 @@
|
||||
"@ckeditor/ckeditor5-package-tools": "5.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "~8.47.0",
|
||||
"@typescript-eslint/parser": "8.47.0",
|
||||
"@vitest/browser": "4.0.12",
|
||||
"@vitest/coverage-istanbul": "4.0.12",
|
||||
"@vitest/browser": "4.0.13",
|
||||
"@vitest/coverage-istanbul": "4.0.13",
|
||||
"ckeditor5": "47.2.0",
|
||||
"eslint": "9.39.1",
|
||||
"eslint-config-ckeditor5": ">=9.1.0",
|
||||
"http-server": "14.1.1",
|
||||
"lint-staged": "16.2.7",
|
||||
"stylelint": "16.25.0",
|
||||
"stylelint": "16.26.0",
|
||||
"stylelint-config-ckeditor5": ">=9.1.0",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "5.9.3",
|
||||
"vite-plugin-svgo": "~2.0.0",
|
||||
"vitest": "4.0.12",
|
||||
"vitest": "4.0.13",
|
||||
"webdriverio": "9.20.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@ -29,19 +29,19 @@
|
||||
"@ckeditor/ckeditor5-package-tools": "5.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "~8.47.0",
|
||||
"@typescript-eslint/parser": "8.47.0",
|
||||
"@vitest/browser": "4.0.12",
|
||||
"@vitest/coverage-istanbul": "4.0.12",
|
||||
"@vitest/browser": "4.0.13",
|
||||
"@vitest/coverage-istanbul": "4.0.13",
|
||||
"ckeditor5": "47.2.0",
|
||||
"eslint": "9.39.1",
|
||||
"eslint-config-ckeditor5": ">=9.1.0",
|
||||
"http-server": "14.1.1",
|
||||
"lint-staged": "16.2.7",
|
||||
"stylelint": "16.25.0",
|
||||
"stylelint": "16.26.0",
|
||||
"stylelint-config-ckeditor5": ">=9.1.0",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "5.9.3",
|
||||
"vite-plugin-svgo": "~2.0.0",
|
||||
"vitest": "4.0.12",
|
||||
"vitest": "4.0.13",
|
||||
"webdriverio": "9.20.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
487
pnpm-lock.yaml
generated
487
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user