Merge branch 'main' into renovate/officeparser-6.x

This commit is contained in:
Elian Doran
2026-04-18 19:56:55 +03:00
committed by GitHub
81 changed files with 1599 additions and 746 deletions

View File

@@ -66,12 +66,20 @@ runs:
if: ${{ inputs.os == 'linux' }}
shell: ${{ inputs.shell }}
run: |
sudo apt-get update && sudo apt-get install rpm flatpak-builder elfutils
sudo apt-get update && sudo apt-get install rpm flatpak-builder elfutils libfuse2
flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
FLATPAK_ARCH=$(if [[ ${{ inputs.arch }} = 'arm64' ]]; then echo 'aarch64'; else echo 'x86_64'; fi)
FLATPAK_VERSION='24.08'
flatpak install --user --no-deps --arch $FLATPAK_ARCH --assumeyes runtime/org.freedesktop.Platform/$FLATPAK_ARCH/$FLATPAK_VERSION runtime/org.freedesktop.Sdk/$FLATPAK_ARCH/$FLATPAK_VERSION org.electronjs.Electron2.BaseApp/$FLATPAK_ARCH/$FLATPAK_VERSION
- name: Install appimagetool
if: ${{ inputs.os == 'linux' }}
shell: ${{ inputs.shell }}
run: |
APPIMAGETOOL_ARCH=$(if [[ ${{ inputs.arch }} = 'arm64' ]]; then echo 'aarch64'; else echo 'x86_64'; fi)
wget -q "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-${APPIMAGETOOL_ARCH}.AppImage" -O /usr/local/bin/appimagetool
chmod +x /usr/local/bin/appimagetool
- name: Update build info
shell: ${{ inputs.shell }}
run: pnpm run chore:update-build-info
@@ -90,6 +98,14 @@ runs:
TARGET_ARCH: ${{ inputs.arch }}
run: pnpm run --filter desktop electron-forge:make --arch=${{ inputs.arch }} --platform=${{ inputs.forge_platform }}
- name: Build AppImage
if: ${{ inputs.os == 'linux' }}
shell: ${{ inputs.shell }}
env:
TRILIUM_ARTIFACT_NAME_HINT: TriliumNotes-${{ github.ref_name }}-${{ inputs.os }}-${{ inputs.arch }}
APPIMAGE_EXTRACT_AND_RUN: "1"
run: bash apps/desktop/scripts/build-appimage.sh ${{ inputs.arch }}
# Add DMG signing step
- name: Sign DMG
if: inputs.os == 'macos'

View File

@@ -0,0 +1,44 @@
name: Claude Code Review
on:
pull_request:
types: [opened, synchronize, ready_for_review, reopened]
# Optional: Only run on specific file changes
# paths:
# - "src/**/*.ts"
# - "src/**/*.tsx"
# - "src/**/*.js"
# - "src/**/*.jsx"
jobs:
claude-review:
# Optional: Filter by PR author
# if: |
# github.event.pull_request.user.login == 'external-contributor' ||
# github.event.pull_request.user.login == 'new-developer' ||
# github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
issues: read
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Run Claude Code Review
id: claude-review
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
plugin_marketplaces: 'https://github.com/anthropics/claude-code.git'
plugins: 'code-review@claude-code-plugins'
prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}'
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
# or https://code.claude.com/docs/en/cli-reference for available options

50
.github/workflows/claude.yml vendored Normal file
View File

@@ -0,0 +1,50 @@
name: Claude Code
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened, assigned]
pull_request_review:
types: [submitted]
jobs:
claude:
if: |
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: write
id-token: write
actions: read # Required for Claude to read CI results on PRs
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Run Claude Code
id: claude
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# This is an optional setting that allows Claude to read CI results on PRs
additional_permissions: |
actions: read
# Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
# prompt: 'Update the pull request description to include a summary of changes.'
# Optional: Add claude_args to customize behavior and configuration
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
# or https://code.claude.com/docs/en/cli-reference for available options
# claude_args: '--allowed-tools Bash(gh pr *)'

2
.nvmrc
View File

@@ -1 +1 @@
24.14.1
24.15.0

View File

@@ -16,7 +16,7 @@
"license": "AGPL-3.0-only",
"packageManager": "pnpm@10.33.0",
"devDependencies": {
"@redocly/cli": "2.26.0",
"@redocly/cli": "2.28.0",
"archiver": "7.0.1",
"fs-extra": "11.3.4",
"js-yaml": "4.1.1",

View File

@@ -66,7 +66,7 @@
"mind-elixir": "5.10.0",
"panzoom": "9.4.4",
"preact": "10.29.1",
"react-i18next": "17.0.2",
"react-i18next": "17.0.3",
"react-window": "2.2.7",
"reveal.js": "6.0.1",
"rrule": "2.8.1",

View File

@@ -66,7 +66,15 @@ class FAttribute {
}
get isAutoLink() {
return this.type === "relation" && ["internalLink", "imageLink", "relationMapLink", "includeNoteLink"].includes(this.name);
if (this.type === "relation") {
return ["internalLink", "imageLink", "relationMapLink", "includeNoteLink"].includes(this.name);
}
if (this.type === "label") {
return this.name === "internalBookmark";
}
return false;
}
get toString() {

View File

@@ -2,7 +2,6 @@
:root {
--print-font-size: 11pt;
--ck-content-color-image-caption-background: transparent !important;
}
@page {
@@ -11,9 +10,12 @@
html,
body {
--print-font-family: var(--detail-font-family, sans-serif);
width: 100%;
height: 100%;
color: black;
font-family: var(--print-font-family);
}
.note-list-widget.full-height,
@@ -26,6 +28,12 @@ body {
}
body[data-note-type="text"] .ck-content {
--ck-content-font-family: var(--print-font-family);
--ck-content-font-size: var(--print-font-size);
--ck-content-font-color: black;
--ck-content-line-height: 1.5;
--ck-content-color-image-caption-background: transparent;
font-size: var(--print-font-size);
text-align: justify;
}
@@ -154,4 +162,4 @@ span[style] {
.page-break::after {
display: none !important;
}
/* #endregion */
/* #endregion */

View File

@@ -31,6 +31,13 @@ async function main() {
if (!noteId) return;
await import("./print.css");
// Load the user's font preferences so that --detail-font-family is available.
const fontLink = document.createElement("link");
fontLink.rel = "stylesheet";
fontLink.href = "api/fonts";
document.head.appendChild(fontLink);
const note = await froca.getNote(noteId);
const bodyWrapper = document.createElement("div");
@@ -105,6 +112,9 @@ function SingleNoteRenderer({ note, onReady }: RendererProps) {
// Check custom CSS.
await loadCustomCss(note);
// Wait for all fonts (including those from custom CSS) to finish loading.
await document.fonts.ready;
}
load().then(() => requestAnimationFrame(() => onReady({
@@ -130,6 +140,7 @@ function CollectionRenderer({ note, onReady, onProgressChanged }: RendererProps)
media="print"
onReady={async (data: PrintReport) => {
await loadCustomCss(note);
await document.fonts.ready;
onReady(data);
}}
onProgressChanged={onProgressChanged}

View File

@@ -7,6 +7,10 @@ async function renderAttribute(attribute: FAttribute, renderIsInheritable: boole
const isInheritable = renderIsInheritable && attribute.isInheritable ? `(inheritable)` : "";
const $attr = $("<span>");
if (attribute.isAutoLink) {
return $attr;
}
if (attribute.type === "label") {
$attr.append(document.createTextNode(`#${attribute.name}${isInheritable}`));
@@ -15,9 +19,6 @@ async function renderAttribute(attribute: FAttribute, renderIsInheritable: boole
$attr.append(document.createTextNode(formatValue(attribute.value)));
}
} else if (attribute.type === "relation") {
if (attribute.isAutoLink) {
return $attr;
}
// when the relation has just been created, then it might not have a value
if (attribute.value) {

View File

@@ -60,6 +60,8 @@ export interface ViewScope {
*/
tocPreviousVisible?: boolean;
tocCollapsedHeadings?: Set<string>;
/** When set, scrolls to a bookmark anchor within the note after navigation. */
bookmark?: string;
}
interface CreateLinkOptions {
@@ -244,7 +246,7 @@ export function parseNavigationStateFromUrl(url: string | undefined) {
hoistedNoteId = value;
} else if (name === "searchString") {
searchString = value; // supports triggering search from URL, e.g. #?searchString=blabla
} else if (["viewMode", "attachmentId"].includes(name)) {
} else if (["viewMode", "attachmentId", "bookmark"].includes(name)) {
(viewScope as any)[name] = value;
} else if (name === "popup") {
openInPopup = true;
@@ -432,6 +434,13 @@ async function loadReferenceLinkTitle($el: JQuery<HTMLElement>, href: string | n
const title = await getReferenceLinkTitle(href);
$el.text(title);
if (viewScope?.bookmark) {
$el.append($("<small>").append(
$("<span>").addClass("bx bx-bookmark"),
document.createTextNode(viewScope.bookmark)
));
}
if (note) {
const icon = await getLinkIcon(noteId, viewScope.viewMode);
@@ -457,8 +466,8 @@ async function getReferenceLinkTitle(href: string) {
return attachment ? attachment.title : "[missing attachment]";
}
return note.title;
return note.title;
}
function getReferenceLinkTitleSync(href: string) {
@@ -481,8 +490,12 @@ function getReferenceLinkTitleSync(href: string) {
return attachment ? attachment.title : "[missing attachment]";
}
return note.title;
if (viewScope?.bookmark) {
return `${note.title} - ${viewScope.bookmark}`;
}
return note.title;
}
if (glob.device !== "print") {

View File

@@ -714,6 +714,15 @@ html .note-detail-editable-text :not(figure, .include-note, hr):first-child {
text-decoration: underline;
}
.ck-content a.reference-link small {
margin-left: 0.25em;
opacity: 0.5;
>span {
font-size: 0.7em;
}
}
/*
* Read-only text content
*/

View File

@@ -41,6 +41,8 @@
"link_title_mirrors": "link title mirrors the note's current title",
"link_title_arbitrary": "link title can be changed arbitrarily",
"link_title": "Link title",
"anchor": "Anchor (optional)",
"anchor_none": "None (link to note)",
"button_add_link": "Add link"
},
"branch_prefix": {
@@ -860,6 +862,9 @@
"no_inherited_attributes": "No inherited attributes.",
"none": "none"
},
"auto_link_attribute_list": {
"title": "System Attributes"
},
"note_info_widget": {
"note_id": "Note ID",
"created": "Created",

View File

@@ -615,7 +615,8 @@
"collections": "コレクション",
"ai-chat": "AI チャット",
"spreadsheet": "スプレッドシート",
"llm-chat": "AI チャット"
"llm-chat": "AI チャット",
"markdown": "Markdown"
},
"edited_notes": {
"no_edited_notes_found": "この日の編集されたノートはまだありません...",
@@ -2479,5 +2480,10 @@
},
"launcher_button_context_menu": {
"remove_from_launch_bar": "ランチャーバーから削除"
},
"display_mode": {
"source": "ソースビュー",
"split": "分割ビュー",
"preview": "プレビュー"
}
}

View File

@@ -89,13 +89,21 @@
},
"delete_notes": {
"delete_all_clones_description": "同時刪除所有克隆(可以在最近修改中撤消)",
"erase_notes_description": "通常(軟)刪除僅標記筆記為已刪除,可以在一段時間內透過最近修改對話方塊撤消。勾選此選項將立即擦除筆記,無法撤銷。",
"erase_notes_description": "立即刪除筆記,而非執行軟刪除。此操作無法撤銷,且會強制重新載入應用程式。",
"erase_notes_warning": "永久擦除筆記(無法撤銷),包括所有克隆。這將強制應用程式重新載入。",
"notes_to_be_deleted": "刪除以下筆記 ({{notesCount}})",
"notes_to_be_deleted": "刪除筆記 ({{notesCount}})",
"no_note_to_delete": "沒有筆記將被刪除(僅克隆)。",
"broken_relations_to_be_deleted": "將刪除以下關聯並斷開連接 ({{ relationCount}})",
"broken_relations_to_be_deleted": "斷開的關聯 ({{ relationCount}})",
"cancel": "取消",
"close": "關閉"
"close": "關閉",
"title": "刪除筆記",
"clones_label": "克隆",
"delete_clones_description_one": "同時刪除 {{count}} 個其他克隆。此操作可在最近修改中撤銷。",
"erase_notes_label": "永久擦除",
"table_note_with_relation": "有關聯的筆記",
"table_relation": "關聯",
"table_points_to": "指向 (已刪除)",
"delete": "刪除"
},
"export": {
"export_note_title": "匯出筆記",
@@ -206,7 +214,8 @@
"box_size_small": "小型(顯示大約 10 行)",
"box_size_medium": "中型 (顯示大約30行)",
"box_size_full": "完整顯示(完整文字框)",
"button_include": "內嵌筆記"
"button_include": "內嵌筆記",
"box_size_expandable": "可展開(預設為摺疊狀態)"
},
"info": {
"modalTitle": "資訊消息",
@@ -1430,7 +1439,7 @@
"expand-subtree": "展開子階層",
"collapse-subtree": "收摺子階層",
"sort-by": "排序方式…",
"recent-changes-in-subtree": "子階層中的最近改",
"recent-changes-in-subtree": "子階層中的最近改",
"convert-to-attachment": "轉換為附件",
"copy-note-path-to-clipboard": "複製筆記路徑至剪貼簿",
"protect-subtree": "保護子階層",
@@ -2334,5 +2343,14 @@
"history": "對話歷史",
"recent_chats": "最近的對話",
"no_chats": "無先前的對話記錄"
},
"revisions": {
"note_revisions": "筆記歷史版本",
"delete_all_revisions": "刪除此筆記的所有歷史版本",
"delete_all_button": "刪除所有歷史版本",
"help_title": "關於筆記歷史版本的說明",
"confirm_delete_all": "您要刪除此筆記的所有歷史版本嗎?",
"no_revisions": "尚無此筆記的歷史版本...",
"restore_button": "還原"
}
}

View File

@@ -269,6 +269,8 @@ declare namespace Fancytree {
lazy: boolean;
/** Alternative description used as hover banner */
tooltip: string;
/** `<li>` element wrapping this node. `null` if the node has not been rendered yet. */
li: HTMLLIElement | null;
/** Outer element of single nodes */
span: HTMLElement;
/** Outer element of single nodes for table extension */

View File

@@ -5,6 +5,7 @@ import FormRadioGroup from "../react/FormRadioGroup";
import NoteAutocomplete from "../react/NoteAutocomplete";
import { useRef, useState, useEffect } from "preact/hooks";
import tree from "../../services/tree";
import froca from "../../services/froca";
import note_autocomplete, { Suggestion } from "../../services/note_autocomplete";
import { logError } from "../../services/ws";
import FormGroup from "../react/FormGroup.js";
@@ -24,6 +25,9 @@ export default function AddLinkDialog() {
const [ linkTitle, setLinkTitle ] = useState("");
const [ linkType, setLinkType ] = useState<LinkType>();
const [ suggestion, setSuggestion ] = useState<Suggestion | null>(null);
const [ bookmarks, setBookmarks ] = useState<string[]>([]);
const [ selectedBookmark, setSelectedBookmark ] = useState("");
const [ noteTitle, setNoteTitle ] = useState("");
const [ shown, setShown ] = useState(false);
const hasSubmittedRef = useRef(false);
@@ -41,26 +45,34 @@ export default function AddLinkDialog() {
}, [ opts ]);
async function setDefaultLinkTitle(noteId: string) {
const noteTitle = await tree.getNoteTitle(noteId);
setLinkTitle(noteTitle);
}
function resetExternalLink() {
if (linkType === "external-link") {
setLinkType("reference-link");
}
const title = await tree.getNoteTitle(noteId);
setNoteTitle(title);
setLinkTitle(title);
}
useEffect(() => {
const resetExternalLink = () =>
setLinkType((prev) => prev === "external-link" ? "reference-link" : prev);
if (!suggestion) {
resetExternalLink();
setBookmarks([]);
setSelectedBookmark("");
return;
}
let cancelled = false;
if (suggestion.notePath) {
const noteId = tree.getNoteIdFromUrl(suggestion.notePath);
if (noteId) {
setDefaultLinkTitle(noteId);
froca.getNote(noteId).then((note) => {
if (cancelled) return;
const bkms = note?.getLabels("internalBookmark").map((l) => l.value) ?? [];
setBookmarks(bkms);
setSelectedBookmark("");
});
}
resetExternalLink();
}
@@ -69,8 +81,18 @@ export default function AddLinkDialog() {
setLinkTitle(suggestion.externalLink);
setLinkType("external-link");
}
return () => { cancelled = true; };
}, [suggestion]);
useEffect(() => {
if (selectedBookmark) {
setLinkTitle(`${noteTitle} - ${selectedBookmark}`);
} else {
setLinkTitle(noteTitle);
}
}, [selectedBookmark, noteTitle]);
function onShown() {
const $autocompleteEl = refToJQuerySelector(autocompleteRef);
if (!opts?.text) {
@@ -114,8 +136,11 @@ export default function AddLinkDialog() {
hasSubmittedRef.current = false;
if (suggestion.notePath) {
// Handle note link
opts.addLink(suggestion.notePath, linkType === "reference-link" ? null : linkTitle);
// Handle note link, optionally with a bookmark anchor
const path = selectedBookmark
? `${suggestion.notePath}?bookmark=${encodeURIComponent(selectedBookmark)}`
: suggestion.notePath;
opts.addLink(path, linkType === "reference-link" ? null : linkTitle);
} else if (suggestion.externalLink) {
// Handle external link
opts.addLink(suggestion.externalLink, linkTitle, true);
@@ -123,6 +148,9 @@ export default function AddLinkDialog() {
}
setSuggestion(null);
setBookmarks([]);
setSelectedBookmark("");
setNoteTitle("");
setShown(false);
}}
show={shown}
@@ -138,6 +166,21 @@ export default function AddLinkDialog() {
/>
</FormGroup>
{bookmarks.length > 0 && (
<FormGroup label={t("add_link.anchor")} name="anchor">
<select
className="form-select"
value={selectedBookmark}
onChange={(e) => setSelectedBookmark((e.target as HTMLSelectElement).value)}
>
<option value="">{t("add_link.anchor_none")}</option>
{bookmarks.map((bk) => (
<option key={bk} value={bk}>{bk}</option>
))}
</select>
</FormGroup>
)}
{!opts?.hasSelection && (
<div className="add-link-title-settings">
{(linkType !== "external-link") && (

View File

@@ -3,13 +3,14 @@ import { t } from "../../services/i18n";
import FormGroup from "../react/FormGroup";
import NoteAutocomplete from "../react/NoteAutocomplete";
import FormList, { FormListHeader, FormListItem } from "../react/FormList";
import { useEffect, useState } from "preact/hooks";
import { useEffect, useRef, useState } from "preact/hooks";
import note_types from "../../services/note_types";
import { MenuCommandItem, MenuItem } from "../../menus/context_menu";
import { TreeCommandNames } from "../../menus/tree_context_menu";
import { Suggestion } from "../../services/note_autocomplete";
import SimpleBadge from "../react/Badge";
import { useTriliumEvent } from "../react/hooks";
import { refToJQuerySelector } from "../react/react_utils";
export interface ChooseNoteTypeResponse {
success: boolean;
@@ -30,6 +31,8 @@ export default function NoteTypeChooserDialogComponent() {
const [ shown, setShown ] = useState(false);
const [ parentNote, setParentNote ] = useState<Suggestion | null>();
const [ noteTypes, setNoteTypes ] = useState<MenuItem<TreeCommandNames>[]>([]);
const modalRef = useRef<HTMLDivElement>(null);
const autocompleteRef = useRef<HTMLInputElement>(null);
useTriliumEvent("chooseNoteType", ({ callback }) => {
setCallback(() => callback);
@@ -68,11 +71,17 @@ export default function NoteTypeChooserDialogComponent() {
return (
<Modal
modalRef={modalRef}
title={t("note_type_chooser.modal_title")}
className="note-type-chooser-dialog"
size="md"
zIndex={1100} // note type chooser needs to be higher than other dialogs from which it is triggered, e.g. "add link"
scrollable
onShown={() => {
refToJQuerySelector(autocompleteRef)
.trigger("focus")
.trigger("select");
}}
onHidden={() => {
callback?.({ success: false });
setShown(false);
@@ -82,6 +91,7 @@ export default function NoteTypeChooserDialogComponent() {
>
<FormGroup name="parent-note" label={t("note_type_chooser.change_path_prompt")}>
<NoteAutocomplete
inputRef={autocompleteRef}
onChange={setParentNote}
placeholder={t("note_type_chooser.search_placeholder")}
opts={{

View File

@@ -87,7 +87,8 @@
font-weight: 600;
}
.inherited-attributes-widget {
.inherited-attributes-widget,
.auto-link-attributes-widget {
display: inline;
> div {

View File

@@ -26,6 +26,7 @@ import LinkButton from "../react/LinkButton";
import { ParentComponent } from "../react/react_utils";
import { ContentLanguagesModal, NoteTypeCodeNoteList, NoteTypeOptionsModal, useLanguageSwitcher, useMimeTypes } from "../ribbon/BasicPropertiesTab";
import AttributeEditor, { AttributeEditorImperativeHandlers } from "../ribbon/components/AttributeEditor";
import AutoLinkAttributesTab from "../ribbon/AutoLinkAttributesTab";
import InheritedAttributesTab from "../ribbon/InheritedAttributesTab";
import { NoteSizeWidget, useNoteMetadata } from "../ribbon/NoteInfoTab";
import { NotePathsWidget, useSortedNotePaths } from "../ribbon/NotePathsTab";
@@ -401,6 +402,11 @@ function AttributesPane({ note, noteContext, attributesShown, setAttributesShown
<span class="attributes-panel-label">{t("inherited_attribute_list.title")}</span>
<InheritedAttributesTab {...context} emptyListString="inherited_attribute_list.none" />
{glob.isDev && <div>
<span class="attributes-panel-label">{t("auto_link_attribute_list.title")}</span>
<AutoLinkAttributesTab {...context} />
</div>}
<AttributeEditor
{...context}
api={api}

View File

@@ -1534,8 +1534,11 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
const hoistedNotePath = await treeService.resolveNotePath(this.noteContext.hoistedNoteId);
if (!forceUpdate && this.lastFilteredHoistedNotePath === hoistedNotePath) {
// no need to re-filter if the hoisting did not change
// (helps with flickering on simple note change with large subtrees)
// Hoisting did not change, so skip the expensive re-filter (avoids flickering on
// simple note changes with large subtrees). The hidden-node class must still be
// reapplied — the <li> may have been recreated by a lazy reload (e.g. via
// `getNodeFromPath` → `parentNode.load(true)` after an import into root).
this.toggleHiddenNode(this.noteContext.hoistedNoteId !== "root");
return;
}
@@ -1568,8 +1571,9 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
toggleHiddenNode(show: boolean) {
const hiddenNode = this.getNodesByNoteId("_hidden")[0];
// TODO: Check how .li exists here.
$((hiddenNode as any).li).toggleClass("hidden-node-is-hidden", !show);
if (hiddenNode?.li) {
$(hiddenNode.li).toggleClass("hidden-node-is-hidden", !show);
}
}
async frocaReloadedEvent() {

View File

@@ -0,0 +1,69 @@
import { useEffect, useState } from "preact/hooks";
import FAttribute from "../../entities/fattribute";
import attributes from "../../services/attributes";
import froca from "../../services/froca";
import { useTriliumEvent } from "../react/hooks";
import { joinElements } from "../react/react_utils";
import { TabContext } from "./ribbon-interface";
type AutoLinkAttributesTabArgs = Pick<TabContext, "note" | "componentId">;
export default function AutoLinkAttributesTab({ note, componentId }: AutoLinkAttributesTabArgs) {
const [autoLinkAttributes, setAutoLinkAttributes] = useState<FAttribute[]>();
function refresh() {
if (!note) return;
const attrs = note.getAttributes().filter((attr) => attr.isAutoLink && attr.noteId === note.noteId);
setAutoLinkAttributes(attrs);
}
useEffect(refresh, [note]);
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
if (loadResults.getAttributeRows(componentId).find((attr) => attributes.isAffecting(attr, note))) {
refresh();
}
});
if (!autoLinkAttributes?.length) {
return null;
}
return (
<div className="auto-link-attributes-widget">
<div className="auto-link-attributes-container selectable-text">
{joinElements(autoLinkAttributes.map((attribute) => (
<AutoLinkAttribute key={attribute.attributeId} attribute={attribute} />
)), " ")}
</div>
</div>
);
}
function AutoLinkAttribute({ attribute }: { attribute: FAttribute }) {
const [html, setHtml] = useState<string>("");
useEffect(() => {
renderAutoLink(attribute).then(setHtml);
}, [attribute]);
return <span dangerouslySetInnerHTML={{ __html: html }} />;
}
async function renderAutoLink(attribute: FAttribute) {
if (attribute.type === "label") {
return `#${escapeHtml(attribute.name)}=${escapeHtml(attribute.value)}`;
}
const note = await froca.getNote(attribute.value);
if (!note) return "";
const link = `<a href="#root/${attribute.value}" class="reference-link">${escapeHtml(note.title)}</a>`;
return `~${escapeHtml(attribute.name)}=${link}`;
}
function escapeHtml(text: string) {
const el = document.createElement("span");
el.textContent = text;
return el.innerHTML;
}

View File

@@ -61,6 +61,17 @@ export default function EditableText({ note, parentComponent, ntxId, noteContext
onContentChange(newContent) {
contentRef.current = newContent;
watchdogRef.current?.editor?.setData(newContent);
// Scroll to bookmark anchor if navigated with ?bookmark=...
const viewScope = noteContext?.viewScope;
if (viewScope?.bookmark) {
requestAnimationFrame(() => {
const el = watchdogRef.current?.editor?.editing.view.getDomRoot()
?.querySelector(`[id="${CSS.escape(viewScope.bookmark!)}"]`);
el?.scrollIntoView({ behavior: "smooth", block: "center" });
viewScope.bookmark = undefined;
});
}
},
dataSaved(savedData) {
// Store back the saved data in order to retrieve it in case the CKEditor crashes.
@@ -263,6 +274,7 @@ export default function EditableText({ note, parentComponent, ntxId, noteContext
// We are not using CKEditor's built-in watch dog content, instead we are using the data we store regularly in the spaced update (see `dataSaved`).
editor.setData(contentRef.current);
parentComponent?.triggerEvent("textEditorRefreshed", { ntxId, editor });
}}
/>}

View File

@@ -6,7 +6,7 @@ import "@triliumnext/ckeditor5";
import clsx from "clsx";
import { Ref } from "preact";
import { useEffect, useLayoutEffect, useMemo } from "preact/hooks";
import { useEffect, useLayoutEffect, useMemo, useRef as usePreactRef } from "preact/hooks";
import appContext from "../../../components/app_context";
import FNote from "../../../entities/fnote";
@@ -24,6 +24,17 @@ import { loadIncludedNote, refreshIncludedNote, setupImageOpening } from "./util
export default function ReadOnlyText({ note, noteContext, ntxId }: TypeWidgetProps) {
const blob = useNoteBlob(note);
const { isRtl } = useNoteLanguage(note);
const readOnlyContentRef = usePreactRef<HTMLDivElement>(null);
// Scroll to bookmark anchor if navigated with ?bookmark=...
useEffect(() => {
const viewScope = noteContext?.viewScope;
if (!viewScope?.bookmark || !readOnlyContentRef.current) return;
const el = readOnlyContentRef.current.querySelector(`[id="${CSS.escape(viewScope.bookmark)}"]`);
el?.scrollIntoView({ behavior: "smooth", block: "center" });
viewScope.bookmark = undefined;
}, [blob]);
return (
<>
@@ -31,6 +42,7 @@ export default function ReadOnlyText({ note, noteContext, ntxId }: TypeWidgetPro
html={blob?.content ?? ""}
ntxId={ntxId}
dir={isRtl ? "rtl" : "ltr"}
contentRef={readOnlyContentRef}
/>
<TouchBar>

View File

@@ -133,6 +133,15 @@ export async function buildConfig(opts: BuildEditorOptions): Promise<EditorConfi
defaultProtocol: "https://",
allowedProtocols: ALLOWED_PROTOCOLS
},
bookmark: {
toolbar: [
"bookmarkPreview",
"|",
"editBookmark",
"copyAnchorLink",
"removeBookmark"
]
},
emoji: {
definitionsUrl: window.glob.isDev
? new URL(import.meta.url).origin + emojiDefinitionsUrl

View File

@@ -6,7 +6,7 @@ WHERE powershell.exe > NUL 2>&1
IF %ERRORLEVEL% NEQ 0 GOTO BATCH ELSE GOTO POWERSHELL
:POWERSHELL
powershell -ExecutionPolicy Bypass -NonInteractive -NoLogo -Command "Set-Item -Path Env:TRILIUM_DATA_DIR -Value './trilium-data'; ./trilium.exe"
powershell -ExecutionPolicy Bypass -NonInteractive -NoLogo -Command "Set-Item -Path Env:TRILIUM_DATA_DIR -Value './trilium-data'; Set-Item -Path Env:TRILIUM_ELECTRON_DATA_DIR -Value './trilium-electron-data'; Set-Item -Path Env:ELECTRON_NO_ATTACH_CONSOLE -Value '1'; ./trilium.exe"
GOTO END
:BATCH
@@ -17,6 +17,8 @@ chcp 65001
SET DIR=%~dp0
SET DIR=%DIR:~0,-1%
SET TRILIUM_DATA_DIR=%DIR%\trilium-data
SET TRILIUM_ELECTRON_DATA_DIR=%DIR%\trilium-electron-data
SET ELECTRON_NO_ATTACH_CONSOLE=1
cd "%DIR%"
start trilium.exe
GOTO END

View File

@@ -2,6 +2,7 @@
DIR=`dirname "$0"`
export TRILIUM_DATA_DIR="$DIR/trilium-data"
export TRILIUM_ELECTRON_DATA_DIR="$DIR/trilium-electron-data"
exec "$DIR/trilium"

View File

@@ -11,16 +11,16 @@
"url": "https://triliumnotes.org"
},
"scripts": {
"dev": "cross-env TRILIUM_PORT=37742 TRILIUM_DATA_DIR=data tsx ../../scripts/electron-start.mts src/main.ts",
"dev": "cross-env TRILIUM_PORT=37742 TRILIUM_DATA_DIR=data TRILIUM_ELECTRON_DATA_DIR=data-electron-37742 tsx ../../scripts/electron-start.mts src/main.ts",
"start-no-dir": "cross-env TRILIUM_PORT=37743 tsx ../../scripts/electron-start.mts src/main.ts",
"build": "tsx scripts/build.ts",
"start-prod": "pnpm build && cross-env TRILIUM_DATA_DIR=data TRILIUM_PORT=37841 ELECTRON_IS_DEV=0 electron dist",
"start-prod": "pnpm build && cross-env TRILIUM_DATA_DIR=data TRILIUM_ELECTRON_DATA_DIR=data-electron-37841 TRILIUM_PORT=37841 ELECTRON_IS_DEV=0 electron dist",
"start-prod-no-dir": "pnpm build && cross-env TRILIUM_PORT=37841 ELECTRON_IS_DEV=0 electron dist",
"electron-forge:make": "pnpm build && electron-forge make dist",
"electron-forge:make-flatpak": "pnpm build && DEBUG=* electron-forge make dist --targets=@electron-forge/maker-flatpak",
"electron-forge:package": "pnpm build && electron-forge package dist",
"electron-forge:start": "pnpm build && electron-forge start dist",
"e2e": "pnpm build && cross-env TRILIUM_INTEGRATION_TEST=memory-no-store TRILIUM_PORT=8082 TRILIUM_DATA_DIR=data-e2e ELECTRON_IS_DEV=0 playwright test"
"e2e": "pnpm build && cross-env TRILIUM_INTEGRATION_TEST=memory-no-store TRILIUM_PORT=8082 TRILIUM_DATA_DIR=data-e2e TRILIUM_ELECTRON_DATA_DIR=data-e2e-electron-8082 ELECTRON_IS_DEV=0 playwright test"
},
"dependencies": {
"@electron/remote": "2.1.3",

View File

@@ -0,0 +1,101 @@
#!/usr/bin/env bash
#
# Build an AppImage from the packaged Electron app.
#
# Usage: ./build-appimage.sh [arch]
# arch: x64 or arm64 (default: x64)
#
# Prerequisites:
# - The Electron app must already be packaged via `electron-forge make` or `electron-forge package`
# - appimagetool must be available in PATH
#
# Environment variables:
# TRILIUM_ARTIFACT_NAME_HINT: If set, used as the base name for the output file
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
DESKTOP_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
FORGE_DIR="$DESKTOP_DIR/electron-forge"
ARCH="${1:-x64}"
EXECUTABLE_NAME="trilium"
PRODUCT_NAME="Trilium Notes"
# Map architecture names
case "$ARCH" in
x64) APPIMAGE_ARCH="x86_64" ;;
arm64) APPIMAGE_ARCH="aarch64" ;;
*) echo "Unsupported architecture: $ARCH"; exit 1 ;;
esac
# Find the packaged app directory
PACKAGED_DIR="$DESKTOP_DIR/dist/out/$PRODUCT_NAME-linux-$ARCH"
if [ ! -d "$PACKAGED_DIR" ]; then
echo "Error: Packaged app not found at $PACKAGED_DIR"
echo "Run 'electron-forge make' or 'electron-forge package' first."
exit 1
fi
echo "Building AppImage from: $PACKAGED_DIR"
# Create AppDir structure
APPDIR="$DESKTOP_DIR/dist/out/$PRODUCT_NAME.AppDir"
rm -rf "$APPDIR"
mkdir -p "$APPDIR"
# Copy the packaged app contents into the AppDir
cp -a "$PACKAGED_DIR"/. "$APPDIR/"
# Create the AppRun entry point
cat > "$APPDIR/AppRun" << 'APPRUN_EOF'
#!/bin/bash
HERE="$(dirname "$(readlink -f "$0")")"
exec "$HERE/trilium" "$@"
APPRUN_EOF
chmod +x "$APPDIR/AppRun"
# Create the .desktop file
cat > "$APPDIR/$EXECUTABLE_NAME.desktop" << DESKTOP_EOF
[Desktop Entry]
Name=$PRODUCT_NAME
Comment=Build your personal knowledge base with Trilium Notes
GenericName=Note Taking Application
Exec=$EXECUTABLE_NAME %U
Icon=$EXECUTABLE_NAME
Type=Application
StartupNotify=true
StartupWMClass=$PRODUCT_NAME
Categories=Office;Utility;
DESKTOP_EOF
# Copy the icon (AppImage expects it at the root of AppDir)
if [ -f "$FORGE_DIR/app-icon/png/256x256.png" ]; then
cp "$FORGE_DIR/app-icon/png/256x256.png" "$APPDIR/$EXECUTABLE_NAME.png"
elif [ -f "$APPDIR/icon.png" ]; then
cp "$APPDIR/icon.png" "$APPDIR/$EXECUTABLE_NAME.png"
else
echo "Warning: No icon found"
fi
# Determine output filename
UPLOAD_DIR="$DESKTOP_DIR/upload"
mkdir -p "$UPLOAD_DIR"
if [ -n "${TRILIUM_ARTIFACT_NAME_HINT:-}" ]; then
OUTPUT_NAME="${TRILIUM_ARTIFACT_NAME_HINT//\//-}.AppImage"
else
VERSION=$(node -e "console.log(require('$DESKTOP_DIR/package.json').version)")
OUTPUT_NAME="TriliumNotes-v${VERSION}-linux-${ARCH}.AppImage"
fi
OUTPUT_PATH="$UPLOAD_DIR/$OUTPUT_NAME"
# Build the AppImage
echo "Creating AppImage: $OUTPUT_PATH"
ARCH="$APPIMAGE_ARCH" appimagetool "$APPDIR" "$OUTPUT_PATH"
# Clean up the AppDir
rm -rf "$APPDIR"
echo "AppImage created successfully: $OUTPUT_PATH"

View File

@@ -10,7 +10,7 @@ import electronDebug from "electron-debug";
import electronDl from "electron-dl";
import { PRODUCT_NAME } from "./app-info";
import port from "@triliumnext/server/src/services/port.js";
import { join } from "path";
import { join, resolve } from "path";
import { deferred, LOCALES } from "../../../packages/commons/src";
async function main() {
@@ -101,10 +101,16 @@ async function main() {
/**
* Returns a unique user data directory for Electron so that single instance locks between legitimately different instances such as different port or data directory can still act independently, but we are focusing the main window otherwise.
*
* When running in portable mode, set TRILIUM_ELECTRON_DATA_DIR (e.g. via the trilium-portable script)
* so that no Electron files are written to the system's roaming profile (e.g. %APPDATA% on Windows).
*/
function getUserData() {
const name = `${app.getName()}-${port}`;
return join(app.getPath("appData"), name);
if (process.env.TRILIUM_ELECTRON_DATA_DIR) {
return resolve(process.env.TRILIUM_ELECTRON_DATA_DIR);
}
return join(app.getPath("appData"), `${app.getName()}-${port}`);
}
async function onReady() {

View File

@@ -32,9 +32,9 @@
"dependencies": {
"@ai-sdk/anthropic": "3.0.69",
"@ai-sdk/google": "3.0.63",
"@ai-sdk/openai": "3.0.52",
"@ai-sdk/openai": "3.0.53",
"@modelcontextprotocol/sdk": "^1.12.1",
"ai": "6.0.159",
"ai": "6.0.161",
"better-sqlite3": "12.9.0",
"html-to-text": "9.0.5",
"js-yaml": "4.1.1",

File diff suppressed because one or more lines are too long

View File

@@ -165,10 +165,16 @@ class="admonition note">
class="reference-link" href="#root/_help_KC1HB96bqqHX">Templates</a>.</li>
</ul>
<p>For example, to change the font of the document from the one defined by
the theme or the user to a serif one:</p><pre><code class="language-text-x-trilium-auto">body {
--main-font-family: serif !important;
--detail-font-family: var(--main-font-family) !important;
the theme or the user to a serif one:</p><pre><code class="language-text-x-trilium-auto">body{
--print-font-family: serif;
--print-font-size: 11pt;
}</code></pre>
<aside class="admonition important">
<p>When altering <code spellcheck="false">--print-font-family</code>, make
sure the change is done at <code spellcheck="false">body</code> level and
not <code spellcheck="false">:root</code>, since otherwise it won't be picked
up due to specificity rules.</p>
</aside>
<p>To remark:</p>
<ul>
<li>Multiple CSS notes can be add by using multiple <code spellcheck="false">~printCss</code> relations.</li>

View File

@@ -1,8 +1,8 @@
<p>Split view is a feature of&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_s1aBHPd79XYj">Mermaid Diagrams</a>&nbsp;and&nbsp;
<p>Split view is a feature of&nbsp;<a class="reference-link" href="#root/_help_s1aBHPd79XYj">Mermaid Diagrams</a>&nbsp;and&nbsp;
<a
class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_6RM1Q7ppFVoj">Markdown</a>&nbsp;notes which displays both the source code on one side
class="reference-link" href="#root/_help_6RM1Q7ppFVoj">Markdown</a>&nbsp;notes which displays both the source code on one side
and the preview of the content on the other.</p>
<p><a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_s1aBHPd79XYj">Mermaid Diagrams</a>&nbsp;also
<p><a class="reference-link" href="#root/_help_s1aBHPd79XYj">Mermaid Diagrams</a>&nbsp;also
allow changing between a horizontal or a vertical split, to accommodate
for the various sizes of diagrams.</p>
<h2>Display modes and interaction</h2>
@@ -20,12 +20,12 @@
<li><em>Preview</em> which displays only the rendering of the diagram or text
in full screen, especially useful for read-only notes.</li>
</ul>
<p>These buttons can be found near the&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_8YBEPzcpUgxw">Note buttons</a>&nbsp;section
on the&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_IjZS7iK5EXtb">New Layout</a>,
or in the&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_XpOYSgsLkTJy">Floating buttons</a>&nbsp;on
<p>These buttons can be found near the&nbsp;<a class="reference-link" href="#root/_help_8YBEPzcpUgxw">Note buttons</a>&nbsp;section
on the&nbsp;<a class="reference-link" href="#root/_help_IjZS7iK5EXtb">New Layout</a>,
or in the&nbsp;<a class="reference-link" href="#root/_help_XpOYSgsLkTJy">Floating buttons</a>&nbsp;on
the old layout.</p>
<p>The display node is stored at note level.</p>
<h2>Relation to read-only notes</h2>
<p>If a note is marked as <a href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/BFs8mudNFgCS/_help_CoFPLs3dRlXc">read-only</a>,
the source view will not be editable. While in preview mode, marking a
note as read-only has no effect since the preview itself is not editable.</p>
<p>If a note is marked as <a href="#root/_help_CoFPLs3dRlXc">read-only</a>, the
source view will not be editable. While in preview mode, marking a note
as read-only has no effect since the preview itself is not editable.</p>

View File

@@ -70,6 +70,19 @@
this:</p><pre><code class="language-text-x-trilium-auto">TRILIUM_DATA_DIR=/home/myuser/data/my-trilium-data trilium</code></pre>
<p>You can then save the above command as a shell script on your path for
convenience.</p>
<h2>Electron user data directory (desktop only)</h2>
<p>When running the desktop application, Electron stores internal data (caches,
spell-check dictionaries, session storage, etc.) separately from the Trilium
data directory. By default this goes to the system's application data folder
(e.g. <code spellcheck="false">%APPDATA%</code> on Windows), which may be
undesirable in corporate environments with roaming profiles or when running
in portable mode.</p>
<p>To keep Electron data out of the system's roaming profile, set the
<code
spellcheck="false">TRILIUM_ELECTRON_DATA_DIR</code>environment variable to an explicit path.
The <code spellcheck="false">trilium-portable</code> script does this automatically,
pointing it to <code spellcheck="false">trilium-electron-data/</code> next
to the application.</p>
<h2>Fine-grained directory/path location</h2>
<p>Apart from the data directory, some of the subdirectories of it can be
moved elsewhere by changing an environment variable:</p>
@@ -129,5 +142,13 @@
</td>
<td>Path to&nbsp;<a class="reference-link" href="#root/_help_Gzjqa934BdH4">Configuration (config.ini or environment variables)</a>&nbsp;file.</td>
</tr>
<tr>
<td><code spellcheck="false">TRILIUM_ELECTRON_DATA_DIR</code>
</td>
<td>System appData</td>
<td>Directory for Electron internal data (caches, spell-check dictionaries,
etc.). Set this in portable mode to avoid writing to the system profile
(desktop only).</td>
</tr>
</tbody>
</table>

View File

@@ -23,7 +23,9 @@
<li><code spellcheck="false">trilium-portable</code>: Launches Trilium in
portable mode, where the <a href="#root/_help_tAassRL4RSQL">data directory</a> is
created within the application's directory, making it easy to move the
entire setup.</li>
entire setup. Electron's internal data (caches, dictionaries, etc.) is
also stored within the data directory, so no files are written to the system's
roaming profile.</li>
<li><code spellcheck="false">trilium-safe-mode</code>: Boots Trilium in "safe
mode," disabling any startup scripts that might cause the application to
crash.</li>

View File

@@ -9,8 +9,7 @@
note where to place the new one and select:</p>
<ul>
<li><em>Insert note after</em>, to put the new note underneath the one selected.</li>
<li
><em>Insert child note</em>, to insert the note as a child of the selected
<li><em>Insert child note</em>, to insert the note as a child of the selected
note.</li>
</ul>
<p>
@@ -21,8 +20,7 @@
<li>When adding a <a href="#root/_help_QEAPj01N5f7w">link</a> in a&nbsp;<a class="reference-link"
href="#root/_help_iPIMuisry3hd">Text</a>&nbsp;note, type the desired title of
the new note and press Enter. Afterwards the type of the note will be asked.</li>
<li
>Similarly, when creating a new tab, type the desired title and press Enter.</li>
<li>Similarly, when creating a new tab, type the desired title and press Enter.</li>
</ul>
<h2>Changing the type of a note</h2>
<p>It is possible to change the type of a note after it has been created
@@ -32,96 +30,94 @@
edit the <a href="#root/_help_4FahAwuGTAwC">source of a note</a>.</p>
<h2>Supported note types</h2>
<p>The following note types are supported by Trilium:</p>
<figure class="table">
<table>
<thead>
<tr>
<th>Note Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><a class="reference-link" href="#root/_help_iPIMuisry3hd">Text</a>
</td>
<td>The default note type, which allows for rich text formatting, images,
admonitions and right-to-left support.</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_6f9hih2hXXZk">Code</a>
</td>
<td>Uses a mono-space font and can be used to store larger chunks of code
or plain text than a text note, and has better syntax highlighting.</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_m523cpzocqaD">Saved Search</a>
</td>
<td>Stores the information about a search (the search text, criteria, etc.)
for later use. Can be used for quick filtering of a large amount of notes,
for example. The search can easily be triggered.</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_iRwzGnHPzonm">Relation Map</a>
</td>
<td>Allows easy creation of notes and relations between them. Can be used
for mainly relational data such as a family tree.</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_bdUJEHsAPYQR">Note Map</a>
</td>
<td>Displays the relationships between the notes, whether via relations or
their hierarchical structure.</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_HcABDtFCkbFN">Render Note</a>
</td>
<td>Used in&nbsp;<a class="reference-link" href="#root/_help_CdNpE2pqjmI6">Scripting</a>,
it displays the HTML content of another note. This allows displaying any
kind of content, provided there is a script behind it to generate it.</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_GTwFsgaA0lCt">Collections</a>
</td>
<td>Displays the children of the note either as a grid, a list, or for a more
specialized case: a calendar.&nbsp;&nbsp;
<br>
<br>Generally useful for easy reading of short notes.</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_s1aBHPd79XYj">Mermaid Diagrams</a>
</td>
<td>Displays diagrams such as bar charts, flow charts, state diagrams, etc.
Requires a bit of technical knowledge since the diagrams are written in
a specialized format.</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_grjYqerjn243">Canvas</a>
</td>
<td>Allows easy drawing of sketches, diagrams, handwritten content. Uses the
same technology behind <a href="https://excalidraw.com">excalidraw.com</a>.</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_1vHRoWCEjj0L">Web View</a>
</td>
<td>Displays the content of an external web page, similar to a browser.</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_gBbsAeiuUxI5">Mind Map</a>
</td>
<td>Easy for brainstorming ideas, by placing them in a hierarchical layout.</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_81SGnPGMk7Xc">Geo Map</a>
</td>
<td>Displays the children of the note as a geographical map, one use-case
would be to plan vacations. It even has basic support for tracks. Notes
can also be created from it.</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_W8vYD3Q1zjCR">File</a>
</td>
<td>Represents an uploaded file such as PDFs, images, video or audio files.</td>
</tr>
</tbody>
</table>
</figure>
<table>
<thead>
<tr>
<th>Note Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><a class="reference-link" href="#root/_help_iPIMuisry3hd">Text</a>
</td>
<td>The default note type, which allows for rich text formatting, images,
admonitions and right-to-left support.</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_6f9hih2hXXZk">Code</a>
</td>
<td>Uses a mono-space font and can be used to store larger chunks of code
or plain text than a text note, and has better syntax highlighting.</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_m523cpzocqaD">Saved Search</a>
</td>
<td>Stores the information about a search (the search text, criteria, etc.)
for later use. Can be used for quick filtering of a large amount of notes,
for example. The search can easily be triggered.</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_iRwzGnHPzonm">Relation Map</a>
</td>
<td>Allows easy creation of notes and relations between them. Can be used
for mainly relational data such as a family tree.</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_bdUJEHsAPYQR">Note Map</a>
</td>
<td>Displays the relationships between the notes, whether via relations or
their hierarchical structure.</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_HcABDtFCkbFN">Render Note</a>
</td>
<td>Used in&nbsp;<a class="reference-link" href="#root/_help_CdNpE2pqjmI6">Scripting</a>,
it displays the HTML content of another note. This allows displaying any
kind of content, provided there is a script behind it to generate it.</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_GTwFsgaA0lCt">Collections</a>
</td>
<td>Displays the children of the note either as a grid, a list, or for a more
specialized case: a calendar.&nbsp;&nbsp;
<br>
<br>Generally useful for easy reading of short notes.</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_s1aBHPd79XYj">Mermaid Diagrams</a>
</td>
<td>Displays diagrams such as bar charts, flow charts, state diagrams, etc.
Requires a bit of technical knowledge since the diagrams are written in
a specialized format.</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_grjYqerjn243">Canvas</a>
</td>
<td>Allows easy drawing of sketches, diagrams, handwritten content. Uses the
same technology behind <a href="https://excalidraw.com">excalidraw.com</a>.</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_1vHRoWCEjj0L">Web View</a>
</td>
<td>Displays the content of an external web page, similar to a browser.</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_gBbsAeiuUxI5">Mind Map</a>
</td>
<td>Easy for brainstorming ideas, by placing them in a hierarchical layout.</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_81SGnPGMk7Xc">Geo Map</a>
</td>
<td>Displays the children of the note as a geographical map, one use-case
would be to plan vacations. It even has basic support for tracks. Notes
can also be created from it.</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_W8vYD3Q1zjCR">File</a>
</td>
<td>Represents an uploaded file such as PDFs, images, video or audio files.</td>
</tr>
</tbody>
</table>

View File

@@ -5,8 +5,7 @@
create a <em>File</em> note type directly:</p>
<ul>
<li>Drag a file into the&nbsp;<a class="reference-link" href="#root/_help_oPVyFC7WL2Lp">Note Tree</a>.</li>
<li
>Right click a note and select <em>Import into note</em> and point it to
<li>Right click a note and select <em>Import into note</em> and point it to
one of the supported files.</li>
</ul>
<h2>Supported file types</h2>
@@ -83,30 +82,28 @@
href="#root/_help_BlN9DFI679QC">Ribbon</a>.
<ul>
<li><em>Download</em>, which will download the file for local use.</li>
<li
><em>Open</em>, will will open the file with the system-default application.</li>
<li
>Upload new revision to replace the file with a new one.</li>
<li><em>Open</em>, will will open the file with the system-default application.</li>
<li>Upload new revision to replace the file with a new one.</li>
</ul>
</li>
<li>It is <strong>not</strong> possible to change the note type of a <em>File</em> note.</li>
<li
>Convert into an <a href="#root/_help_0vhv7lsOLy82">attachment</a> from the <a href="#root/_help_8YBEPzcpUgxw">note menu</a>.</li>
</li>
<li>It is <strong>not</strong> possible to change the note type of a <em>File</em> note.</li>
<li>Convert into an <a href="#root/_help_0vhv7lsOLy82">attachment</a> from the <a href="#root/_help_8YBEPzcpUgxw">note menu</a>.</li>
</ul>
<h2>Relation with other notes</h2>
<ul>
<li>
<p>Files are also displayed in the&nbsp;<a class="reference-link" href="#root/_help_0ESUbbAxVnoK">Note List</a>&nbsp;based
on their type:</p>
<p>
<img class="image_resized" style="aspect-ratio:853/315;width:50%;" src="4_File_image.png"
width="853" height="315">
</p>
<img class="image_resized" style="aspect-ratio:853/315;width:50%;"
src="4_File_image.png" width="853" height="315">
</li>
<li>
<p>Non-image files can be embedded into text notes as read-only widgets via
the&nbsp;<a class="reference-link" href="#root/_help_nBAXQFj20hS1">Include Note</a>&nbsp;functionality.</p>
</li>
<li>
<p>Image files can be embedded into text notes like normal images via&nbsp;
<a
class="reference-link" href="#root/_help_0Ofbk1aSuVRu">Image references</a>.</p>
</li>
<li>Non-image files can be embedded into text notes as read-only widgets via
the&nbsp;<a class="reference-link" href="#root/_help_nBAXQFj20hS1">Include Note</a>&nbsp;functionality.</li>
<li
>Image files can be embedded into text notes like normal images via&nbsp;
<a
class="reference-link" href="#root/_help_0Ofbk1aSuVRu">Image references</a>.</li>
</ul>

View File

@@ -1,13 +1,12 @@
<p>Trilium has always supported Markdown through its <a href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/mHbBMPDPkVV5/_help_Oau6X9rCuegd">import feature</a>,
<p>Trilium has always supported Markdown through its <a href="#root/_help_Oau6X9rCuegd">import feature</a>,
however the file was either transformed to a&nbsp;<a class="reference-link"
href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_iPIMuisry3hd">Text</a>&nbsp;note
(converted to Trilium's internal HTML format) or saved as a&nbsp;<a class="reference-link"
href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_6f9hih2hXXZk">Code</a>&nbsp;note
href="#root/_help_iPIMuisry3hd">Text</a>&nbsp;note (converted to Trilium's internal
HTML format) or saved as a&nbsp;<a class="reference-link" href="#root/_help_6f9hih2hXXZk">Code</a>&nbsp;note
with only syntax highlight.</p>
<p>This note type is a split view, meaning that both the source code and
a preview of the document are displayed side-by-side. See&nbsp;<a class="reference-link"
href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_SL5f1Auq7sVN">Note types with split view</a>&nbsp;for
more information.</p>
href="#root/_help_SL5f1Auq7sVN">Note types with split view</a>&nbsp;for more
information.</p>
<h2>Rationale</h2>
<p>The goal of this note type is to fill a gap: rendering Markdown but not
altering its structure or its whitespace which would inevitably change
@@ -37,77 +36,65 @@
<li
>Code blocks with syntax highlight (e.g. <code spellcheck="false">```js</code>)
and automatic syntax highlight</li>
<li><a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/iPIMuisry3hd/_help_NwBbFdNZ9h7O">Block quotes &amp; admonitions</a>
<li><a class="reference-link" href="#root/_help_NwBbFdNZ9h7O">Block quotes &amp; admonitions</a>
</li>
<li><a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/iPIMuisry3hd/_help_YfYAtQBcfo5V">Math Equations</a>
<li><a class="reference-link" href="#root/_help_YfYAtQBcfo5V">Math Equations</a>
</li>
<li><a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_s1aBHPd79XYj">Mermaid Diagrams</a>&nbsp;using
<li><a class="reference-link" href="#root/_help_s1aBHPd79XYj">Mermaid Diagrams</a>&nbsp;using
<code
spellcheck="false">```mermaid</code>
</li>
<li>
<p><a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/iPIMuisry3hd/_help_nBAXQFj20hS1">Include Note</a>&nbsp;(no
<p><a class="reference-link" href="#root/_help_nBAXQFj20hS1">Include Note</a>&nbsp;(no
builtin Markdown syntax, but HTML syntax works just fine):</p><pre><code class="language-text-x-trilium-auto">&lt;section class="include-note" data-note-id="vJDjQm0VK8Na" data-box-size="expandable"&gt;
&amp;nbsp;
&amp;nbsp;
&lt;/section&gt;n</code></pre>
</li>
<li>
<p><a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/iPIMuisry3hd/QEAPj01N5f7w/_help_hrZ1D00cLbal">Internal (reference) links</a>&nbsp;via
<p><a class="reference-link" href="#root/_help_hrZ1D00cLbal">Internal (reference) links</a>&nbsp;via
its HTML syntax, or through a <em>Wikilinks</em>-like format (only&nbsp;
<a
class="reference-link" href="#root/pOsGYCXsbNQG/tC7s2alapj8V/_help_m1lbrzyKDaRB">Note ID</a>):</p><pre><code class="language-text-x-trilium-auto">[[Hg8TS5ZOxti6]]</code></pre>
class="reference-link" href="#root/_help_m1lbrzyKDaRB">Note ID</a>):</p><pre><code class="language-text-x-trilium-auto">[[Hg8TS5ZOxti6]]</code></pre>
</li>
</ul>
<h2>Creating Markdown notes</h2>
<p>There are two ways to create a Markdown note:</p>
<ol>
<li>Create a new note (e.g. in the&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_oPVyFC7WL2Lp">Note Tree</a>)
<li>Create a new note (e.g. in the&nbsp;<a class="reference-link" href="#root/_help_oPVyFC7WL2Lp">Note Tree</a>)
and select the type <em>Markdown</em>, just like all the other note types.</li>
<li
>Create a note of type&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_6f9hih2hXXZk">Code</a>&nbsp;and
select as the language either <em>Markdown </em>or <em>GitHub-Flavored Markdown</em>.
>Create a note of type&nbsp;<a class="reference-link" href="#root/_help_6f9hih2hXXZk">Code</a>&nbsp;and
select as the language either <em>Markdown</em> or <em>GitHub-Flavored Markdown</em>.
This maintains compatibility with your existing notes prior to the introduction
of this feature.</li>
</ol>
<aside class="admonition note">
<p>There is no distinction between the new Markdown note type and code notes
of type Markdown; internally both are represented as&nbsp;<a class="reference-link"
href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_6f9hih2hXXZk">Code</a>&nbsp;notes
with the proper MIME type (e.g. <code spellcheck="false">text/x-markdown</code>).</p>
href="#root/_help_6f9hih2hXXZk">Code</a>&nbsp;notes with the proper MIME type
(e.g. <code spellcheck="false">text/x-markdown</code>).</p>
</aside>
<h2>Import/export</h2>
<ul>
<li>
<p>By default, when importing a single Markdown file it automatically gets
converted to a&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_iPIMuisry3hd">Text</a>&nbsp;note.
To avoid that and have it imported as a Markdown note instead:</p>
<li>By default, when importing a single Markdown file it automatically gets
converted to a&nbsp;<a class="reference-link" href="#root/_help_iPIMuisry3hd">Text</a>&nbsp;note.
To avoid that and have it imported as a Markdown note instead:
<ul>
<li>
<p>Right click the&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_oPVyFC7WL2Lp">Note Tree</a>&nbsp;and
select <em>Import into note</em>.</p>
</li>
<li>
<p>Select the file normally.</p>
</li>
<li>
<p>Uncheck <em>Import HTML, Markdown and TXT as text notes if it's unclear from the metadata</em>.</p>
</li>
<li>Right click the&nbsp;<a class="reference-link" href="#root/_help_oPVyFC7WL2Lp">Note Tree</a>&nbsp;and
select <em>Import into note</em>.</li>
<li>Select the file normally.</li>
<li>Uncheck <em>Import HTML, Markdown and TXT as text notes if it's unclear from the metadata</em>.</li>
</ul>
</li>
<li>
<p>When exporting Markdown files, the extension is preserved and the content
remains the same as in the source view.</p>
</li>
<li>
<p>Once exported as a Trilium ZIP, the ZIP will preserve the Markdown type
without converting to text notes thanks to the meta-information in it.</p>
</li>
<li>When exporting Markdown files, the extension is preserved and the content
remains the same as in the source view.</li>
<li>Once exported as a Trilium ZIP, the ZIP will preserve the Markdown type
without converting to text notes thanks to the meta-information in it.</li>
</ul>
<h2>Conversion between text notes and Markdown notes</h2>
<p>Currently there is no built-in functionality to convert a&nbsp;<a class="reference-link"
href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_iPIMuisry3hd">Text</a>&nbsp;note
into a Markdown note or vice-versa. We do have plans to address this in
the future.</p>
href="#root/_help_iPIMuisry3hd">Text</a>&nbsp;note into a Markdown note or vice-versa.
We do have plans to address this in the future.</p>
<p>This can be achieved manually, for a single note:</p>
<ol>
<li>Export the file as Markdown, with single format.</li>
@@ -135,6 +122,6 @@
<p>This feature of synchronizing the scroll is based on blocks but it's provided
on a best-effort basis since our underlying Markdown library doesn't support
this feature natively, so we had to implement our own algorithm. Feel free
to <a href="#root/pOsGYCXsbNQG/BgmBlOIl72jZ/_help_wy8So3yZZlH9">report issues</a>,
but always provide a sample Markdown file to be able to reproduce it.</p>
to <a href="#root/_help_wy8So3yZZlH9">report issues</a>, but always provide a
sample Markdown file to be able to reproduce it.</p>
</aside>

View File

@@ -12,8 +12,8 @@
the diagram.</p>
<p>This note type is a split view, meaning that both the source code and
a preview of the document are displayed side-by-side. See&nbsp;<a class="reference-link"
href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_SL5f1Auq7sVN">Note types with split view</a>&nbsp;for
more information.</p>
href="#root/_help_SL5f1Auq7sVN">Note types with split view</a>&nbsp;for more
information.</p>
<h2>Sample diagrams</h2>
<p>Starting with v0.103.0, Mermaid diagrams no longer start with a sample
flowchart, but instead a pane at the bottom will show all the supported
@@ -52,34 +52,30 @@
<img src="1_Mermaid Diagrams_image.png">
</li>
<li>The preview can be moved around by holding the left mouse button and dragging.</li>
<li
>Zooming can also be done by using the scroll wheel.</li>
<li>The zoom and position on the preview will remain fixed as the diagram
changes, to be able to work more easily with large diagrams.</li>
</ul>
<li>Zooming can also be done by using the scroll wheel.</li>
<li>The zoom and position on the preview will remain fixed as the diagram
changes, to be able to work more easily with large diagrams.</li>
</ul>
</li>
<li>The size of the source/preview panes can be adjusted by hovering over
the border between them and dragging it with the mouse.</li>
<li>In the&nbsp;<a class="reference-link" href="#root/_help_XpOYSgsLkTJy">Floating buttons</a>&nbsp;area:
<ul>
<li>The source/preview can be laid out left-right or bottom-top via the <em>Move editing pane to the left / bottom</em> option.</li>
<li
>Press <em>Lock editing</em> to automatically mark the note as read-only.
<li>Press <em>Lock editing</em> to automatically mark the note as read-only.
In this mode, the code pane is hidden and the diagram is displayed full-size.
Similarly, press <em>Unlock editing</em> to mark a read-only note as editable.</li>
<li
>Press the <em>Copy image reference to the clipboard</em> to be able to insert
the image representation of the diagram into a text note. See&nbsp;<a class="reference-link"
href="#root/_help_0Ofbk1aSuVRu">Image references</a>&nbsp;for more information.</li>
<li
>Press the <em>Export diagram as SVG</em> to download a scalable/vector rendering
of the diagram. Can be used to present the diagram without degrading when
zooming.</li>
<li>Press the <em>Copy image reference to the clipboard</em> to be able to insert
the image representation of the diagram into a text note. See&nbsp;<a class="reference-link"
href="#root/_help_0Ofbk1aSuVRu">Image references</a>&nbsp;for more information.</li>
<li>Press the <em>Export diagram as SVG</em> to download a scalable/vector rendering
of the diagram. Can be used to present the diagram without degrading when
zooming.</li>
<li>Press the <em>Export diagram as PNG</em> to download a normal image (at
1x scale, raster) of the diagram. Can be used to send the diagram in more
traditional channels such as e-mail.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2>Errors in the diagram</h2>
<p>If there is an error in the source code, the error will be displayed in

View File

@@ -13,13 +13,11 @@
<ol>
<li>HTML language for the legacy/vanilla method, with what needs to be displayed
(for example <code spellcheck="false">&lt;p&gt;Hello world.&lt;/p&gt;</code>).</li>
<li
>JSX for the Preact-based approach (see below).</li>
</ol>
<li>JSX for the Preact-based approach (see below).</li>
</ol>
</li>
<li>Create a&nbsp;<a class="reference-link" href="#root/_help_HcABDtFCkbFN">Render Note</a>.</li>
<li
>Assign the <code spellcheck="false">renderNote</code> <a href="#root/_help_zEY4DaJG4YT5">relation</a> to
<li>Assign the <code spellcheck="false">renderNote</code> <a href="#root/_help_zEY4DaJG4YT5">relation</a> to
point at the previously created code note.</li>
</ol>
<h2>Legacy scripting using jQuery</h2>
@@ -48,9 +46,10 @@ $dateEl.text(new Date());</code></pre>
need to provide a HTML anymore.</p>
<p>Here are the steps to creating a simple render note:</p>
<ol>
<li>Create a note of type&nbsp;<a class="reference-link" href="#root/_help_HcABDtFCkbFN">Render Note</a>.</li>
<li
>
<li>
<p>Create a note of type&nbsp;<a class="reference-link" href="#root/_help_HcABDtFCkbFN">Render Note</a>.</p>
</li>
<li>
<p>Create a child&nbsp;<a class="reference-link" href="#root/_help_6f9hih2hXXZk">Code</a>&nbsp;note
with JSX as the language.
<br>As an example, use the following content:</p><pre><code class="language-text-x-trilium-auto">export default function() {
@@ -60,17 +59,20 @@ $dateEl.text(new Date());</code></pre>
&lt;/&gt;
);
}</code></pre>
</li>
<li>In the parent render note, define a <code spellcheck="false">~renderNote</code> relation
pointing to the newly created child.</li>
<li>Refresh the render note and it should display a “Hello world” message.</li>
</li>
<li>
<p>In the parent render note, define a <code spellcheck="false">~renderNote</code> relation
pointing to the newly created child.</p>
</li>
<li>
<p>Refresh the render note and it should display a “Hello world” message.</p>
</li>
</ol>
<h2>Refreshing the note</h2>
<p>It's possible to refresh the note via:</p>
<ul>
<li>The corresponding button in&nbsp;<a class="reference-link" href="#root/_help_XpOYSgsLkTJy">Floating buttons</a>.</li>
<li
>The “Render active note” <a href="#root/_help_A9Oc6YKKc65v">keyboard shortcut</a> (not
<li>The “Render active note” <a href="#root/_help_A9Oc6YKKc65v">keyboard shortcut</a> (not
assigned by default).</li>
</ul>
<h2>Examples</h2>

View File

@@ -64,9 +64,8 @@
yet:</p>
<ul>
<li>Trilium-specific formulas (e.g. to obtain the title of a note).</li>
<li
>User-defined formulas</li>
<li>Cross-workbook calculation</li>
<li>User-defined formulas</li>
<li>Cross-workbook calculation</li>
</ul>
<p>If you would like us to work on these features, consider <a href="https://triliumnotes.org/en/support-us">supporting us</a>.</p>
<h2>Known limitations</h2>
@@ -81,8 +80,7 @@
</ul>
</li>
<li>There is currently no export functionality, as stated previously.</li>
<li
>There is no dedicated mobile support. Mobile support is currently experimental
<li>There is no dedicated mobile support. Mobile support is currently experimental
in Univer and when it becomes stable, we could potentially integrate it
into Trilium as well.</li>
</ul>

View File

@@ -20,171 +20,168 @@
<p>Fore more information see&nbsp;<a class="reference-link" href="#root/_help_nRhnJkTT8cPs">Formatting toolbar</a>.</p>
<h2>Features and formatting</h2>
<p>Here's a list of various features supported by text notes:</p>
<figure
class="table">
<table>
<thead>
<tr>
<th>Dedicated article</th>
<th>Feature</th>
</tr>
</thead>
<tbody>
<tr>
<td><a class="reference-link" href="#root/_help_Gr6xFaF6ioJ5">General formatting</a>
</td>
<td>
<ul>
<li>Headings (section titles, paragraph)</li>
<li>Font size</li>
<li>Bold, italic, underline, strike-through</li>
<li>Superscript, subscript</li>
<li>Font color &amp; background color</li>
<li>Remove formatting</li>
</ul>
</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_S6Xx8QIWTV66">Lists</a>
</td>
<td>
<ul>
<li>Bulleted lists</li>
<li>Numbered lists</li>
<li>To-do lists</li>
</ul>
</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_NwBbFdNZ9h7O">Block quotes &amp; admonitions</a>
</td>
<td>
<ul>
<li>Block quotes</li>
<li>Admonitions</li>
</ul>
</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_NdowYOC1GFKS">Tables</a>
</td>
<td>
<ul>
<li>Basic tables</li>
<li>Merging cells</li>
<li>Styling tables and cells.</li>
<li>Table captions</li>
</ul>
</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_UYuUB1ZekNQU">Developer-specific formatting</a>
</td>
<td>
<ul>
<li>Inline code</li>
<li>Code blocks</li>
<li>Keyboard shortcuts</li>
</ul>
</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_AgjCISero73a">Footnotes</a>
</td>
<td>
<ul>
<li>Footnotes</li>
</ul>
</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_mT0HEkOsz6i1">Images</a>
</td>
<td>
<ul>
<li>Images</li>
</ul>
</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_QEAPj01N5f7w">Links</a>
</td>
<td>
<ul>
<li>External links</li>
<li>Internal Trilium links</li>
</ul>
</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_nBAXQFj20hS1">Include Note</a>
</td>
<td>
<ul>
<li>Include note</li>
</ul>
</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_CohkqWQC1iBv">Insert buttons</a>
</td>
<td>
<ul>
<li>Symbols</li>
<li><a class="reference-link" href="#root/_help_YfYAtQBcfo5V">Math Equations</a>
</li>
<li>Mermaid diagrams</li>
<li>Horizontal ruler</li>
<li>Page break</li>
</ul>
</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_dEHYtoWWi8ct">Other features</a>
</td>
<td>
<ul>
<li>Indentation
<ul>
<li>Markdown import</li>
</ul>
</li>
<li><a class="reference-link" href="#root/_help_2x0ZAX9ePtzV">Cut to subnote</a>
</li>
</ul>
</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_gLt3vA97tMcp">Premium features</a>
</td>
<td>
<ul>
<li><a class="reference-link" href="#root/_help_ZlN4nump6EbW">Slash Commands</a>
</li>
<li><a class="reference-link" href="#root/_help_KC1HB96bqqHX">Templates</a>
</li>
<li><a class="reference-link" href="#root/_help_5wZallV2Qo1t">Format Painter</a>
</li>
</ul>
</td>
</tr>
</tbody>
</table>
</figure>
<h2>Read-Only vs. Editing Mode</h2>
<p>Text notes are usually opened in edit mode. However, they may open in
read-only mode if the note is too big or the note is explicitly marked
as read-only. For more information, see&nbsp;<a class="reference-link"
href="#root/_help_CoFPLs3dRlXc">Read-Only Notes</a>.</p>
<h2>Keyboard shortcuts</h2>
<p>There are numerous keyboard shortcuts to format the text without having
to use the mouse. For a reference of all the key combinations, see&nbsp;
<a
class="reference-link" href="#root/_help_A9Oc6YKKc65v">Keyboard Shortcuts</a>. In addition, see&nbsp;<a class="reference-link"
href="#root/_help_QrtTYPmdd1qq">Markdown-like formatting</a>&nbsp;as an alternative
to the keyboard shortcuts.</p>
<h2>Technical details</h2>
<p>For the text editing functionality, Trilium uses a commercial product
(with an open-source base) called&nbsp;<a class="reference-link" href="#root/_help_MI26XDLSAlCD">CKEditor</a>.
This brings the benefit of having a powerful WYSIWYG (What You See Is What
You Get) editor.</p>
<table>
<thead>
<tr>
<th>Dedicated article</th>
<th>Feature</th>
</tr>
</thead>
<tbody>
<tr>
<td><a class="reference-link" href="#root/_help_Gr6xFaF6ioJ5">General formatting</a>
</td>
<td>
<ul>
<li>Headings (section titles, paragraph)</li>
<li>Font size</li>
<li>Bold, italic, underline, strike-through</li>
<li>Superscript, subscript</li>
<li>Font color &amp; background color</li>
<li>Remove formatting</li>
</ul>
</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_S6Xx8QIWTV66">Lists</a>
</td>
<td>
<ul>
<li>Bulleted lists</li>
<li>Numbered lists</li>
<li>To-do lists</li>
</ul>
</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_NwBbFdNZ9h7O">Block quotes &amp; admonitions</a>
</td>
<td>
<ul>
<li>Block quotes</li>
<li>Admonitions</li>
</ul>
</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_NdowYOC1GFKS">Tables</a>
</td>
<td>
<ul>
<li>Basic tables</li>
<li>Merging cells</li>
<li>Styling tables and cells.</li>
<li>Table captions</li>
</ul>
</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_UYuUB1ZekNQU">Developer-specific formatting</a>
</td>
<td>
<ul>
<li>Inline code</li>
<li>Code blocks</li>
<li>Keyboard shortcuts</li>
</ul>
</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_AgjCISero73a">Footnotes</a>
</td>
<td>
<ul>
<li>Footnotes</li>
</ul>
</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_mT0HEkOsz6i1">Images</a>
</td>
<td>
<ul>
<li>Images</li>
</ul>
</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_QEAPj01N5f7w">Links</a>
</td>
<td>
<ul>
<li>External links</li>
<li>Internal Trilium links</li>
</ul>
</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_nBAXQFj20hS1">Include Note</a>
</td>
<td>
<ul>
<li>Include note</li>
</ul>
</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_CohkqWQC1iBv">Insert buttons</a>
</td>
<td>
<ul>
<li>Symbols</li>
<li><a class="reference-link" href="#root/_help_YfYAtQBcfo5V">Math Equations</a>
</li>
<li>Mermaid diagrams</li>
<li>Horizontal ruler</li>
<li>Page break</li>
</ul>
</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_dEHYtoWWi8ct">Other features</a>
</td>
<td>
<ul>
<li>Indentation
<ul>
<li>Markdown import</li>
</ul>
</li>
<li><a class="reference-link" href="#root/_help_2x0ZAX9ePtzV">Cut to subnote</a>
</li>
</ul>
</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_gLt3vA97tMcp">Premium features</a>
</td>
<td>
<ul>
<li><a class="reference-link" href="#root/_help_ZlN4nump6EbW">Slash Commands</a>
</li>
<li><a class="reference-link" href="#root/_help_KC1HB96bqqHX">Templates</a>
</li>
<li><a class="reference-link" href="#root/_help_5wZallV2Qo1t">Format Painter</a>
</li>
</ul>
</td>
</tr>
</tbody>
</table>
<h2>Read-Only vs. Editing Mode</h2>
<p>Text notes are usually opened in edit mode. However, they may open in
read-only mode if the note is too big or the note is explicitly marked
as read-only. For more information, see&nbsp;<a class="reference-link"
href="#root/_help_CoFPLs3dRlXc">Read-Only Notes</a>.</p>
<h2>Keyboard shortcuts</h2>
<p>There are numerous keyboard shortcuts to format the text without having
to use the mouse. For a reference of all the key combinations, see&nbsp;
<a
class="reference-link" href="#root/_help_A9Oc6YKKc65v">Keyboard Shortcuts</a>. In addition, see&nbsp;<a class="reference-link"
href="#root/_help_QrtTYPmdd1qq">Markdown-like formatting</a>&nbsp;as an alternative
to the keyboard shortcuts.</p>
<h2>Technical details</h2>
<p>For the text editing functionality, Trilium uses a commercial product
(with an open-source base) called&nbsp;<a class="reference-link" href="#root/_help_MI26XDLSAlCD">CKEditor</a>.
This brings the benefit of having a powerful WYSIWYG (What You See Is What
You Get) editor.</p>

View File

@@ -0,0 +1,70 @@
<aside class="admonition note">
<p>This feature used to be called <em>Bookmarks</em> (as it is the official
name in the editor we are using), but in order not to collide with the
concept of&nbsp;<a class="reference-link" href="#root/_help_u3YFHC9tQlpm">Bookmarks</a>,
we have renamed it to <em>Anchors.</em>
</p>
</aside>
<p>Anchors allows creating <a href="#root/_help_QEAPj01N5f7w">links</a> to a certain
part of a note, such as referencing a particular heading or section within
a note.</p>
<p>This feature was introduced in TriliumNext v0.94.0 and augmented in v0.130.0
to support linking across notes.</p>
<h2>Interaction</h2>
<ul>
<li>To create a anchor:
<ul>
<li>Place the cursor at the desired position where to place the anchor.</li>
<li>Look for the
<img src="Anchors_plus.png"
width="15" height="16">button in the&nbsp;<a class="reference-link" href="#root/_help_nRhnJkTT8cPs">Formatting toolbar</a>,
and then press the
<img src="1_Anchors_plus.png"
width="12" height="15">button.</li>
<li>Alternatively, use&nbsp;<a class="reference-link" href="#root/_help_ZlN4nump6EbW">Slash Commands</a>&nbsp;and
look for <em>anchor</em>.</li>
</ul>
</li>
<li>To place a link to a anchor:
<ul>
<li>Place the cursor at the desired position of the link.</li>
<li>From the <a href="#root/_help_QEAPj01N5f7w">link</a> pane, select the <em>Anchors</em> section
and select the desired anchor.</li>
</ul>
</li>
</ul>
<h2>Linking across notes</h2>
<p>Trilium v0.103.0 introduces cross-note Anchors, which makes it possible
to create&nbsp;<a class="reference-link" href="#root/_help_hrZ1D00cLbal">Internal (reference) links</a>&nbsp;which
point to a specific anchor in that document.</p>
<h3>Compatibility with documents from previous versions</h3>
<p>For notes created prior to Trilium v0.103.0, you might notice that the
Anchors might not be identified. This limitation is intentional in order
not to have to re-process all the notes, looking for anchors.</p>
<p>To fix this, simply go that note and make any change (e.g. inserting a
space), this will trigger the recalculation of the links.</p>
<h3>Linking to anchors through the <em>Add link</em> dialog</h3>
<ol>
<li>Create an anchor in the target note using the same process as described
above.</li>
<li>In another note, press <kbd>Ctrl</kbd>+<kbd>L</kbd> to insert an internal
link. Select the target note containing Anchors.</li>
<li>If the target note contains Anchors, a section will appear underneath
the note selector with the list of Anchors.</li>
<li>Add the link normally.</li>
</ol>
<p>Clicking on a reference link pointing to a anchor will automatically scroll
to the desired section.</p>
<h3>Linking to anchors through the bookmark toolbar</h3>
<ol>
<li>Create an anchor in the target note using the same process as described
above.</li>
<li>Click on the anchor to reveal the anchor's floating toolbar.</li>
<li>Click on the <em>Copy anchor reference link</em> button.</li>
<li>Go to the note where to insert the link and press <kbd>Ctrl</kbd>+<kbd>V</kbd>.</li>
</ol>
<aside class="admonition note">
<p>Use this method only to insert&nbsp;<a class="reference-link" href="#root/_help_hrZ1D00cLbal">Internal (reference) links</a>&nbsp;between
two documents. To link to an anchor on the same note, use the <em>Insert link</em> dialog
(<kbd>Ctrl</kbd>+<kbd>K</kbd>) and select the <em>Anchors</em> item instead.</p>
</aside>

View File

@@ -1,31 +0,0 @@
<p>Bookmarks allows creating <a href="#root/_help_QEAPj01N5f7w">links</a> to a certain
part of a note, such as referencing a particular heading.</p>
<p>Technically, bookmarks are HTML anchors.</p>
<p>This feature was introduced in TriliumNext 0.94.0.</p>
<h2>Interaction</h2>
<ul>
<li>To create a bookmark:
<ul>
<li>Place the cursor at the desired position where to place the bookmark.</li>
<li>Look for the
<img src="Bookmarks_plus.png"
width="15" height="16">button in the&nbsp;<a class="reference-link" href="#root/_help_nRhnJkTT8cPs">Formatting toolbar</a>,
and then press the
<img src="1_Bookmarks_plus.png"
width="12" height="15">button.</li>
</ul>
</li>
<li>To place a link to a bookmark:
<ul>
<li>Place the cursor at the desired position of the link.</li>
<li>From the <a href="#root/_help_QEAPj01N5f7w">link</a> pane, select the <em>Bookmarks</em> section
and select the desired bookmark.</li>
</ul>
</li>
</ul>
<h2>Limitations</h2>
<ul>
<li>Currently it's not possible to create a link to a bookmark from a different
note. This functionality will be added after the internal links feature
is enhanced to support bookmarks.</li>
</ul>

View File

@@ -4,7 +4,7 @@
reveal special inserable items and blocks such as symbols, Math expressions
and separators.</p>
<h2>Bookmarks</h2>
<p>See the dedicated&nbsp;<a class="reference-link" href="#root/_help_oSuaNgyyKnhu">Bookmarks</a>&nbsp;section.</p>
<p>See the dedicated&nbsp;<a class="reference-link" href="#root/_help_oSuaNgyyKnhu">Anchors</a>&nbsp;section.</p>
<h2>Emoji</h2>
<figure class="image image-style-align-right image_resized" style="width:42.4%;">
<img style="aspect-ratio:366/410;" src="Insert buttons_plus.png"

View File

@@ -382,7 +382,8 @@
"migration": {
"old_version": "現在のバージョンからの直接的な移行はサポートされていません。まず最新のv0.60.4にアップグレードしてから、このバージョンにアップグレードしてください。",
"error_message": "バージョン {{version}} への移行中にエラーが発生しました: {{stack}}",
"wrong_db_version": "データベースのバージョン({{version}})は、アプリケーションが想定しているバージョン({{targetVersion}}よりも新しく、互換性のないバージョンによって作成された可能性があります。この問題を解決するには、Triliumを最新バージョンにアップグレードしてください。"
"wrong_db_version": "データベースのバージョン({{version}})は、アプリケーションが想定しているバージョン({{targetVersion}}よりも新しく、互換性のないバージョンによって作成された可能性があります。この問題を解決するには、Triliumを最新バージョンにアップグレードしてください。",
"invalid_db_version": "データベースのバージョン番号が無効です。これは通常、データベース内の 'dbVersion' オプションが破損していることを示しています。バックアップから復元してください。"
},
"modals": {
"error_title": "エラー"

View File

@@ -45,7 +45,7 @@
"show-note-source": "顯示筆記來源對話方塊",
"show-options": "打開選項頁面",
"show-revisions": "顯示筆記歷史版本對話方塊",
"show-recent-changes": "顯示最近改對話方塊",
"show-recent-changes": "顯示最近改對話方塊",
"show-sql-console": "打開 SQL 控制台頁面",
"show-backend-log": "打開後端日誌頁面",
"text-note-operations": "文字筆記操作",
@@ -261,7 +261,7 @@
"show-note-source": "顯示筆記原始碼",
"show-options": "顯示選項",
"show-revisions": "顯示歷史版本",
"show-recent-changes": "顯示最近改",
"show-recent-changes": "顯示最近改",
"show-sql-console": "顯示 SQL 控制台",
"show-backend-log": "顯示後端日誌",
"show-help": "顯示說明",

View File

@@ -85,6 +85,13 @@
"reload-frontend-app": "ئالدى تەرەپ ئەپىنى قايتا يۈكلەش",
"open-dev-tools": "تەتقىقاتچى قوراللىرىنى ئېچىش",
"find-in-text": "تېكىست ئىچىدىن ئىزدەش",
"toggle-left-note-tree-panel": "سول تەرەپ (خاتىرە دەرىخى) تاختىسىنى ئالماشتۇرۇش"
"toggle-left-note-tree-panel": "سول تەرەپ (خاتىرە دەرىخى) تاختىسىنى ئالماشتۇرۇش",
"toggle-full-screen": "پۈتۈن ئېكران شەكلىگە ئالماشتۇرۇش",
"zoom-out": "كىچىكلىتىش",
"zoom-in": "چوڭايتىش",
"note-navigation": "خاتىرە يولباشچىسى",
"reset-zoom-level": "چوڭ-كىچىكلىك دەرىجىسىنى ئەسلىگە كەلتۈرۈش",
"copy-without-formatting": "تاللانغان تېكىستنى فارماٹسىز كۆچۈرۈش",
"force-save-revision": "نۆۋەتتىكى خاتىرىنىڭ يېڭى نەشرىنى مەجبۇرىي قۇرۇش/ساقلاش"
}
}

View File

@@ -119,7 +119,15 @@ class BAttribute extends AbstractBeccaEntity<BAttribute> {
}
isAutoLink() {
return this.type === "relation" && ["internalLink", "imageLink", "relationMapLink", "includeNoteLink"].includes(this.name);
if (this.type === "relation") {
return ["internalLink", "imageLink", "relationMapLink", "includeNoteLink"].includes(this.name);
}
if (this.type === "label") {
return this.name === "internalBookmark";
}
return false;
}
get note() {

View File

@@ -77,7 +77,7 @@ function getAttributeNames(type: string, nameLike: string) {
}
}
names = names.filter((name) => !["internalLink", "imageLink", "includeNoteLink", "relationMapLink"].includes(name));
names = names.filter((name) => !["internalLink", "imageLink", "includeNoteLink", "relationMapLink", "internalBookmark"].includes(name));
names.sort((a, b) => {
const aPrefix = a.toLowerCase().startsWith(nameLike);

View File

@@ -50,4 +50,26 @@ describe("sanitize", () => {
</figure>`;
expect(html_sanitizer.sanitize(dirty)).toBe(clean);
});
describe("bookmark anchors", () => {
it("preserves id attribute on empty <a> tags (CKEditor bookmarks)", () => {
const dirty = `<a id="my-bookmark"></a>`;
expect(html_sanitizer.sanitize(dirty)).toBe(dirty);
});
it("preserves id attribute on <a> tags with bookmark class", () => {
const dirty = `<a id="chapter-1" class="ck-bookmark"></a>`;
expect(html_sanitizer.sanitize(dirty)).toBe(dirty);
});
it("strips id attribute from non-anchor tags to prevent DOM clobbering", () => {
const dirty = `<div id="loginForm">content</div>`;
expect(html_sanitizer.sanitize(dirty)).toBe(`<div>content</div>`);
});
it("strips id attribute from <img> tags to prevent DOM clobbering", () => {
const dirty = `<img id="someId" src="test.png" />`;
expect(html_sanitizer.sanitize(dirty)).toBe(`<img src="test.png" />`);
});
});
});

View File

@@ -42,6 +42,7 @@ function sanitize(dirtyHtml: string) {
allowedTags: allowedTags as string[],
allowedAttributes: {
"*": ["class", "style", "title", "src", "href", "hash", "disabled", "align", "alt", "center", "data-*"],
a: ["id"], // CKEditor bookmark anchors use <a id="name"></a>
input: ["type", "checked"],
img: ["width", "height"],
code: [ "spellcheck" ]

View File

@@ -0,0 +1,42 @@
import { describe, expect, it } from "vitest";
import { findBookmarks } from "./notes.js";
describe("findBookmarks", () => {
it("extracts bookmark IDs from empty anchor tags", () => {
const content = `<p>Hello</p><a id="chapter-1"></a><p>World</p>`;
expect(findBookmarks(content)).toEqual(["chapter-1"]);
});
it("extracts multiple bookmarks", () => {
const content = `<a id="intro"></a><p>Text</p><a id="conclusion"></a>`;
expect(findBookmarks(content)).toEqual(["intro", "conclusion"]);
});
it("returns empty array when no bookmarks exist", () => {
const content = `<p>No bookmarks here</p>`;
expect(findBookmarks(content)).toEqual([]);
});
it("ignores anchor tags with href (regular links, not bookmarks)", () => {
const content = `<a href="#root/abc123" id="some-id">link</a>`;
expect(findBookmarks(content)).toEqual([]);
});
it("handles bookmarks with various valid ID characters", () => {
const content = `<a id="my_bookmark-2.0"></a>`;
expect(findBookmarks(content)).toEqual(["my_bookmark-2.0"]);
});
it("does not produce duplicates", () => {
const content = `<a id="same"></a><a id="same"></a>`;
expect(findBookmarks(content)).toEqual(["same"]);
});
it("matches self-closing bookmark anchors (CKEditor empty elements)", () => {
const content = `<p>Text</p><a id="my-bookmark"></a><p>More</p>`;
// CKEditor may also output without closing tag
const contentNoClose = `<p>Text</p><a id="my-bookmark"><p>More</p>`;
expect(findBookmarks(content)).toEqual(["my-bookmark"]);
expect(findBookmarks(contentNoClose)).toEqual(["my-bookmark"]);
});
});

View File

@@ -454,6 +454,54 @@ function findImageLinks(content: string, foundLinks: FoundLink[]) {
return content.replace(/src="[^"]*\/api\/images\//g, 'src="api/images/');
}
/**
* Extracts bookmark IDs from CKEditor bookmark anchors (`<a id="..."></a>` without href).
* Bookmarks are stored as labels on the note so they can be looked up without parsing content.
*/
export function findBookmarks(content: string): string[] {
const re = /<a\s+id="([^"]+)"[^>]*>(<\/a>)?/g;
const bookmarks: string[] = [];
let match;
while ((match = re.exec(content))) {
// Skip anchors that also have an href (those are regular links, not bookmarks)
if (match[0].includes("href=")) {
continue;
}
const id = match[1];
if (!bookmarks.includes(id)) {
bookmarks.push(id);
}
}
return bookmarks;
}
function saveBookmarks(note: BNote, content: string) {
const foundBookmarks = findBookmarks(content);
const existingBookmarks = note.getOwnedLabels("internalBookmark");
for (const bookmarkId of foundBookmarks) {
const existing = existingBookmarks.find((l) => l.value === bookmarkId);
if (!existing) {
new BAttribute({
noteId: note.noteId,
type: "label",
name: "internalBookmark",
value: bookmarkId
}).save();
}
}
// Remove bookmarks that are no longer in the content
const unusedBookmarks = existingBookmarks.filter((l) => !foundBookmarks.includes(l.value));
for (const unused of unusedBookmarks) {
unused.markAsDeleted();
}
}
function findInternalLinks(content: string, foundLinks: FoundLink[]) {
const re = /href="[^"]*#root[a-zA-Z0-9_\/]*\/([a-zA-Z0-9_]+)\/?"/g;
let match;
@@ -695,6 +743,7 @@ function saveLinks(note: BNote, content: string | Buffer) {
content = findImageLinks(content, foundLinks);
content = findInternalLinks(content, foundLinks);
content = findIncludeNoteLinks(content, foundLinks);
saveBookmarks(note, content);
({ forceFrontendReload, content } = checkImageAttachments(note, content));
} else if (note.type === "relationMap" && typeof content === "string") {

View File

@@ -16,7 +16,7 @@
"packageManager": "pnpm@10.33.0",
"devDependencies": {
"@wxt-dev/auto-icons": "1.1.1",
"wxt": "0.20.21"
"wxt": "0.20.22"
},
"dependencies": {
"cash-dom": "8.1.5"

View File

@@ -13,7 +13,7 @@
"preact": "10.29.1",
"preact-iso": "2.11.1",
"preact-render-to-string": "6.6.7",
"react-i18next": "17.0.2"
"react-i18next": "17.0.3"
},
"devDependencies": {
"@preact/preset-vite": "2.10.5",

View File

@@ -1,5 +1,5 @@
# Documentation
There are multiple types of documentation for Trilium:<img class="image-style-align-right" src="api/images/1ysfcELr4Xua/Documentation_image.png" width="205" height="162">
There are multiple types of documentation for Trilium:<img class="image-style-align-right" src="api/images/BZVD2exxpGnn/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.

2
docs/README-ja.md vendored
View File

@@ -63,7 +63,7 @@ Trilium Notes
* ートは任意の深さのツリーに配置できます。1つのートをツリー内の複数の場所に配置できます[クローン](https://docs.triliumnotes.org/user-guide/concepts/notes/cloning)を参照)
* 豊富な WYSIWYG ノートエディター 例:
表、画像、[数式](https://docs.triliumnotes.org/user-guide/note-types/text) とマークダウン
表、画像、[数式](https://docs.triliumnotes.org/user-guide/note-types/text) と markdown
[自動フォーマット](https://docs.triliumnotes.org/user-guide/note-types/text/markdown-formatting)
など
* 構文ハイライト表示を含む

31
docs/README-ug.md vendored
View File

@@ -285,23 +285,24 @@ pnpm run --filter desktop electron-forge:make --arch=x64 --platform=win32
### تەتقىقاتچى ھۆججەتلىرى
Please view the [documentation
guide](https://github.com/TriliumNext/Trilium/blob/main/docs/Developer%20Guide/Developer%20Guide/Environment%20Setup.md)
for details. If you have more questions, feel free to reach out via the links
described in the "Discuss with us" section above.
تەپسىلاتلار ئۈچۈن [ھۆججەت
يېتەكچىسى](https://github.com/TriliumNext/Trilium/blob/main/docs/Developer%20Guide/Developer%20Guide/Environment%20Setup.md)گە
قاراڭ. ئەگەر تېخىمۇ كۆپ سوئاللىرىڭىز بولسا، ئۈستىدىكى "بىز بىلەن ئالاقىلىشىڭ"
بۆلىكىدە تەمىنلەنگەن ئۇلىنىشلار ئارقىلىق بىز بىلەن ئالاقىلىشىڭنى قارشى ئالىمىز.
## 👏 Shoutouts
## 👏 مىننەتدارلىق
* [zadam](https://github.com/zadam) for the original concept and implementation
of the application.
* [Sarah Hussein](https://github.com/Sarah-Hussein) for designing the
application icon.
* [nriver](https://github.com/nriver) for his work on internationalization.
* [Thomas Frei](https://github.com/thfrei) for his original work on the Canvas.
* [antoniotejada](https://github.com/nriver) for the original syntax highlight
widget.
* [Dosu](https://dosu.dev/) for providing us with the automated responses to
GitHub issues and discussions.
* ئەپنىڭ ئەسلى ئۇقۇم لاھىيەسى ۋە ئەمەلگە ئاشۇرۇلۇشىغا تۆھپە قوشقان
[zadam](https://github.com/zadam).
* ئەپ سىنبەلگىسىنى لاھىيەلىگەن [Sarah
Hussein](https://github.com/Sarah-Hussein).
* خەلقئارالاشتۇرۇش خىزمىتىگە تۆھپە قوشقان [nriver](https://github.com/nriver).
* Canvas جەھەتتىكى ئەسلى ئىجادىي خىزمەتلىرى ئۈچۈن [Thomas
Frei](https://github.com/thfrei).
* ئەسلى گرامماتىكا گەۋدىلەندۈرۈش كىچىك زاپچاسلارنى ئاپتورى
[antoniotejada](https://github.com/nriver).
* GitHub مەسىلىلىرى ۋە مۇنازىرىلىرىگە ئاپتوماتىك جاۋاب قايتۇرۇش بىلەن تەمىنلىگەن
[Dosu](https://dosu.dev/).
* [Tabler Icons](https://tabler.io/icons) for the system tray icons.
Trilium would not be possible without the technologies behind it:

View File

@@ -3984,42 +3984,42 @@
"name": "internalLink",
"value": "s1aBHPd79XYj",
"isInheritable": false,
"position": 30
"position": 10
},
{
"type": "relation",
"name": "internalLink",
"value": "6RM1Q7ppFVoj",
"isInheritable": false,
"position": 40
},
{
"type": "relation",
"name": "internalLink",
"value": "CoFPLs3dRlXc",
"isInheritable": false,
"position": 50
"position": 20
},
{
"type": "relation",
"name": "internalLink",
"value": "8YBEPzcpUgxw",
"isInheritable": false,
"position": 60
"position": 30
},
{
"type": "relation",
"name": "internalLink",
"value": "IjZS7iK5EXtb",
"isInheritable": false,
"position": 70
"position": 40
},
{
"type": "relation",
"name": "internalLink",
"value": "XpOYSgsLkTJy",
"isInheritable": false,
"position": 80
"position": 50
},
{
"type": "relation",
"name": "internalLink",
"value": "CoFPLs3dRlXc",
"isInheritable": false,
"position": 60
},
{
"type": "label",
@@ -7188,6 +7188,93 @@
],
"dirFileName": "Text",
"children": [
{
"isClone": false,
"noteId": "oSuaNgyyKnhu",
"notePath": [
"pOsGYCXsbNQG",
"KSZ04uQ2D1St",
"iPIMuisry3hd",
"oSuaNgyyKnhu"
],
"title": "Anchors",
"notePosition": 10,
"prefix": null,
"isExpanded": false,
"type": "text",
"mime": "text/html",
"attributes": [
{
"type": "relation",
"name": "internalLink",
"value": "u3YFHC9tQlpm",
"isInheritable": false,
"position": 10
},
{
"type": "relation",
"name": "internalLink",
"value": "QEAPj01N5f7w",
"isInheritable": false,
"position": 20
},
{
"type": "relation",
"name": "internalLink",
"value": "nRhnJkTT8cPs",
"isInheritable": false,
"position": 30
},
{
"type": "relation",
"name": "internalLink",
"value": "ZlN4nump6EbW",
"isInheritable": false,
"position": 40
},
{
"type": "relation",
"name": "internalLink",
"value": "hrZ1D00cLbal",
"isInheritable": false,
"position": 50
},
{
"type": "label",
"name": "iconClass",
"value": "bx bx-bookmark",
"isInheritable": false,
"position": 10
},
{
"type": "label",
"name": "shareAlias",
"value": "bookmarks",
"isInheritable": false,
"position": 30
}
],
"format": "markdown",
"dataFileName": "Anchors.md",
"attachments": [
{
"attachmentId": "2cn9iY3Qgyjs",
"title": "plus.png",
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "Anchors_plus.png"
},
{
"attachmentId": "JaiAT3dHDIyy",
"title": "plus.png",
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "1_Anchors_plus.png"
}
]
},
{
"isClone": false,
"noteId": "NwBbFdNZ9h7O",
@@ -7198,7 +7285,7 @@
"NwBbFdNZ9h7O"
],
"title": "Block quotes & admonitions",
"notePosition": 10,
"notePosition": 20,
"prefix": null,
"isExpanded": false,
"type": "text",
@@ -7262,72 +7349,6 @@
}
]
},
{
"isClone": false,
"noteId": "oSuaNgyyKnhu",
"notePath": [
"pOsGYCXsbNQG",
"KSZ04uQ2D1St",
"iPIMuisry3hd",
"oSuaNgyyKnhu"
],
"title": "Bookmarks",
"notePosition": 20,
"prefix": null,
"isExpanded": false,
"type": "text",
"mime": "text/html",
"attributes": [
{
"type": "relation",
"name": "internalLink",
"value": "QEAPj01N5f7w",
"isInheritable": false,
"position": 10
},
{
"type": "relation",
"name": "internalLink",
"value": "nRhnJkTT8cPs",
"isInheritable": false,
"position": 20
},
{
"type": "label",
"name": "iconClass",
"value": "bx bx-bookmark",
"isInheritable": false,
"position": 10
},
{
"type": "label",
"name": "shareAlias",
"value": "bookmarks",
"isInheritable": false,
"position": 30
}
],
"format": "markdown",
"dataFileName": "Bookmarks.md",
"attachments": [
{
"attachmentId": "2cn9iY3Qgyjs",
"title": "plus.png",
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "Bookmarks_plus.png"
},
{
"attachmentId": "JaiAT3dHDIyy",
"title": "plus.png",
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "1_Bookmarks_plus.png"
}
]
},
{
"isClone": false,
"noteId": "veGu4faJErEM",
@@ -10147,17 +10168,24 @@
{
"type": "relation",
"name": "internalLink",
"value": "XpOYSgsLkTJy",
"value": "SL5f1Auq7sVN",
"isInheritable": false,
"position": 20
},
{
"type": "relation",
"name": "internalLink",
"value": "0Ofbk1aSuVRu",
"value": "XpOYSgsLkTJy",
"isInheritable": false,
"position": 30
},
{
"type": "relation",
"name": "internalLink",
"value": "0Ofbk1aSuVRu",
"isInheritable": false,
"position": 40
},
{
"type": "label",
"name": "shareAlias",
@@ -10171,13 +10199,6 @@
"value": "bx bx-selection",
"isInheritable": false,
"position": 20
},
{
"type": "relation",
"name": "internalLink",
"value": "SL5f1Auq7sVN",
"isInheritable": false,
"position": 40
}
],
"format": "markdown",
@@ -10839,6 +10860,90 @@
"type": "text",
"mime": "text/html",
"attributes": [
{
"type": "relation",
"name": "internalLink",
"value": "Oau6X9rCuegd",
"isInheritable": false,
"position": 10
},
{
"type": "relation",
"name": "internalLink",
"value": "iPIMuisry3hd",
"isInheritable": false,
"position": 20
},
{
"type": "relation",
"name": "internalLink",
"value": "6f9hih2hXXZk",
"isInheritable": false,
"position": 30
},
{
"type": "relation",
"name": "internalLink",
"value": "SL5f1Auq7sVN",
"isInheritable": false,
"position": 40
},
{
"type": "relation",
"name": "internalLink",
"value": "NwBbFdNZ9h7O",
"isInheritable": false,
"position": 50
},
{
"type": "relation",
"name": "internalLink",
"value": "YfYAtQBcfo5V",
"isInheritable": false,
"position": 60
},
{
"type": "relation",
"name": "internalLink",
"value": "s1aBHPd79XYj",
"isInheritable": false,
"position": 70
},
{
"type": "relation",
"name": "internalLink",
"value": "nBAXQFj20hS1",
"isInheritable": false,
"position": 80
},
{
"type": "relation",
"name": "internalLink",
"value": "hrZ1D00cLbal",
"isInheritable": false,
"position": 90
},
{
"type": "relation",
"name": "internalLink",
"value": "m1lbrzyKDaRB",
"isInheritable": false,
"position": 100
},
{
"type": "relation",
"name": "internalLink",
"value": "oPVyFC7WL2Lp",
"isInheritable": false,
"position": 110
},
{
"type": "relation",
"name": "internalLink",
"value": "wy8So3yZZlH9",
"isInheritable": false,
"position": 120
},
{
"type": "label",
"name": "iconClass",
@@ -10852,90 +10957,6 @@
"value": "markdown",
"isInheritable": false,
"position": 40
},
{
"type": "relation",
"name": "internalLink",
"value": "Oau6X9rCuegd",
"isInheritable": false,
"position": 50
},
{
"type": "relation",
"name": "internalLink",
"value": "iPIMuisry3hd",
"isInheritable": false,
"position": 60
},
{
"type": "relation",
"name": "internalLink",
"value": "6f9hih2hXXZk",
"isInheritable": false,
"position": 70
},
{
"type": "relation",
"name": "internalLink",
"value": "oPVyFC7WL2Lp",
"isInheritable": false,
"position": 80
},
{
"type": "relation",
"name": "internalLink",
"value": "wy8So3yZZlH9",
"isInheritable": false,
"position": 150
},
{
"type": "relation",
"name": "internalLink",
"value": "SL5f1Auq7sVN",
"isInheritable": false,
"position": 160
},
{
"type": "relation",
"name": "internalLink",
"value": "NwBbFdNZ9h7O",
"isInheritable": false,
"position": 170
},
{
"type": "relation",
"name": "internalLink",
"value": "YfYAtQBcfo5V",
"isInheritable": false,
"position": 180
},
{
"type": "relation",
"name": "internalLink",
"value": "s1aBHPd79XYj",
"isInheritable": false,
"position": 190
},
{
"type": "relation",
"name": "internalLink",
"value": "nBAXQFj20hS1",
"isInheritable": false,
"position": 200
},
{
"type": "relation",
"name": "internalLink",
"value": "hrZ1D00cLbal",
"isInheritable": false,
"position": 210
},
{
"type": "relation",
"name": "internalLink",
"value": "m1lbrzyKDaRB",
"isInheritable": false,
"position": 220
}
],
"format": "markdown",

View File

@@ -99,12 +99,15 @@ To do so:
For example, to change the font of the document from the one defined by the theme or the user to a serif one:
```
body {
--main-font-family: serif !important;
--detail-font-family: var(--main-font-family) !important;
body{
--print-font-family: serif;
--print-font-size: 11pt;
}
```
> [!IMPORTANT]
> When altering `--print-font-family`, make sure the change is done at `body` level and not `:root`, since otherwise it won't be picked up due to specificity rules.
To remark:
* Multiple CSS notes can be add by using multiple `~printCss` relations.

View File

@@ -77,6 +77,12 @@ TRILIUM_DATA_DIR=/home/myuser/data/my-trilium-data trilium
You can then save the above command as a shell script on your path for convenience.
## Electron user data directory (desktop only)
When running the desktop application, Electron stores internal data (caches, spell-check dictionaries, session storage, etc.) separately from the Trilium data directory. By default this goes to the system's application data folder (e.g. `%APPDATA%` on Windows), which may be undesirable in corporate environments with roaming profiles or when running in portable mode.
To keep Electron data out of the system's roaming profile, set the `TRILIUM_ELECTRON_DATA_DIR` environment variable to an explicit path. The `trilium-portable` script does this automatically, pointing it to `trilium-electron-data/` next to the application.
## Fine-grained directory/path location
Apart from the data directory, some of the subdirectories of it can be moved elsewhere by changing an environment variable:
@@ -88,4 +94,5 @@ Apart from the data directory, some of the subdirectories of it can be moved els
| `TRILIUM_LOG_DIR` | `${TRILIUM_DATA_DIR}/log` | Directory where daily <a class="reference-link" href="../Troubleshooting/Error%20logs/Backend%20(server)%20logs.md">Backend (server) logs</a> are stored. |
| `TRILIUM_TMP_DIR` | `${TRILIUM_DATA_DIR}/tmp` | Directory where temporary files are stored (for example when opening in an external app). |
| `TRILIUM_ANONYMIZED_DB_DIR` | `${TRILIUM_DATA_DIR}/anonymized-db` | Directory where a <a class="reference-link" href="../Troubleshooting/Anonymized%20Database.md">Anonymized Database</a> is stored. |
| `TRILIUM_CONFIG_INI_PATH` | `${TRILIUM_DATA_DIR}/config.ini` | Path to <a class="reference-link" href="../Advanced%20Usage/Configuration%20(config.ini%20or%20e.md">Configuration (config.ini or environment variables)</a> file. |
| `TRILIUM_CONFIG_INI_PATH` | `${TRILIUM_DATA_DIR}/config.ini` | Path to <a class="reference-link" href="../Advanced%20Usage/Configuration%20(config.ini%20or%20e.md">Configuration (config.ini or environment variables)</a> file. |
| `TRILIUM_ELECTRON_DATA_DIR` | System appData | Directory for Electron internal data (caches, spell-check dictionaries, etc.). Set this in portable mode to avoid writing to the system profile (desktop only). |

View File

@@ -11,7 +11,7 @@ Trilium offers various startup scripts to customize your experience:
* `trilium-no-cert-check`: Starts Trilium without validating [TLS certificates](Server%20Installation/HTTPS%20\(TLS\).md), useful if connecting to a server with a self-signed certificate.
* Alternatively, set the `NODE_TLS_REJECT_UNAUTHORIZED=0` environment variable before starting Trilium.
* `trilium-portable`: Launches Trilium in portable mode, where the [data directory](Data%20directory.md) is created within the application's directory, making it easy to move the entire setup.
* `trilium-portable`: Launches Trilium in portable mode, where the [data directory](Data%20directory.md) is created within the application's directory, making it easy to move the entire setup. Electron's internal data (caches, dictionaries, etc.) is also stored within the data directory, so no files are written to the system's roaming profile.
* `trilium-safe-mode`: Boots Trilium in "safe mode," disabling any startup scripts that might cause the application to crash.
## Synchronization

View File

@@ -33,7 +33,7 @@ The following features are supported by Trilium's Markdown format and will show
```
<section class="include-note" data-note-id="vJDjQm0VK8Na" data-box-size="expandable">
&nbsp;
&nbsp;
</section>n
```
* <a class="reference-link" href="Text/Links/Internal%20(reference)%20links.md">Internal (reference) links</a> via its HTML syntax, or through a _Wikilinks_\-like format (only <a class="reference-link" href="../Advanced%20Usage/Note%20ID.md">Note ID</a>):
@@ -55,7 +55,6 @@ There are two ways to create a Markdown note:
## Import/export
* By default, when importing a single Markdown file it automatically gets converted to a <a class="reference-link" href="Text.md">Text</a> note. To avoid that and have it imported as a Markdown note instead:
* Right click the <a class="reference-link" href="../Basic%20Concepts%20and%20Features/UI%20Elements/Note%20Tree.md">Note Tree</a> and select _Import into note_.
* Select the file normally.
* Uncheck _Import HTML, Markdown and TXT as text notes if it's unclear from the metadata_.

View File

Before

Width:  |  Height:  |  Size: 703 B

After

Width:  |  Height:  |  Size: 703 B

View File

@@ -0,0 +1,46 @@
# Anchors
> [!NOTE]
> This feature used to be called _Bookmarks_ (as it is the official name in the editor we are using), but in order not to collide with the concept of <a class="reference-link" href="../../Basic%20Concepts%20and%20Features/Navigation/Bookmarks.md">Bookmarks</a>, we have renamed it to _Anchors._
Anchors allows creating [links](Links.md) to a certain part of a note, such as referencing a particular heading or section within a note.
This feature was introduced in TriliumNext v0.94.0 and augmented in v0.130.0 to support linking across notes.
## Interaction
* To create a anchor:
* Place the cursor at the desired position where to place the anchor.
* Look for the <img src="Anchors_plus.png" width="15" height="16"> button in the <a class="reference-link" href="Formatting%20toolbar.md">Formatting toolbar</a>, and then press the <img src="1_Anchors_plus.png" width="12" height="15"> button.
* Alternatively, use <a class="reference-link" href="Premium%20features/Slash%20Commands.md">Slash Commands</a> and look for _anchor_.
* To place a link to a anchor:
* Place the cursor at the desired position of the link.
* From the [link](Links.md) pane, select the _Anchors_ section and select the desired anchor.
## Linking across notes
Trilium v0.103.0 introduces cross-note Anchors, which makes it possible to create <a class="reference-link" href="Links/Internal%20(reference)%20links.md">Internal (reference) links</a> which point to a specific anchor in that document.
### Compatibility with documents from previous versions
For notes created prior to Trilium v0.103.0, you might notice that the Anchors might not be identified. This limitation is intentional in order not to have to re-process all the notes, looking for anchors.
To fix this, simply go that note and make any change (e.g. inserting a space), this will trigger the recalculation of the links.
### Linking to anchors through the _Add link_ dialog
1. Create an anchor in the target note using the same process as described above.
2. In another note, press <kbd>Ctrl</kbd>+<kbd>L</kbd> to insert an internal link. Select the target note containing Anchors.
3. If the target note contains Anchors, a section will appear underneath the note selector with the list of Anchors.
4. Add the link normally.
Clicking on a reference link pointing to a anchor will automatically scroll to the desired section.
### Linking to anchors through the bookmark toolbar
1. Create an anchor in the target note using the same process as described above.
2. Click on the anchor to reveal the anchor's floating toolbar.
3. Click on the _Copy anchor reference link_ button.
4. Go to the note where to insert the link and press <kbd>Ctrl</kbd>+<kbd>V</kbd>.
> [!NOTE]
> Use this method only to insert <a class="reference-link" href="Links/Internal%20(reference)%20links.md">Internal (reference) links</a> between two documents. To link to an anchor on the same note, use the _Insert link_ dialog (<kbd>Ctrl</kbd>+<kbd>K</kbd>) and select the _Anchors_ item instead.

View File

Before

Width:  |  Height:  |  Size: 636 B

After

Width:  |  Height:  |  Size: 636 B

View File

@@ -1,19 +0,0 @@
# Bookmarks
Bookmarks allows creating [links](Links.md) to a certain part of a note, such as referencing a particular heading.
Technically, bookmarks are HTML anchors.
This feature was introduced in TriliumNext 0.94.0.
## Interaction
* To create a bookmark:
* Place the cursor at the desired position where to place the bookmark.
* Look for the <img src="Bookmarks_plus.png" width="15" height="16"> button in the <a class="reference-link" href="Formatting%20toolbar.md">Formatting toolbar</a>, and then press the <img src="1_Bookmarks_plus.png" width="12" height="15"> button.
* To place a link to a bookmark:
* Place the cursor at the desired position of the link.
* From the [link](Links.md) pane, select the _Bookmarks_ section and select the desired bookmark.
## Limitations
* Currently it's not possible to create a link to a bookmark from a different note. This functionality will be added after the internal links feature is enhanced to support bookmarks.

View File

@@ -3,7 +3,7 @@ Press the <img src="4_Insert buttons_image.png" width="34" height="16"> button i
## Bookmarks
See the dedicated <a class="reference-link" href="Bookmarks.md">Bookmarks</a> section.
See the dedicated <a class="reference-link" href="Anchors.md">Anchors</a> section.
## Emoji

View File

@@ -17,6 +17,8 @@ import internalLinkIcon from './icons/trilium.svg?raw';
import noteIcon from './icons/note.svg?raw';
import importMarkdownIcon from './icons/markdown-mark.svg?raw';
import { icons as mathIcons, MathUI } from '@triliumnext/ckeditor5-math';
import { BookmarkUI } from "ckeditor5";
import bxBookmark from "boxicons/svg/regular/bx-bookmark.svg?raw";
type SlashCommandDefinition = SlashCommandEditorConfig["extraCommands"][number];
@@ -74,6 +76,19 @@ export default function buildExtraCommands(): SlashCommandDefinition[] {
description: "Import a markdown file into this note",
icon: importMarkdownIcon,
commandName: MARKDOWN_IMPORT_COMMAND
},
{
id: "anchor",
title: "Anchor",
description: "Insert an anchor for internal linking",
aliases: [ "bookmark" ],
icon: bxBookmark,
execute: (editor: Editor) => {
// Defer to the next event loop tick so the slash command fully finishes
// its DOM/selection cleanup; _showFormView needs the view and mapper to
// be in a settled state for balloon positioning.
setTimeout(() => (editor.plugins.get(BookmarkUI) as any)._showFormView(), 0);
}
}
];
}

View File

@@ -21,6 +21,7 @@ import { Mermaid } from "@triliumnext/ckeditor5-mermaid";
import { Admonition } from "@triliumnext/ckeditor5-admonition";
import { Footnotes } from "@triliumnext/ckeditor5-footnotes";
import { Math, AutoformatMath } from "@triliumnext/ckeditor5-math";
import CopyAnchorLinkButton from "./plugins/copy_anchor_link.js";
// import "@triliumnext/ckeditor5-mermaid/index.css";
// import "@triliumnext/ckeditor5-admonition/index.css";
@@ -63,6 +64,7 @@ const TRILIUM_PLUGINS: typeof Plugin[] = [
AdmonitionToolbar,
IncludeNoteBoxSizeDropdown,
IncludeNoteToolbar,
CopyAnchorLinkButton,
];
/**

View File

@@ -0,0 +1,51 @@
import { ButtonView, Plugin } from "ckeditor5";
import copyIcon from "../icons/copy.svg?raw";
import { escapeHtml } from "../utils";
/**
* Adds a "Copy anchor link" button to the bookmark/anchor widget toolbar.
* When clicked, copies a reference link href (e.g. `#root/noteId?bookmark=anchorName`)
* to the clipboard.
*/
export default class CopyAnchorLinkButton extends Plugin {
public init() {
const editor = this.editor;
editor.ui.componentFactory.add("copyAnchorLink", (locale) => {
const button = new ButtonView(locale);
const t = locale.t;
button.set({
label: t("Copy anchor reference link"),
icon: copyIcon,
tooltip: true
});
this.listenTo(button, "execute", () => {
const selection = editor.model.document.selection;
const selectedElement = selection.getSelectedElement();
if (selectedElement?.name === "bookmark") {
const bookmarkId = selectedElement.getAttribute("bookmarkId") as string;
const noteId = glob.getActiveContextNote()?.noteId;
if (noteId && bookmarkId) {
const href = `#root/${noteId}?bookmark=${encodeURIComponent(bookmarkId)}`;
const title = glob.getReferenceLinkTitleSync(href);
const html = `<a class="reference-link" href="${escapeHtml(href)}">${escapeHtml(title)}</a>`;
navigator.clipboard.write([
new ClipboardItem({
"text/html": new Blob([html], { type: "text/html" }),
"text/plain": new Blob([href], { type: "text/plain" })
})
]);
}
}
});
return button;
});
}
}

View File

@@ -2,7 +2,16 @@ window.CKEDITOR_TRANSLATIONS = {
en: {
dictionary: {
"Insert template": "Insert text snippet",
"Search template": "Search text snippet"
"Search template": "Search text snippet",
"Bookmark": "Anchor",
"Bookmarks": "Anchors",
"Bookmark name": "Anchor name",
"Bookmark must not be empty.": "Anchor name must not be empty.",
"Bookmark name already exists.": "Anchor name already exists.",
"Bookmark name cannot contain space characters.": "Anchor name cannot contain space characters.",
"Edit bookmark": "Edit anchor",
"Enter the bookmark name without spaces.": "Enter the anchor name without spaces.",
"Bookmark toolbar": "Anchor toolbar"
}
}
};

View File

@@ -1,5 +1,9 @@
import type { DifferItemAttribute, Editor, ModelDocumentFragment, ModelElement, ModelNode } from "ckeditor5";
export function escapeHtml(str: string): string {
return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
}
function hasHeadingAncestor(node: ModelElement | ModelNode | ModelDocumentFragment | null): boolean {
let current: ModelElement | ModelNode | ModelDocumentFragment | null = node;
while (current) {

View File

@@ -91,6 +91,7 @@ export default [
{ type: "label", name: "printPageSize" },
{ type: "label", name: "printScale" },
{ type: "label", name: "printMargins" },
{ type: "label", name: "internalBookmark" },
// relation names
{ type: "relation", name: "internalLink" },

View File

@@ -0,0 +1,85 @@
import { describe, expect, it } from "vitest";
import { extractCodeBlocks } from "./markdown_renderer.js";
describe("extractCodeBlocks", () => {
it("should extract a fenced code block", () => {
const input = "before\n```js\nconsole.log('hi');\n```\nafter";
const { processedText, placeholderMap } = extractCodeBlocks(input);
expect(placeholderMap.size).toBe(1);
expect(processedText).toContain("before\n");
expect(processedText).toContain("\nafter");
expect(processedText).not.toContain("```");
const placeholder = [...placeholderMap.keys()][0];
expect(placeholderMap.get(placeholder)).toBe("```js\nconsole.log('hi');\n```");
});
it("should extract inline code", () => {
const input = "use `console.log` here";
const { processedText, placeholderMap } = extractCodeBlocks(input);
expect(placeholderMap.size).toBe(1);
expect(processedText).not.toContain("`console.log`");
const placeholder = [...placeholderMap.keys()][0];
expect(placeholderMap.get(placeholder)).toBe("`console.log`");
});
it("should extract multiple fenced code blocks independently", () => {
const input = "```js\na\n```\ntext\n```py\nb\n```";
const { processedText, placeholderMap } = extractCodeBlocks(input);
expect(placeholderMap.size).toBe(2);
expect(processedText).toContain("text");
});
it("should not treat inline backtick-escaped triple backticks as a fenced code block", () => {
const input = [
"* Code blocks with syntax highlight (e.g. ` ```js `) and automatic syntax highlight",
"* Block quotes & admonitions",
"* Math Equations",
"* Mermaid Diagrams using ` ```mermaid `"
].join("\n");
const { processedText, placeholderMap } = extractCodeBlocks(input);
// All four bullet points must survive
expect(processedText).toContain("Block quotes & admonitions");
expect(processedText).toContain("Math Equations");
expect(processedText).toContain("Mermaid Diagrams");
expect(processedText).toContain("automatic syntax highlight");
// The inline code spans should be extracted, not fenced code blocks
for (const value of placeholderMap.values()) {
expect(value).not.toMatch(/^```[\s\S]*```$/);
}
});
it("should not swallow content between two inline triple-backtick mentions", () => {
const input = "Use ` ```js ` for JS and ` ```py ` for Python";
const { processedText } = extractCodeBlocks(input);
expect(processedText).toContain("for JS and");
expect(processedText).toContain("for Python");
});
it("should handle a real fenced code block after inline triple backticks", () => {
const input = [
"Use ` ```js ` for JavaScript.",
"",
"```py",
"print('hello')",
"```"
].join("\n");
const { processedText, placeholderMap } = extractCodeBlocks(input);
expect(processedText).toContain("for JavaScript.");
// Should have the inline code and the fenced block as separate entries
const values = [...placeholderMap.values()];
const hasFencedBlock = values.some((v) => v.includes("print('hello')"));
expect(hasFencedBlock).toBe(true);
});
});

View File

@@ -106,13 +106,13 @@ function handleH1(content: string, title: string): string {
});
}
function extractCodeBlocks(text: string): { processedText: string; placeholderMap: Map<string, string> } {
export function extractCodeBlocks(text: string): { processedText: string; placeholderMap: Map<string, string> } {
const codeMap = new Map<string, string>();
let id = 0;
const timestamp = Date.now();
text = text
.replace(/```[\s\S]*?```/g, (m) => {
.replace(/^[ \t]*```[^\n]*\n[\s\S]*?^[ \t]*```[ \t]*$/gm, (m) => {
const key = `<!--CODE_BLOCK_${timestamp}_${id++}-->`;
codeMap.set(key, m);
return key;

108
pnpm-lock.yaml generated
View File

@@ -187,8 +187,8 @@ importers:
apps/build-docs:
devDependencies:
'@redocly/cli':
specifier: 2.26.0
version: 2.26.0(@opentelemetry/api@1.9.0)(bufferutil@4.0.9)(core-js@3.46.0)(encoding@0.1.13)(utf-8-validate@6.0.5)
specifier: 2.28.0
version: 2.28.0(@opentelemetry/api@1.9.0)(bufferutil@4.0.9)(core-js@3.46.0)(encoding@0.1.13)(utf-8-validate@6.0.5)
archiver:
specifier: 7.0.1
version: 7.0.1
@@ -358,8 +358,8 @@ importers:
specifier: 10.29.1
version: 10.29.1
react-i18next:
specifier: 17.0.2
version: 17.0.2(i18next@26.0.4(typescript@6.0.2))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@6.0.2)
specifier: 17.0.3
version: 17.0.3(i18next@26.0.4(typescript@6.0.2))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@6.0.2)
react-window:
specifier: 2.2.7
version: 2.2.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
@@ -575,14 +575,14 @@ importers:
specifier: 3.0.63
version: 3.0.63(zod@4.3.6)
'@ai-sdk/openai':
specifier: 3.0.52
version: 3.0.52(zod@4.3.6)
specifier: 3.0.53
version: 3.0.53(zod@4.3.6)
'@modelcontextprotocol/sdk':
specifier: ^1.12.1
version: 1.29.0(zod@4.3.6)
ai:
specifier: 6.0.159
version: 6.0.159(zod@4.3.6)
specifier: 6.0.161
version: 6.0.161(zod@4.3.6)
better-sqlite3:
specifier: 12.9.0
version: 12.9.0
@@ -890,10 +890,10 @@ importers:
devDependencies:
'@wxt-dev/auto-icons':
specifier: 1.1.1
version: 1.1.1(wxt@0.20.21(@types/node@24.12.2)(eslint@10.2.0(jiti@2.6.1))(jiti@2.6.1)(less@4.1.3)(rollup@4.60.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))
version: 1.1.1(wxt@0.20.22(@types/node@24.12.2)(eslint@10.2.0(jiti@2.6.1))(jiti@2.6.1)(less@4.1.3)(rollup@4.60.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))
wxt:
specifier: 0.20.21
version: 0.20.21(@types/node@24.12.2)(eslint@10.2.0(jiti@2.6.1))(jiti@2.6.1)(less@4.1.3)(rollup@4.60.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)
specifier: 0.20.22
version: 0.20.22(@types/node@24.12.2)(eslint@10.2.0(jiti@2.6.1))(jiti@2.6.1)(less@4.1.3)(rollup@4.60.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)
apps/website:
dependencies:
@@ -910,8 +910,8 @@ importers:
specifier: 6.6.7
version: 6.6.7(preact@10.29.1)
react-i18next:
specifier: 17.0.2
version: 17.0.2(i18next@26.0.4(typescript@6.0.2))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@6.0.2)
specifier: 17.0.3
version: 17.0.3(i18next@26.0.4(typescript@6.0.2))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@6.0.2)
devDependencies:
'@preact/preset-vite':
specifier: 2.10.5
@@ -1482,8 +1482,8 @@ packages:
peerDependencies:
zod: ^3.25.76 || ^4.1.8
'@ai-sdk/gateway@3.0.96':
resolution: {integrity: sha512-BDiVEMUVHGpngReeigzLyJobG0TvzYbNGzdHI8JYBZHrjOX4aL6qwIls7z3p7V4TuXVWUCbG8TSWEe7ksX4Vhw==}
'@ai-sdk/gateway@3.0.98':
resolution: {integrity: sha512-Ol+nP8PIlj8FjN8qKlxhE89N0woqAaGi9CUBGp1boe3RafpphJ7WMuq/RErSvxtwTqje03TP+zIdzP113krxRg==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.25.76 || ^4.1.8
@@ -1494,8 +1494,8 @@ packages:
peerDependencies:
zod: ^3.25.76 || ^4.1.8
'@ai-sdk/openai@3.0.52':
resolution: {integrity: sha512-4Rr8NCGmfWTz6DCUvixn9UmyZcMatiHn0zWoMzI3JCUe9R1P/vsPOpCBALKoSzVYOjyJnhtnVIbfUKujcS39uw==}
'@ai-sdk/openai@3.0.53':
resolution: {integrity: sha512-Wld+Rbc05KaUn08uBt06eEuwcgalcIFtIl32Yp+GxuZXUQwOb6YeAuq+C6da4ch6BurFoqEaLemJVwjBb7x+PQ==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.25.76 || ^4.1.8
@@ -4872,27 +4872,27 @@ packages:
'@redocly/cli-otel@0.1.2':
resolution: {integrity: sha512-Bg7BoO5t1x3lVK+KhA5aGPmeXpQmdf6WtTYHhelKJCsQ+tRMiJoFAQoKHoBHAoNxXrhlS3K9lKFLHGmtxsFQfA==}
'@redocly/cli@2.26.0':
resolution: {integrity: sha512-24S1ls0qvu3uaPiW4OImy06CpImAkUOd3h7OG+Hq9By5pPavjOE34KtdQTaaFso3e1qgzXYdQh6HPqEY1nTZgA==}
'@redocly/cli@2.28.0':
resolution: {integrity: sha512-hAHtMjo4fLdLqZXtZwQqlwGnAiOzEAh7EPbE01rs9j7cewj2btOXrGQW8v6Eg3gDh+i77/DOxxazRWvZ/zAa7w==}
engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'}
hasBin: true
'@redocly/config@0.22.2':
resolution: {integrity: sha512-roRDai8/zr2S9YfmzUfNhKjOF0NdcOIqF7bhf4MVC5UxpjIysDjyudvlAiVbpPHp3eDRWbdzUgtkK1a7YiDNyQ==}
'@redocly/config@0.46.1':
resolution: {integrity: sha512-dSdkB2wRLtvl3f7ayRu9vqVhUMjjRaxZlHgRbgOtPPXxn4uI/ciDO87h4CJb7Iet+OVpevpAU6gU8bo5qVbQxg==}
'@redocly/config@0.48.0':
resolution: {integrity: sha512-8W3wz+Q7y4e9klJWlYOvQWK5r7P2Mo589vcjtlT5coOxsyAdt53k8Vb8iAqnRiGWExbjBQmSbL2XbuU747Nf6Q==}
'@redocly/openapi-core@1.34.5':
resolution: {integrity: sha512-0EbE8LRbkogtcCXU7liAyC00n9uNG9hJ+eMyHFdUsy9lB/WGqnEBgwjA9q2cyzAVcdTkQqTBBU1XePNnN3OijA==}
engines: {node: '>=18.17.0', npm: '>=9.5.0'}
'@redocly/openapi-core@2.26.0':
resolution: {integrity: sha512-BjTPzSV1Gv430W9S/7i5T/dEZDK00GFk6ILCNTI+31pA9lEFJOXc0XRJT+V3v+m3nXIgGoo6GgqeLdAiM10rNg==}
'@redocly/openapi-core@2.28.0':
resolution: {integrity: sha512-Htpp4PsjKMgEuMT9iJu4iuFFzWCDe8FylvpGaQEA5D7jZXWv+8XvnqhpGCKN2cM/n/Uri2QfqNdw0JlKIC59sg==}
engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'}
'@redocly/respect-core@2.26.0':
resolution: {integrity: sha512-mejFg26XNp8pqHwnL75QvI7MO4dhgFKa+v35OgOcVMrU9tGZ/VaFbplEyvdrRgjoonguXoLDoMN4Iw1rWlZg0g==}
'@redocly/respect-core@2.28.0':
resolution: {integrity: sha512-svjCRzXsj/EyN7chfB9pTVYvWT1+hlOqMkZVlkrH6PqFKXAHYeP47YRW9+3omUSDBd1Ph4A4J4NBUW1PRph5+g==}
engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'}
'@replit/codemirror-indentation-markers@6.5.3':
@@ -6770,8 +6770,8 @@ packages:
resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}
engines: {node: '>=8'}
ai@6.0.159:
resolution: {integrity: sha512-S18ozG7Dkm3Ud1tzOtAK5acczD4vygfml80RkpM9VWMFpvAFwAKSHaGYkATvPQHIE+VpD1tJY9zcTXLZ/zR5cw==}
ai@6.0.161:
resolution: {integrity: sha512-ufhmijmx2YyWTPAicGgtpLOB/xD7mG8zKs1pT1Trj+JL/3r1rS8fkMi/cHZoChSAQSGB4pgmcWVxDrVTUvK2IQ==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.25.76 || ^4.1.8
@@ -10860,9 +10860,6 @@ packages:
engines: {node: '>=10'}
hasBin: true
mlly@1.7.4:
resolution: {integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==}
mlly@1.8.0:
resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==}
@@ -11891,8 +11888,8 @@ packages:
peerDependencies:
react: ^19.2.4
react-i18next@17.0.2:
resolution: {integrity: sha512-shBftH2vaTWK2Bsp7FiL+cevx3xFJlvFxmsDFQSrJc+6twHkP0tv/bGa01VVWzpreUVVwU+3Hev5iFqRg65RwA==}
react-i18next@17.0.3:
resolution: {integrity: sha512-x4xjvUNZ56T+zfXWNedNnCET9Xq1IBYWX7IsWo5cCQ/RT+Rm7GWqt0h9PShFi4IhyMnsdiu1C6Jc4DE+/S3PFQ==}
peerDependencies:
i18next: '>= 26.0.1'
react: '>= 16.8.0'
@@ -14099,8 +14096,8 @@ packages:
resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==}
engines: {node: '>=20'}
wxt@0.20.21:
resolution: {integrity: sha512-rDocJ9QEWKnGd7NTTTxKIJ5MsXwv9lXk4/ITFS9eeksTCYotshxzAiptkRPEHCgsJNiGMNY/UFNxVDyqWGe7Bw==}
wxt@0.20.22:
resolution: {integrity: sha512-njFI77H0dAbK/bQCN8u8QYiusg6GKDPMtsQDCqIfrh1oGHMHgrMEMgeGOlqAltG9OOGGB1DvFYDzTvxqfEKVKQ==}
engines: {bun: '>=1.2.0', node: '>=20.12.0'}
hasBin: true
peerDependencies:
@@ -14270,7 +14267,7 @@ snapshots:
'@ai-sdk/provider-utils': 4.0.23(zod@4.3.6)
zod: 4.3.6
'@ai-sdk/gateway@3.0.96(zod@4.3.6)':
'@ai-sdk/gateway@3.0.98(zod@4.3.6)':
dependencies:
'@ai-sdk/provider': 3.0.8
'@ai-sdk/provider-utils': 4.0.23(zod@4.3.6)
@@ -14283,7 +14280,7 @@ snapshots:
'@ai-sdk/provider-utils': 4.0.23(zod@4.3.6)
zod: 4.3.6
'@ai-sdk/openai@3.0.52(zod@4.3.6)':
'@ai-sdk/openai@3.0.53(zod@4.3.6)':
dependencies:
'@ai-sdk/provider': 3.0.8
'@ai-sdk/provider-utils': 4.0.23(zod@4.3.6)
@@ -18747,15 +18744,15 @@ snapshots:
dependencies:
ulid: 2.4.0
'@redocly/cli@2.26.0(@opentelemetry/api@1.9.0)(bufferutil@4.0.9)(core-js@3.46.0)(encoding@0.1.13)(utf-8-validate@6.0.5)':
'@redocly/cli@2.28.0(@opentelemetry/api@1.9.0)(bufferutil@4.0.9)(core-js@3.46.0)(encoding@0.1.13)(utf-8-validate@6.0.5)':
dependencies:
'@opentelemetry/exporter-trace-otlp-http': 0.202.0(@opentelemetry/api@1.9.0)
'@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.0)
'@opentelemetry/sdk-trace-node': 2.0.1(@opentelemetry/api@1.9.0)
'@opentelemetry/semantic-conventions': 1.34.0
'@redocly/cli-otel': 0.1.2
'@redocly/openapi-core': 2.26.0
'@redocly/respect-core': 2.26.0
'@redocly/openapi-core': 2.28.0
'@redocly/respect-core': 2.28.0
abort-controller: 3.0.0
ajv: '@redocly/ajv@8.18.0'
ajv-formats: 3.0.1(@redocly/ajv@8.18.0)
@@ -18789,7 +18786,7 @@ snapshots:
'@redocly/config@0.22.2': {}
'@redocly/config@0.46.1':
'@redocly/config@0.48.0':
dependencies:
json-schema-to-ts: 2.7.2
@@ -18807,10 +18804,10 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@redocly/openapi-core@2.26.0':
'@redocly/openapi-core@2.28.0':
dependencies:
'@redocly/ajv': 8.18.0
'@redocly/config': 0.46.1
'@redocly/config': 0.48.0
ajv: '@redocly/ajv@8.18.0'
ajv-formats: 3.0.1(@redocly/ajv@8.18.0)
colorette: 1.4.0
@@ -18820,12 +18817,12 @@ snapshots:
pluralize: 8.0.0
yaml-ast-parser: 0.0.43
'@redocly/respect-core@2.26.0':
'@redocly/respect-core@2.28.0':
dependencies:
'@faker-js/faker': 7.6.0
'@noble/hashes': 1.8.0
'@redocly/ajv': 8.18.0
'@redocly/openapi-core': 2.26.0
'@redocly/openapi-core': 2.28.0
ajv: '@redocly/ajv@8.18.0'
better-ajv-errors: 1.2.0(@redocly/ajv@8.18.0)
colorette: 2.0.20
@@ -21577,12 +21574,12 @@ snapshots:
optionalDependencies:
react: 19.2.4
'@wxt-dev/auto-icons@1.1.1(wxt@0.20.21(@types/node@24.12.2)(eslint@10.2.0(jiti@2.6.1))(jiti@2.6.1)(less@4.1.3)(rollup@4.60.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))':
'@wxt-dev/auto-icons@1.1.1(wxt@0.20.22(@types/node@24.12.2)(eslint@10.2.0(jiti@2.6.1))(jiti@2.6.1)(less@4.1.3)(rollup@4.60.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))':
dependencies:
defu: 6.1.6
fs-extra: 11.3.4
sharp: 0.34.5
wxt: 0.20.21(@types/node@24.12.2)(eslint@10.2.0(jiti@2.6.1))(jiti@2.6.1)(less@4.1.3)(rollup@4.60.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)
wxt: 0.20.22(@types/node@24.12.2)(eslint@10.2.0(jiti@2.6.1))(jiti@2.6.1)(less@4.1.3)(rollup@4.60.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)
'@wxt-dev/browser@0.1.40':
dependencies:
@@ -21668,9 +21665,9 @@ snapshots:
clean-stack: 2.2.0
indent-string: 4.0.0
ai@6.0.159(zod@4.3.6):
ai@6.0.161(zod@4.3.6):
dependencies:
'@ai-sdk/gateway': 3.0.96(zod@4.3.6)
'@ai-sdk/gateway': 3.0.98(zod@4.3.6)
'@ai-sdk/provider': 3.0.8
'@ai-sdk/provider-utils': 4.0.23(zod@4.3.6)
'@opentelemetry/api': 1.9.0
@@ -24676,7 +24673,7 @@ snapshots:
glob@13.0.6:
dependencies:
minimatch: 10.2.4
minimatch: 10.2.5
minipass: 7.1.3
path-scurry: 2.0.2
@@ -25924,7 +25921,7 @@ snapshots:
local-pkg@1.1.1:
dependencies:
mlly: 1.7.4
mlly: 1.8.0
pkg-types: 2.3.0
quansync: 0.2.10
@@ -26707,13 +26704,6 @@ snapshots:
mkdirp@1.0.4: {}
mlly@1.7.4:
dependencies:
acorn: 8.16.0
pathe: 2.0.3
pkg-types: 1.3.1
ufo: 1.6.1
mlly@1.8.0:
dependencies:
acorn: 8.16.0
@@ -27813,7 +27803,7 @@ snapshots:
react: 19.2.4
scheduler: 0.27.0
react-i18next@17.0.2(i18next@26.0.4(typescript@6.0.2))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@6.0.2):
react-i18next@17.0.3(i18next@26.0.4(typescript@6.0.2))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@6.0.2):
dependencies:
'@babel/runtime': 7.29.2
html-parse-stringify: 3.0.1
@@ -30417,7 +30407,7 @@ snapshots:
is-wsl: 3.1.1
powershell-utils: 0.1.0
wxt@0.20.21(@types/node@24.12.2)(eslint@10.2.0(jiti@2.6.1))(jiti@2.6.1)(less@4.1.3)(rollup@4.60.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3):
wxt@0.20.22(@types/node@24.12.2)(eslint@10.2.0(jiti@2.6.1))(jiti@2.6.1)(less@4.1.3)(rollup@4.60.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3):
dependencies:
'@1natsu/wait-element': 4.1.2
'@aklinker1/rollup-plugin-visualizer': 5.12.0(rollup@4.60.1)