Compare commits

..

1 Commits

Author SHA1 Message Date
SiriusXT
5fb7badfb4 fix(rightPane): toggling right pane visibility incorrectly affects all windows 2025-12-31 19:54:31 +08:00
37 changed files with 177 additions and 1586 deletions

View File

@@ -9,9 +9,9 @@
"keywords": [],
"author": "Elian Doran <contact@eliandoran.me>",
"license": "AGPL-3.0-only",
"packageManager": "pnpm@10.27.0",
"packageManager": "pnpm@10.26.2",
"devDependencies": {
"@redocly/cli": "2.14.2",
"@redocly/cli": "2.14.1",
"archiver": "7.0.1",
"fs-extra": "11.3.3",
"react": "19.2.3",

View File

@@ -473,11 +473,6 @@ type EventMappings = {
noteContextRemoved: {
ntxIds: string[];
};
contextDataChanged: {
noteContext: NoteContext;
key: string;
value: unknown;
};
exportSvg: { ntxId: string | null | undefined; };
exportPng: { ntxId: string | null | undefined; };
geoMapCreateChildNote: {

View File

@@ -12,7 +12,6 @@ import server from "../services/server.js";
import treeService from "../services/tree.js";
import utils from "../services/utils.js";
import { ReactWrappedWidget } from "../widgets/basic_widget.js";
import type { HeadingContext } from "../widgets/sidebar/TableOfContents.js";
import appContext, { type EventData, type EventListener } from "./app_context.js";
import Component from "./component.js";
@@ -23,26 +22,6 @@ export interface SetNoteOpts {
export type GetTextEditorCallback = (editor: CKTextEditor) => void;
export interface NoteContextDataMap {
toc: HeadingContext;
pdfPages: {
totalPages: number;
currentPage: number;
scrollToPage(page: number): void;
requestThumbnail(page: number): void;
};
pdfAttachments: {
attachments: Array<{ filename: string; size: number }>;
downloadAttachment(filename: string): void;
};
pdfLayers: {
layers: Array<{ id: string; name: string; visible: boolean }>;
toggleLayer(layerId: string, visible: boolean): void;
};
}
type ContextDataKey = keyof NoteContextDataMap;
class NoteContext extends Component implements EventListener<"entitiesReloaded"> {
ntxId: string | null;
hoistedNoteId: string;
@@ -53,13 +32,6 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded">
parentNoteId?: string | null;
viewScope?: ViewScope;
/**
* Metadata storage for UI components (e.g., table of contents, PDF page list, code outline).
* This allows type widgets to publish data that sidebar/toolbar components can consume.
* Data is automatically cleared when navigating to a different note.
*/
private contextData: Map<string, unknown> = new Map();
constructor(ntxId: string | null = null, hoistedNoteId: string = "root", mainNtxId: string | null = null) {
super();
@@ -119,17 +91,6 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded">
this.viewScope = opts.viewScope;
({ noteId: this.noteId, parentNoteId: this.parentNoteId } = treeService.getNoteIdAndParentIdFromUrl(resolvedNotePath));
// Clear context data when switching notes and notify subscribers
const oldKeys = Array.from(this.contextData.keys());
this.contextData.clear();
for (const key of oldKeys) {
this.triggerEvent("contextDataChanged", {
noteContext: this,
key,
value: undefined
});
}
this.saveToRecentNotes(resolvedNotePath);
protectedSessionHolder.touchProtectedSessionIfNecessary(this.note);
@@ -482,52 +443,6 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded">
return title;
}
/**
* Set metadata for this note context (e.g., table of contents, PDF pages, code outline).
* This data can be consumed by sidebar/toolbar components.
*
* @param key - Unique identifier for the data type (e.g., "toc", "pdfPages", "codeOutline")
* @param value - The data to store (will be cleared when switching notes)
*/
setContextData<K extends ContextDataKey>(key: K, value: NoteContextDataMap[K]): void {
this.contextData.set(key, value);
// Trigger event so subscribers can react
this.triggerEvent("contextDataChanged", {
noteContext: this,
key,
value
});
}
/**
* Get metadata for this note context.
*
* @param key - The data key to retrieve
* @returns The stored data, or undefined if not found
*/
getContextData<K extends ContextDataKey>(key: K): NoteContextDataMap[K] | undefined {
return this.contextData.get(key) as NoteContextDataMap[K] | undefined;
}
/**
* Check if context data exists for a given key.
*/
hasContextData(key: ContextDataKey): boolean {
return this.contextData.has(key);
}
/**
* Clear specific context data.
*/
clearContextData(key: ContextDataKey): void {
this.contextData.delete(key);
this.triggerEvent("contextDataChanged", {
noteContext: this,
key,
value: undefined
});
}
}
export function openInCurrentNoteContext(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDownEvent | React.PointerEvent<HTMLCanvasElement> | null, notePath: string, viewScope?: ViewScope) {

View File

@@ -187,15 +187,13 @@ export function formatSize(size: number | null | undefined) {
return "";
}
if (size === 0) {
return "0 B";
size = Math.max(Math.round(size / 1024), 1);
if (size < 1024) {
return `${size} KiB`;
}
return `${Math.round(size / 102.4) / 10} MiB`;
const k = 1024;
const sizes = ["B", "KB", "MB", "GB"];
const i = Math.floor(Math.log(size) / Math.log(k));
return `${Math.round((size / Math.pow(k, i)) * 100) / 100} ${sizes[i]}`;
}
function toObject<T, R>(array: T[], fn: (arg0: T) => [key: string, value: R]) {

View File

@@ -2234,13 +2234,5 @@
"empty_button": "Hide the panel",
"toggle": "Toggle right panel",
"custom_widget_go_to_source": "Go to source code"
},
"pdf": {
"attachments_one": "{{count}} attachment",
"attachments_other": "{{count}} attachments",
"layers_one": "{{count}} layer",
"layers_other": "{{count}} layers",
"pages_one": "{{count}} page",
"pages_other": "{{count}} pages"
}
}

View File

@@ -1,15 +1,3 @@
interface Window {
/**
* By default, pdf.js will try to store information about the opened PDFs such as zoom and scroll position in local storage.
* The Trilium alternative is to use attachments stored at note level.
* This variable represents the direct content used by the pdf.js viewer in its local storage key, but in plain JS object format.
* The variable must be set early at startup, before pdf.js fully initializes.
*/
TRILIUM_VIEW_HISTORY_STORE?: object;
/**
* If set to true, hides the pdf.js viewer default sidebar containing the outline, page navigation, etc.
* This needs to be set early in the main method.
*/
TRILIUM_HIDE_SIDEBAR?: boolean;
}

View File

@@ -3,9 +3,12 @@ import clsx from "clsx";
import { t } from "../../services/i18n";
import ActionButton from "../react/ActionButton";
import { useTriliumOptionBool } from "../react/hooks";
import { useState } from "preact/hooks";
import appContext from "../../components/app_context";
export default function RightPaneToggle() {
const [ rightPaneVisible, setRightPaneVisible ] = useTriliumOptionBool("rightPaneVisible");
const [rightPaneVisibleOption, setRightPaneVisibleOption] = useTriliumOptionBool("rightPaneVisible");
const [rightPaneVisible, setRightPaneVisible] = useState(rightPaneVisibleOption);
return (
<ActionButton
@@ -15,7 +18,11 @@ export default function RightPaneToggle() {
)}
text={t("right_pane.toggle")}
icon="bx bx-sidebar"
onClick={() => setRightPaneVisible(!rightPaneVisible)}
onClick={() => {
setRightPaneVisible(!rightPaneVisible);
setRightPaneVisibleOption(!rightPaneVisible);
appContext.triggerEvent("toggleRightPane", {});
}}
/>
);
}

View File

@@ -4,8 +4,7 @@
*/
import { NoteType } from "@triliumnext/commons";
import { type JSX,VNode } from "preact";
import { VNode, type JSX } from "preact";
import { TypeWidgetProps } from "./type_widgets/type_widget";
/**
@@ -14,7 +13,7 @@ import { TypeWidgetProps } from "./type_widgets/type_widget";
*/
export type ExtendedNoteType = Exclude<NoteType, "launcher" | "text" | "code"> | "empty" | "readOnlyCode" | "readOnlyText" | "editableText" | "editableCode" | "attachmentDetail" | "attachmentList" | "protectedSession" | "aiChat";
export type TypeWidget = ((props: TypeWidgetProps) => VNode | JSX.Element | undefined);
export type TypeWidget = ((props: TypeWidgetProps) => VNode | JSX.Element);
type NoteTypeView = () => (Promise<{ default: TypeWidget } | TypeWidget> | TypeWidget);
interface NoteTypeMapping {

View File

@@ -8,7 +8,7 @@ import { MutableRef, useCallback, useContext, useDebugValue, useEffect, useLayou
import appContext, { EventData, EventNames } from "../../components/app_context";
import Component from "../../components/component";
import NoteContext, { NoteContextDataMap } from "../../components/note_context";
import NoteContext from "../../components/note_context";
import FBlob from "../../entities/fblob";
import FNote from "../../entities/fnote";
import attributes from "../../services/attributes";
@@ -1192,113 +1192,3 @@ export function useContentElement(noteContext: NoteContext | null | undefined) {
return contentElement;
}
/**
* Set context data on the current note context.
* This allows type widgets to publish data (e.g., table of contents, PDF pages)
* that can be consumed by sidebar/toolbar components.
*
* Data is automatically cleared when navigating to a different note.
*
* @param key - Unique identifier for the data type (e.g., "toc", "pdfPages")
* @param value - The data to publish
*
* @example
* // In a PDF viewer widget:
* const { noteContext } = useActiveNoteContext();
* useSetContextData(noteContext, "pdfPages", pages);
*/
export function useSetContextData<K extends keyof NoteContextDataMap>(
noteContext: NoteContext | null | undefined,
key: K,
value: NoteContextDataMap[K] | undefined
) {
const valueRef = useRef<NoteContextDataMap[K] | undefined>(value);
valueRef.current = value;
useEffect(() => {
if (!noteContext || valueRef.current === undefined) return;
noteContext.setContextData(key, valueRef.current);
return () => {
noteContext.clearContextData(key);
};
}, [noteContext, key]);
// Update when value changes
useEffect(() => {
if (!noteContext || value === undefined) return;
noteContext.setContextData(key, value);
}, [noteContext, key, value]);
}
/**
* Get context data from the active note context.
* This is typically used in sidebar/toolbar components that need to display
* data published by type widgets.
*
* The component will automatically re-render when the data changes.
*
* @param key - The data key to retrieve (e.g., "toc", "pdfPages")
* @returns The current data, or undefined if not available
*
* @example
* // In a Table of Contents sidebar widget:
* function TableOfContents() {
* const headings = useGetContextData<Heading[]>("toc");
* if (!headings) return <div>No headings available</div>;
* return <ul>{headings.map(h => <li>{h.text}</li>)}</ul>;
* }
*/
export function useGetContextData<K extends keyof NoteContextDataMap>(key: K): NoteContextDataMap[K] | undefined {
const { noteContext } = useActiveNoteContext();
const [data, setData] = useState<NoteContextDataMap[K] | undefined>(() =>
noteContext?.getContextData(key)
);
// Update initial value when noteContext changes
useEffect(() => {
setData(noteContext?.getContextData(key));
}, [noteContext, key]);
// Subscribe to changes via Trilium event system
useTriliumEvent("contextDataChanged", ({ noteContext: eventNoteContext, key: changedKey, value }) => {
if (eventNoteContext === noteContext && changedKey === key) {
setData(value as NoteContextDataMap[K]);
}
});
return data;
}
/**
* Get context data from a specific note context (not necessarily the active one).
*
* @param noteContext - The specific note context to get data from
* @param key - The data key to retrieve
* @returns The current data, or undefined if not available
*/
export function useGetContextDataFrom<K extends keyof NoteContextDataMap>(
noteContext: NoteContext | null | undefined,
key: K
): NoteContextDataMap[K] | undefined {
const [data, setData] = useState<NoteContextDataMap[K] | undefined>(() =>
noteContext?.getContextData(key)
);
// Update initial value when noteContext changes
useEffect(() => {
setData(noteContext?.getContextData(key));
}, [noteContext, key]);
// Subscribe to changes via Trilium event system
useTriliumEvent("contextDataChanged", ({ noteContext: eventNoteContext, key: changedKey, value }) => {
if (eventNoteContext === noteContext && changedKey === key) {
setData(value as NoteContextDataMap[K]);
}
});
return data;
}

View File

@@ -3,7 +3,7 @@ import "./RightPanelContainer.css";
import Split from "@triliumnext/split.js";
import { VNode } from "preact";
import { useEffect, useRef } from "preact/hooks";
import { useState, useEffect, useRef } from "preact/hooks";
import appContext from "../../components/app_context";
import { WidgetsByParent } from "../../services/bundle";
@@ -15,9 +15,6 @@ import { useActiveNoteContext, useLegacyWidget, useNoteProperty, useTriliumEvent
import Icon from "../react/Icon";
import LegacyRightPanelWidget from "../right_panel_widget";
import HighlightsList from "./HighlightsList";
import PdfAttachments from "./pdf/PdfAttachments";
import PdfLayers from "./pdf/PdfLayers";
import PdfPages from "./pdf/PdfPages";
import RightPanelWidget from "./RightPanelWidget";
import TableOfContents from "./TableOfContents";
@@ -30,10 +27,12 @@ interface RightPanelWidgetDefinition {
}
export default function RightPanelContainer({ widgetsByParent }: { widgetsByParent: WidgetsByParent }) {
const [ rightPaneVisible, setRightPaneVisible ] = useTriliumOptionBool("rightPaneVisible");
const [rightPaneVisibleOption, setRightPaneVisibleOption] = useTriliumOptionBool("rightPaneVisible");
const [rightPaneVisible, setRightPaneVisible] = useState(rightPaneVisibleOption);
const items = useItems(rightPaneVisible, widgetsByParent);
useSplit(rightPaneVisible);
useTriliumEvent("toggleRightPane", () => {
setRightPaneVisibleOption(!rightPaneVisible);
setRightPaneVisible(!rightPaneVisible);
});
@@ -48,7 +47,10 @@ export default function RightPanelContainer({ widgetsByParent }: { widgetsByPare
{t("right_pane.empty_message")}
<Button
text={t("right_pane.empty_button")}
onClick={() => setRightPaneVisible(!rightPaneVisible)}
onClick={() => {
setRightPaneVisibleOption(!rightPaneVisible);
setRightPaneVisible(!rightPaneVisible);
}}
/>
</div>
)
@@ -60,27 +62,13 @@ export default function RightPanelContainer({ widgetsByParent }: { widgetsByPare
function useItems(rightPaneVisible: boolean, widgetsByParent: WidgetsByParent) {
const { note } = useActiveNoteContext();
const noteType = useNoteProperty(note, "type");
const noteMime = useNoteProperty(note, "mime");
const [ highlightsList ] = useTriliumOptionJson<string[]>("highlightsList");
const isPdf = noteType === "file" && noteMime === "application/pdf";
if (!rightPaneVisible) return [];
const definitions: RightPanelWidgetDefinition[] = [
{
el: <TableOfContents />,
enabled: (noteType === "text" || noteType === "doc" || isPdf),
},
{
el: <PdfPages />,
enabled: isPdf,
},
{
el: <PdfAttachments />,
enabled: isPdf,
},
{
el: <PdfLayers />,
enabled: isPdf,
enabled: (noteType === "text" || noteType === "doc"),
},
{
el: <HighlightsList />,

View File

@@ -29,11 +29,6 @@
hyphens: auto;
}
.toc li.active > .item-content {
font-weight: bold;
color: var(--main-text-color);
}
.toc > ol {
--toc-depth-level: 1;
}

View File

@@ -6,7 +6,7 @@ import { useCallback, useEffect, useState } from "preact/hooks";
import { t } from "../../services/i18n";
import { randomString } from "../../services/utils";
import { useActiveNoteContext, useContentElement, useGetContextData, useIsNoteReadOnly, useNoteProperty, useTextEditor } from "../react/hooks";
import { useActiveNoteContext, useContentElement, useIsNoteReadOnly, useNoteProperty, useTextEditor } from "../react/hooks";
import Icon from "../react/Icon";
import RightPanelWidget from "./RightPanelWidget";
@@ -21,50 +21,29 @@ interface HeadingsWithNesting extends RawHeading {
children: HeadingsWithNesting[];
}
export interface HeadingContext {
scrollToHeading(heading: RawHeading): void;
headings: RawHeading[];
activeHeadingId?: string | null;
}
export default function TableOfContents() {
const { note, noteContext } = useActiveNoteContext();
const noteType = useNoteProperty(note, "type");
const noteMime = useNoteProperty(note, "mime");
const { isReadOnly } = useIsNoteReadOnly(note, noteContext);
return (
<RightPanelWidget id="toc" title={t("toc.table_of_contents")} grow>
{((noteType === "text" && isReadOnly) || (noteType === "doc")) && <ReadOnlyTextTableOfContents />}
{noteType === "text" && !isReadOnly && <EditableTextTableOfContents />}
{noteType === "file" && noteMime === "application/pdf" && <PdfTableOfContents />}
</RightPanelWidget>
);
}
function PdfTableOfContents() {
const data = useGetContextData("toc");
return (
<AbstractTableOfContents
headings={data?.headings || []}
scrollToHeading={data?.scrollToHeading || (() => {})}
activeHeadingId={data?.activeHeadingId}
/>
);
}
function AbstractTableOfContents<T extends RawHeading>({ headings, scrollToHeading, activeHeadingId }: {
function AbstractTableOfContents<T extends RawHeading>({ headings, scrollToHeading }: {
headings: T[];
scrollToHeading(heading: T): void;
activeHeadingId?: string | null;
}) {
const nestedHeadings = buildHeadingTree(headings);
return (
<span className="toc">
{nestedHeadings.length > 0 ? (
<ol>
{nestedHeadings.map(heading => <TableOfContentsHeading key={heading.id} heading={heading} scrollToHeading={scrollToHeading} activeHeadingId={activeHeadingId} />)}
{nestedHeadings.map(heading => <TableOfContentsHeading key={heading.id} heading={heading} scrollToHeading={scrollToHeading} />)}
</ol>
) : (
<div className="no-headings">{t("toc.no_headings")}</div>
@@ -73,16 +52,14 @@ function AbstractTableOfContents<T extends RawHeading>({ headings, scrollToHeadi
);
}
function TableOfContentsHeading({ heading, scrollToHeading, activeHeadingId }: {
function TableOfContentsHeading({ heading, scrollToHeading }: {
heading: HeadingsWithNesting;
scrollToHeading(heading: RawHeading): void;
activeHeadingId?: string | null;
}) {
const [ collapsed, setCollapsed ] = useState(false);
const isActive = heading.id === activeHeadingId;
return (
<>
<li className={clsx(collapsed && "collapsed", isActive && "active")}>
<li className={clsx(collapsed && "collapsed")}>
{heading.children.length > 0 && (
<Icon
className="collapse-button"
@@ -97,7 +74,7 @@ function TableOfContentsHeading({ heading, scrollToHeading, activeHeadingId }: {
</li>
{heading.children && (
<ol>
{heading.children.map(heading => <TableOfContentsHeading key={heading.id} heading={heading} scrollToHeading={scrollToHeading} activeHeadingId={activeHeadingId} />)}
{heading.children.map(heading => <TableOfContentsHeading key={heading.id} heading={heading} scrollToHeading={scrollToHeading} />)}
</ol>
)}
</>

View File

@@ -1,57 +0,0 @@
.pdf-attachments-list {
width: 100%;
}
.pdf-attachment-item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
cursor: pointer;
border-bottom: 1px solid var(--main-border-color);
transition: background-color 0.2s;
}
.pdf-attachment-item:hover {
background-color: var(--hover-item-background-color);
}
.pdf-attachment-item:last-child {
border-bottom: none;
}
.pdf-attachment-info {
flex: 1;
min-width: 0;
}
.pdf-attachment-filename {
font-size: 13px;
font-weight: 500;
color: var(--main-text-color);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.pdf-attachment-size {
font-size: 11px;
color: var(--muted-text-color);
margin-top: 2px;
}
.no-attachments {
padding: 16px;
text-align: center;
color: var(--muted-text-color);
}
.pdf-attachment-item .bx {
flex-shrink: 0;
font-size: 18px;
color: var(--muted-text-color);
}
.pdf-attachment-item:hover .bx {
color: var(--main-text-color);
}

View File

@@ -1,62 +0,0 @@
import "./PdfAttachments.css";
import { t } from "../../../services/i18n";
import { formatSize } from "../../../services/utils";
import { useActiveNoteContext, useGetContextData, useNoteProperty } from "../../react/hooks";
import Icon from "../../react/Icon";
import RightPanelWidget from "../RightPanelWidget";
interface AttachmentInfo {
filename: string;
size: number;
}
export default function PdfAttachments() {
const { note } = useActiveNoteContext();
const noteType = useNoteProperty(note, "type");
const noteMime = useNoteProperty(note, "mime");
const attachmentsData = useGetContextData("pdfAttachments");
if (noteType !== "file" || noteMime !== "application/pdf") {
return null;
}
if (!attachmentsData || attachmentsData.attachments.length === 0) {
return null;
}
return (
<RightPanelWidget id="pdf-attachments" title={t("pdf.attachments", { count: attachmentsData.attachments.length })}>
<div className="pdf-attachments-list">
{attachmentsData.attachments.map((attachment) => (
<PdfAttachmentItem
key={attachment.filename}
attachment={attachment}
onDownload={attachmentsData.downloadAttachment}
/>
))}
</div>
</RightPanelWidget>
);
}
function PdfAttachmentItem({
attachment,
onDownload
}: {
attachment: AttachmentInfo;
onDownload: (filename: string) => void;
}) {
const sizeText = formatSize(attachment.size);
return (
<div className="pdf-attachment-item" onClick={() => onDownload(attachment.filename)}>
<Icon icon="bx bx-paperclip" />
<div className="pdf-attachment-info">
<div className="pdf-attachment-filename">{attachment.filename}</div>
<div className="pdf-attachment-size">{sizeText}</div>
</div>
<Icon icon="bx bx-download" />
</div>
);
}

View File

@@ -1,54 +0,0 @@
.pdf-layers-list {
width: 100%;
}
.pdf-layer-item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
cursor: pointer;
border-bottom: 1px solid var(--main-border-color);
transition: background-color 0.2s;
}
.pdf-layer-item:hover {
background-color: var(--hover-item-background-color);
}
.pdf-layer-item:last-child {
border-bottom: none;
}
.pdf-layer-item.hidden {
opacity: 0.5;
}
.pdf-layer-name {
flex: 1;
font-size: 13px;
color: var(--main-text-color);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.no-layers {
padding: 16px;
text-align: center;
color: var(--muted-text-color);
}
.pdf-layer-item .bx {
flex-shrink: 0;
font-size: 18px;
color: var(--muted-text-color);
}
.pdf-layer-item:hover .bx {
color: var(--main-text-color);
}
.pdf-layer-item.visible .bx {
color: var(--main-text-color);
}

View File

@@ -1,55 +0,0 @@
import "./PdfLayers.css";
import { t } from "../../../services/i18n";
import { useActiveNoteContext, useGetContextData, useNoteProperty } from "../../react/hooks";
import Icon from "../../react/Icon";
import RightPanelWidget from "../RightPanelWidget";
interface LayerInfo {
id: string;
name: string;
visible: boolean;
}
export default function PdfLayers() {
const { note } = useActiveNoteContext();
const noteType = useNoteProperty(note, "type");
const noteMime = useNoteProperty(note, "mime");
const layersData = useGetContextData("pdfLayers");
if (noteType !== "file" || noteMime !== "application/pdf") {
return null;
}
return (layersData?.layers && layersData.layers.length > 0 &&
<RightPanelWidget id="pdf-layers" title={t("pdf.layers", { count: layersData.layers.length })}>
<div className="pdf-layers-list">
{layersData.layers.map((layer) => (
<PdfLayerItem
key={layer.id}
layer={layer}
onToggle={layersData.toggleLayer}
/>
))}
</div>
</RightPanelWidget>
);
}
function PdfLayerItem({
layer,
onToggle
}: {
layer: LayerInfo;
onToggle: (layerId: string, visible: boolean) => void;
}) {
return (
<div
className={`pdf-layer-item ${layer.visible ? 'visible' : 'hidden'}`}
onClick={() => onToggle(layer.id, !layer.visible)}
>
<Icon icon={layer.visible ? "bx bx-show" : "bx bx-hide"} />
<div className="pdf-layer-name">{layer.name}</div>
</div>
);
}

View File

@@ -1,67 +0,0 @@
.pdf-pages-list {
width: 100%;
height: 100%;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 8px;
padding: 8px;
align-content: flex-start;
}
.pdf-page-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 8px;
cursor: pointer;
border: 2px solid transparent;
transition: border-color 0.2s;
box-sizing: border-box;
position: relative;
.pdf-page-number {
font-size: 12px;
margin-bottom: 4px;
color: var(--main-text-color);
position: absolute;
bottom: 1em;
left: 50%;
transform: translateX(-50%);
background-color: var(--accented-background-color);
padding: 0.2em 0.5em;
border-radius: 4px;
}
}
.pdf-page-item:hover {
background-color: var(--hover-item-background-color);
}
.pdf-page-item.active {
border-color: var(--main-border-color);
background-color: var(--active-item-background-color);
}
.pdf-page-thumbnail {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.pdf-page-thumbnail img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
.pdf-page-loading {
color: var(--muted-text-color);
font-size: 11px;
}
.no-pages {
padding: 16px;
text-align: center;
color: var(--muted-text-color);
}

View File

@@ -1,111 +0,0 @@
import "./PdfPages.css";
import { useCallback, useEffect, useRef, useState } from "preact/hooks";
import { NoteContextDataMap } from "../../../components/note_context";
import { t } from "../../../services/i18n";
import { useActiveNoteContext, useGetContextData, useNoteProperty } from "../../react/hooks";
import RightPanelWidget from "../RightPanelWidget";
export default function PdfPages() {
const { note } = useActiveNoteContext();
const noteType = useNoteProperty(note, "type");
const noteMime = useNoteProperty(note, "mime");
const pagesData = useGetContextData("pdfPages");
if (noteType !== "file" || noteMime !== "application/pdf") {
return null;
}
return (pagesData &&
<RightPanelWidget id="pdf-pages" title={t("pdf.pages", { count: pagesData?.totalPages || 0 })} grow>
<PdfPagesList key={note?.noteId} pagesData={pagesData} />
</RightPanelWidget>
);
}
function PdfPagesList({ pagesData }: { pagesData: NoteContextDataMap["pdfPages"] }) {
const [thumbnails, setThumbnails] = useState<Map<number, string>>(new Map());
const requestedThumbnails = useRef<Set<number>>(new Set());
useEffect(() => {
// Listen for thumbnail responses via custom event
function handleThumbnail(event: CustomEvent) {
const { pageNumber, dataUrl } = event.detail;
setThumbnails(prev => new Map(prev).set(pageNumber, dataUrl));
}
window.addEventListener("pdf-thumbnail", handleThumbnail as EventListener);
return () => {
window.removeEventListener("pdf-thumbnail", handleThumbnail as EventListener);
};
}, []);
const requestThumbnail = useCallback((pageNumber: number) => {
// Only request if we haven't already requested it and don't have it
if (!requestedThumbnails.current.has(pageNumber) && !thumbnails.has(pageNumber) && pagesData) {
requestedThumbnails.current.add(pageNumber);
pagesData.requestThumbnail(pageNumber);
}
}, [pagesData, thumbnails]);
if (!pagesData || pagesData.totalPages === 0) {
return <div className="no-pages">No pages available</div>;
}
const pages = Array.from({ length: pagesData.totalPages }, (_, i) => i + 1);
return (
<div className="pdf-pages-list">
{pages.map(pageNumber => (
<PdfPageItem
key={pageNumber}
pageNumber={pageNumber}
isActive={pageNumber === pagesData.currentPage}
thumbnail={thumbnails.get(pageNumber)}
onRequestThumbnail={requestThumbnail}
onPageClick={() => pagesData.scrollToPage(pageNumber)}
/>
))}
</div>
);
}
function PdfPageItem({
pageNumber,
isActive,
thumbnail,
onRequestThumbnail,
onPageClick
}: {
pageNumber: number;
isActive: boolean;
thumbnail?: string;
onRequestThumbnail(page: number): void;
onPageClick(): void;
}) {
const hasRequested = useRef(false);
useEffect(() => {
if (!thumbnail && !hasRequested.current) {
hasRequested.current = true;
onRequestThumbnail(pageNumber);
}
}, [pageNumber, thumbnail, onRequestThumbnail]);
return (
<div
className={`pdf-page-item ${isActive ? 'active' : ''}`}
onClick={onPageClick}
>
<div className="pdf-page-number">{pageNumber}</div>
<div className="pdf-page-thumbnail">
{thumbnail ? (
<img src={thumbnail} alt={`Page ${pageNumber}`} />
) : (
<div className="pdf-page-loading">Loading...</div>
)}
</div>
</div>
);
}

View File

@@ -16,7 +16,7 @@ export default function FileTypeWidget({ note, parentComponent, noteContext }: T
if (blob?.content) {
return <TextPreview content={blob.content} />;
} else if (note.mime === "application/pdf") {
return noteContext && <PdfPreview blob={blob} note={note} componentId={parentComponent?.componentId} noteContext={noteContext} />;
return <PdfPreview blob={blob} note={note} ntxId={noteContext?.ntxId} componentId={parentComponent?.componentId} />;
} else if (note.mime.startsWith("video/")) {
return <VideoPreview note={note} />;
} else if (note.mime.startsWith("audio/")) {

View File

@@ -2,12 +2,11 @@ import { RefObject } from "preact";
import { useCallback, useEffect, useRef } from "preact/hooks";
import appContext from "../../../components/app_context";
import type NoteContext from "../../../components/note_context";
import FBlob from "../../../entities/fblob";
import FNote from "../../../entities/fnote";
import server from "../../../services/server";
import { useViewModeConfig } from "../../collections/NoteList";
import { useTriliumOption, useTriliumOptionBool } from "../../react/hooks";
import { useTriliumOption } from "../../react/hooks";
const VARIABLE_WHITELIST = new Set([
"root-background",
@@ -16,17 +15,16 @@ const VARIABLE_WHITELIST = new Set([
"main-text-color"
]);
export default function PdfPreview({ note, blob, componentId, noteContext }: {
note: FNote;
noteContext: NoteContext;
blob: FBlob | null | undefined;
export default function PdfPreview({ note, blob, componentId, ntxId }: {
note: FNote,
blob: FBlob | null | undefined,
componentId: string | undefined;
ntxId: string | null | undefined;
}) {
const iframeRef = useRef<HTMLIFrameElement>(null);
const { onLoad } = useStyleInjection(iframeRef);
const historyConfig = useViewModeConfig(note, "pdfHistory");
const [ locale ] = useTriliumOption("locale");
const [ newLayout ] = useTriliumOptionBool("newLayout");
useEffect(() => {
function handleMessage(event: MessageEvent) {
@@ -38,111 +36,13 @@ export default function PdfPreview({ note, blob, componentId, noteContext }: {
if (event.data.type === "pdfjs-viewer-save-view-history" && event.data?.data) {
historyConfig?.storeFn(JSON.parse(event.data.data));
}
if (event.data.type === "pdfjs-viewer-toc") {
if (event.data.data) {
// Convert PDF outline to HeadingContext format
const headings = convertPdfOutlineToHeadings(event.data.data);
noteContext.setContextData("toc", {
headings,
activeHeadingId: null,
scrollToHeading: (heading) => {
iframeRef.current?.contentWindow?.postMessage({
type: "trilium-scroll-to-heading",
headingId: heading.id
}, window.location.origin);
}
});
} else {
// No ToC available, use empty headings
noteContext.setContextData("toc", {
headings: [],
activeHeadingId: null,
scrollToHeading: () => {}
});
}
}
if (event.data.type === "pdfjs-viewer-active-heading") {
const currentToc = noteContext.getContextData("toc");
if (currentToc) {
noteContext.setContextData("toc", {
...currentToc,
activeHeadingId: event.data.headingId
});
}
}
if (event.data.type === "pdfjs-viewer-page-info") {
noteContext.setContextData("pdfPages", {
totalPages: event.data.totalPages,
currentPage: event.data.currentPage,
scrollToPage: (page: number) => {
iframeRef.current?.contentWindow?.postMessage({
type: "trilium-scroll-to-page",
pageNumber: page
}, window.location.origin);
},
requestThumbnail: (page: number) => {
iframeRef.current?.contentWindow?.postMessage({
type: "trilium-request-thumbnail",
pageNumber: page
}, window.location.origin);
}
});
}
if (event.data.type === "pdfjs-viewer-current-page") {
const currentPages = noteContext.getContextData("pdfPages");
if (currentPages) {
noteContext.setContextData("pdfPages", {
...currentPages,
currentPage: event.data.currentPage
});
}
}
if (event.data.type === "pdfjs-viewer-thumbnail") {
// Forward thumbnail to any listeners
window.dispatchEvent(new CustomEvent("pdf-thumbnail", {
detail: {
pageNumber: event.data.pageNumber,
dataUrl: event.data.dataUrl
}
}));
}
if (event.data.type === "pdfjs-viewer-attachments") {
noteContext.setContextData("pdfAttachments", {
attachments: event.data.attachments,
downloadAttachment: (filename: string) => {
iframeRef.current?.contentWindow?.postMessage({
type: "trilium-download-attachment",
filename
}, window.location.origin);
}
});
}
if (event.data.type === "pdfjs-viewer-layers") {
noteContext.setContextData("pdfLayers", {
layers: event.data.layers,
toggleLayer: (layerId: string, visible: boolean) => {
iframeRef.current?.contentWindow?.postMessage({
type: "trilium-toggle-layer",
layerId,
visible
}, window.location.origin);
}
});
}
}
window.addEventListener("message", handleMessage);
return () => {
window.removeEventListener("message", handleMessage);
};
}, [ note, historyConfig, componentId, blob, noteContext ]);
}, [ note, historyConfig, componentId, blob ]);
// Refresh when blob changes.
useEffect(() => {
@@ -157,8 +57,8 @@ export default function PdfPreview({ note, blob, componentId, noteContext }: {
if (!iframe) return;
const handleIframeClick = () => {
if (noteContext.ntxId) {
appContext.tabManager.activateNoteContext(noteContext.ntxId);
if (ntxId) {
appContext.tabManager.activateNoteContext(ntxId);
}
};
@@ -168,14 +68,14 @@ export default function PdfPreview({ note, blob, componentId, noteContext }: {
iframeDoc.addEventListener('click', handleIframeClick);
return () => iframeDoc.removeEventListener('click', handleIframeClick);
}
}, [ iframeRef.current?.contentWindow, noteContext ]);
}, [ iframeRef.current?.contentWindow, ntxId ]);
return (historyConfig &&
<iframe
tabIndex={300}
ref={iframeRef}
class="pdf-preview"
src={`pdfjs/web/viewer.html?file=../../api/notes/${note.noteId}/open&lang=${locale}&sidebar=${newLayout ? "0" : "1"}`}
src={`pdfjs/web/viewer.html?file=../../api/notes/${note.noteId}/open&lang=${locale}`}
onLoad={() => {
const win = iframeRef.current?.contentWindow;
if (win) {
@@ -238,40 +138,3 @@ function cssVarsToString(vars: Record<string, string>) {
.map(([k, v]) => ` ${k}: ${v};`)
.join('\n')}\n}`;
}
interface PdfOutlineItem {
title: string;
level: number;
dest: unknown;
id: string;
items: PdfOutlineItem[];
}
interface PdfHeading {
level: number;
text: string;
id: string;
element: null;
}
function convertPdfOutlineToHeadings(outline: PdfOutlineItem[]): PdfHeading[] {
const headings: PdfHeading[] = [];
function flatten(items: PdfOutlineItem[]) {
for (const item of items) {
headings.push({
level: item.level + 1,
text: item.title,
id: item.id,
element: null // PDFs don't have DOM elements
});
if (item.items && item.items.length > 0) {
flatten(item.items);
}
}
}
flatten(outline);
return headings;
}

View File

@@ -1,86 +0,0 @@
import { describe } from "node:test";
import test, { BrowserContext, expect, Page } from "@playwright/test";
import { statSync } from "fs";
import App from "../support/app";
describe("PDF sidebar", () => {
test.beforeAll(async ({ page, context }) => {
const app = await setLayout({ page, context }, true);
await app.setOption("rightPaneCollapsedItems", "[]");
});
// test.beforeAll(async ({ page, context }) => await setLayout({ page, context }, true));
test("Table of contents works", async ({ page, context }) => {
const app = new App(page, context);
await app.goto();
await app.goToNoteInNewTab("Dacia Logan.pdf");
const toc = app.sidebar.locator(".toc");
await expect(toc.locator("li")).toHaveCount(48);
await expect(toc.locator("li", { hasText: "Logan Van" })).toHaveCount(1);
const pdfHelper = new PdfHelper(app);
await toc.locator("li", { hasText: "Logan Pick-Up" }).click();
await pdfHelper.expectPageToBe(13);
});
test("Page navigation works", async ({ page, context }) => {
const app = new App(page, context);
await app.goto();
await app.goToNoteInNewTab("Dacia Logan.pdf");
const pagesList = app.sidebar.locator(".pdf-pages-list");
// Check count is correct.
await expect(app.sidebar).toContainText("28 pages");
expect(await pagesList.locator(".pdf-page-item").count()).toBe(28);
// Go to page 3.
await pagesList.locator(".pdf-page-item").nth(2).click();
const pdfHelper = new PdfHelper(app);
await pdfHelper.expectPageToBe(3);
});
test("Attachments listing works", async ({ page, context }) => {
const app = new App(page, context);
await app.goto();
await app.goToNoteInNewTab("Dacia Logan.pdf");
const attachmentsList = app.sidebar.locator(".pdf-attachments-list");
await expect(attachmentsList.locator(".pdf-attachment-item")).toHaveCount(2);
const attachmentInfo = attachmentsList.locator(".pdf-attachment-item", { hasText: "Note.trilium" });
await expect(attachmentInfo).toContainText("3.36 MB");
// Download the attachment and check its size.
const [ download ] = await Promise.all([
page.waitForEvent("download"),
attachmentInfo.locator(".bx-download").click()
]);
expect(download).toBeDefined();
});
});
async function setLayout({ page, context}: { page: Page; context: BrowserContext }, newLayout: boolean) {
const app = new App(page, context);
await app.goto();
await app.setOption("newLayout", newLayout ? "true" : "false");
return app;
}
class PdfHelper {
private contentFrame: ReturnType<Page["frameLocator"]>;
constructor(app: App) {
this.contentFrame = app.currentNoteSplit.frameLocator("iframe");
}
async expectPageToBe(expectedPageNumber: number) {
await expect(this.contentFrame.locator("#pageNumber")).toHaveValue(`${expectedPageNumber}`);
}
}

View File

@@ -17,9 +17,6 @@ async function main() {
// Integrate the client.
build.triggerBuildAndCopyTo("apps/client", "public/");
build.deleteFromOutput("public/webpack-stats.json");
// pdf.js
build.triggerBuildAndCopyTo("packages/pdfjs-viewer", "pdfjs-viewer");
}
main();

Binary file not shown.

View File

@@ -73,7 +73,7 @@ export function getPdfjsAssetDir() {
return path.join(srcRoot, "../../packages/pdfjs-viewer/dist");
}
const resourceDir = getResourceDir();
return path.join(resourceDir, "pdfjs-viewer");
return path.join(resourceDir, "pdfjs-viewer/assets");
}
export function getClientDir() {

View File

@@ -89,7 +89,7 @@
"url": "https://github.com/TriliumNext/Trilium/issues"
},
"homepage": "https://triliumnotes.org",
"packageManager": "pnpm@10.27.0",
"packageManager": "pnpm@10.26.2",
"pnpm": {
"patchedDependencies": {
"@ckeditor/ckeditor5-mention": "patches/@ckeditor__ckeditor5-mention.patch",

View File

@@ -16,7 +16,7 @@
"@codemirror/lang-xml": "6.1.0",
"@codemirror/legacy-modes": "6.5.2",
"@codemirror/search": "6.5.11",
"@codemirror/view": "6.39.8",
"@codemirror/view": "6.39.7",
"@fsegurai/codemirror-theme-abcdef": "6.2.3",
"@fsegurai/codemirror-theme-abyss": "6.2.3",
"@fsegurai/codemirror-theme-android-studio": "6.2.3",

View File

@@ -4,8 +4,7 @@
"private": true,
"scripts": {
"build": "tsx scripts/build.ts",
"watch": "tsx scripts/build.ts --watch",
"test": "vitest"
"watch": "tsx scripts/build.ts --watch"
},
"dependencies": {
"@triliumnext/commons": "workspace:*"

View File

@@ -1,80 +0,0 @@
export async function setupPdfAttachments() {
const app = window.PDFViewerApplication;
// Extract immediately since we're called after documentloaded
await extractAndSendAttachments();
// Listen for download requests
window.addEventListener("message", async (event) => {
if (event.data?.type === "trilium-download-attachment") {
const filename = event.data.filename;
await downloadAttachment(filename);
}
});
}
async function extractAndSendAttachments() {
const app = window.PDFViewerApplication;
try {
const attachments = await app.pdfDocument.getAttachments();
console.log("Got attachments:", attachments);
if (!attachments) {
window.parent.postMessage({
type: "pdfjs-viewer-attachments",
attachments: []
}, window.location.origin);
return;
}
// Convert attachments object to array
const attachmentList = Object.entries(attachments).map(([filename, data]: [string, any]) => ({
filename,
content: data.content, // Uint8Array
size: data.content?.length || 0
}));
// Send metadata only (not the full content)
window.parent.postMessage({
type: "pdfjs-viewer-attachments",
attachments: attachmentList.map(att => ({
filename: att.filename,
size: att.size
}))
}, window.location.origin);
} catch (error) {
console.error("Error extracting attachments:", error);
window.parent.postMessage({
type: "pdfjs-viewer-attachments",
attachments: []
}, window.location.origin);
}
}
async function downloadAttachment(filename: string) {
const app = window.PDFViewerApplication;
try {
const attachments = await app.pdfDocument.getAttachments();
const attachment = attachments?.[filename];
if (!attachment) {
console.error("Attachment not found:", filename);
return;
}
// Create blob and download
const blob = new Blob([attachment.content], { type: "application/octet-stream" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
} catch (error) {
console.error("Error downloading attachment:", error);
}
}

View File

@@ -12,7 +12,3 @@
box-shadow: 0 0 3px rgba(0, 0, 0, 0.2);
}
}
#toolbarViewerLeft > .toolbarButtonSpacer:first-child {
display: none !important;
}

View File

@@ -1,16 +1,7 @@
import interceptPersistence from "./persistence";
import { extractAndSendToc, setupScrollToHeading, setupActiveHeadingTracking } from "./toc";
import { setupPdfPages } from "./pages";
import { setupPdfAttachments } from "./attachments";
import { setupPdfLayers } from "./layers";
async function main() {
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get("sidebar") === "0") {
hideSidebar();
}
interceptPersistence(getCustomAppOptions(urlParams));
interceptPersistence(getCustomAppOptions());
// Wait for the PDF viewer application to be available.
while (!window.PDFViewerApplication) {
@@ -20,37 +11,18 @@ async function main() {
app.eventBus.on("documentloaded", () => {
manageSave();
extractAndSendToc();
setupScrollToHeading();
setupActiveHeadingTracking();
setupPdfPages();
setupPdfAttachments();
setupPdfLayers();
});
await app.initializedPromise;
};
function hideSidebar() {
window.TRILIUM_HIDE_SIDEBAR = true;
const toggleButtonEl = document.getElementById("viewsManagerToggleButton");
if (toggleButtonEl) {
const spacer = toggleButtonEl.nextElementSibling.nextElementSibling;
if (spacer.classList.contains("toolbarButtonSpacer")) {
spacer.remove();
}
toggleButtonEl.remove();
}
}
function getCustomAppOptions() {
const urlParams = new URLSearchParams(window.location.search);
function getCustomAppOptions(urlParams: URLSearchParams) {
return {
localeProperties: {
// Read from URL query
lang: urlParams.get("lang") || "en"
},
// Control sidebar visibility via query parameter
// sidebarViewOnLoad: -1 disables sidebar, 0 = NONE (default)
viewsManager: null
}
};
}

View File

@@ -1,118 +0,0 @@
export async function setupPdfLayers() {
const app = window.PDFViewerApplication;
// Extract immediately since we're called after documentloaded
await extractAndSendLayers();
// Listen for layer visibility toggle requests
window.addEventListener("message", async (event) => {
if (event.data?.type === "trilium-toggle-layer") {
const layerId = event.data.layerId;
const visible = event.data.visible;
await toggleLayer(layerId, visible);
}
});
}
async function extractAndSendLayers() {
const app = window.PDFViewerApplication;
try {
// Get the config from the viewer if available (has updated state), otherwise from document
const pdfViewer = app.pdfViewer;
const optionalContentConfig = pdfViewer?.optionalContentConfigPromise
? await pdfViewer.optionalContentConfigPromise
: await app.pdfDocument.getOptionalContentConfig();
if (!optionalContentConfig) {
window.parent.postMessage({
type: "pdfjs-viewer-layers",
layers: []
}, window.location.origin);
return;
}
// Get all layer group IDs from the order
const order = optionalContentConfig.getOrder();
if (!order || order.length === 0) {
window.parent.postMessage({
type: "pdfjs-viewer-layers",
layers: []
}, window.location.origin);
return;
}
// Flatten the order array (it can be nested) and extract group IDs
const groupIds: string[] = [];
const flattenOrder = (items: any[]) => {
for (const item of items) {
if (typeof item === 'string') {
groupIds.push(item);
} else if (Array.isArray(item)) {
flattenOrder(item);
} else if (item && typeof item === 'object' && item.id) {
groupIds.push(item.id);
}
}
};
flattenOrder(order);
// Get group details for each ID and only include valid, toggleable layers
const layers = groupIds.map(id => {
const group = optionalContentConfig.getGroup(id);
// Only include groups that have a name and usage property (actual layers)
if (!group || !group.name || !group.usage) {
return null;
}
// Use group.visible property like PDF.js viewer does
return {
id,
name: group.name,
visible: group.visible
};
}).filter(layer => layer !== null); // Filter out invalid layers
window.parent.postMessage({
type: "pdfjs-viewer-layers",
layers
}, window.location.origin);
} catch (error) {
console.error("Error extracting layers:", error);
window.parent.postMessage({
type: "pdfjs-viewer-layers",
layers: []
}, window.location.origin);
}
}
async function toggleLayer(layerId: string, visible: boolean) {
const app = window.PDFViewerApplication;
try {
const pdfViewer = app.pdfViewer;
if (!pdfViewer) {
return;
}
const optionalContentConfig = await pdfViewer.optionalContentConfigPromise;
if (!optionalContentConfig) {
return;
}
// Set visibility on the config (like PDF.js viewer does)
optionalContentConfig.setVisibility(layerId, visible);
// Dispatch optionalcontentconfig event with the existing config
app.eventBus.dispatch("optionalcontentconfig", {
source: app,
promise: Promise.resolve(optionalContentConfig)
});
// Send updated layer state back
await extractAndSendLayers();
} catch (error) {
console.error("Error toggling layer:", error);
}
}

View File

@@ -1,83 +0,0 @@
export function setupPdfPages() {
const app = window.PDFViewerApplication;
// Send initial page info when pages are initialized
app.eventBus.on("pagesinit", () => {
sendPageInfo();
});
// Also send immediately if document is already loaded
if (app.pdfDocument && app.pdfViewer) {
sendPageInfo();
}
// Track current page changes
app.eventBus.on("pagechanging", (evt: any) => {
window.parent.postMessage({
type: "pdfjs-viewer-current-page",
currentPage: evt.pageNumber
}, window.location.origin);
});
// Listen for scroll-to-page requests
window.addEventListener("message", (event) => {
if (event.data?.type === "trilium-scroll-to-page") {
const pageNumber = event.data.pageNumber;
app.pdfViewer.currentPageNumber = pageNumber;
}
});
// Listen for thumbnail requests
window.addEventListener("message", async (event) => {
if (event.data?.type === "trilium-request-thumbnail") {
const pageNumber = event.data.pageNumber;
await generateThumbnail(pageNumber);
}
});
}
function sendPageInfo() {
const app = window.PDFViewerApplication;
window.parent.postMessage({
type: "pdfjs-viewer-page-info",
totalPages: app.pdfDocument.numPages,
currentPage: app.pdfViewer.currentPageNumber
}, window.location.origin);
}
async function generateThumbnail(pageNumber: number) {
const app = window.PDFViewerApplication;
try {
const page = await app.pdfDocument.getPage(pageNumber);
// Create canvas for thumbnail
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
if (!context) return;
// Set thumbnail size (smaller than actual page)
const viewport = page.getViewport({ scale: 0.2 });
canvas.width = viewport.width;
canvas.height = viewport.height;
// Render page to canvas
await page.render({
canvas: canvas,
viewport: viewport
}).promise;
// Convert to data URL
const dataUrl = canvas.toDataURL('image/jpeg', 0.7);
// Send thumbnail to parent
window.parent.postMessage({
type: "pdfjs-viewer-thumbnail",
pageNumber,
dataUrl
}, window.location.origin);
} catch (error) {
console.error(`Error generating thumbnail for page %d:`, pageNumber, error);
}
}

View File

@@ -1,7 +0,0 @@
import { describe, expect, it } from "vitest";
describe("Toc", () => {
it("should pass", () => {
expect(true).toBe(true);
});
});

View File

@@ -1,186 +0,0 @@
let outlineMap: Map<string, any> | null = null;
let headingPositions: Array<{ id: string; pageIndex: number; y: number }> | null = null;
export async function extractAndSendToc() {
const app = window.PDFViewerApplication;
try {
const outline = await app.pdfDocument.getOutline();
if (!outline || outline.length === 0) {
window.parent.postMessage({
type: "pdfjs-viewer-toc",
data: null
}, window.location.origin);
return;
}
// Store outline items with their destinations for later scrolling
outlineMap = new Map();
headingPositions = [];
const toc = convertOutlineToToc(outline, 0, outlineMap);
// Build position mapping for active heading detection
await buildPositionMapping(outlineMap);
window.parent.postMessage({
type: "pdfjs-viewer-toc",
data: toc
}, window.location.origin);
} catch (error) {
window.parent.postMessage({
type: "pdfjs-viewer-toc",
data: null
}, window.location.origin);
}
}
function convertOutlineToToc(outline: any[], level = 0, outlineMap?: Map<string, any>, parentId = ""): any[] {
return outline.map((item, index) => {
const id = parentId ? `${parentId}-${index}` : `pdf-outline-${index}`;
if (outlineMap) {
outlineMap.set(id, item);
}
return {
title: item.title,
level: level,
dest: item.dest,
id: id,
items: item.items && item.items.length > 0 ? convertOutlineToToc(item.items, level + 1, outlineMap, id) : []
};
});
}
export function setupScrollToHeading() {
window.addEventListener("message", async (event) => {
if (event.data?.type === "trilium-scroll-to-heading") {
const headingId = event.data.headingId;
if (!outlineMap) return;
const outlineItem = outlineMap.get(headingId);
if (!outlineItem || !outlineItem.dest) return;
const app = window.PDFViewerApplication;
// Navigate to the destination
try {
const dest = typeof outlineItem.dest === 'string'
? await app.pdfDocument.getDestination(outlineItem.dest)
: outlineItem.dest;
if (dest) {
app.pdfLinkService.goToDestination(dest);
}
} catch (error) {
console.error("Error navigating to heading:", error);
}
}
});
}
async function buildPositionMapping(outlineMap: Map<string, any>) {
const app = window.PDFViewerApplication;
for (const [id, item] of outlineMap.entries()) {
if (!item.dest) continue;
try {
const dest = typeof item.dest === 'string'
? await app.pdfDocument.getDestination(item.dest)
: item.dest;
if (dest && dest[0]) {
const pageRef = dest[0];
const pageIndex = await app.pdfDocument.getPageIndex(pageRef);
// Extract Y coordinate from destination (dest[3] is typically the y-coordinate)
const y = typeof dest[3] === 'number' ? dest[3] : 0;
headingPositions?.push({ id, pageIndex, y });
}
} catch (error) {
// Skip items with invalid destinations
}
}
// Sort by page and then by Y position (descending, since PDF coords are bottom-up)
headingPositions?.sort((a, b) => {
if (a.pageIndex !== b.pageIndex) {
return a.pageIndex - b.pageIndex;
}
return b.y - a.y; // Higher Y comes first (top of page)
});
}
export function setupActiveHeadingTracking() {
const app = window.PDFViewerApplication;
let lastActiveHeading: string | null = null;
// Offset from top of viewport to consider a heading "active"
// This makes the heading active when it's near the top, not when fully scrolled past
const ACTIVE_HEADING_OFFSET = 100;
function updateActiveHeading() {
if (!headingPositions || headingPositions.length === 0) return;
const viewer = app.pdfViewer;
const container = viewer.container;
const scrollTop = container.scrollTop;
// Find the heading closest to the top of the viewport
let activeHeadingId: string | null = null;
let bestDistance = Infinity;
for (const heading of headingPositions) {
// Get the page view to calculate actual position
const pageView = viewer.getPageView(heading.pageIndex);
if (!pageView || !pageView.div) {
continue;
}
const pageTop = pageView.div.offsetTop;
const pageHeight = pageView.div.clientHeight;
// Convert PDF Y coordinate (bottom-up) to screen position (top-down)
const headingScreenY = pageTop + (pageHeight - heading.y);
// Calculate distance from top of viewport
const distance = Math.abs(headingScreenY - scrollTop);
// If this heading is closer to the top of viewport, and it's not too far below
if (headingScreenY <= scrollTop + ACTIVE_HEADING_OFFSET && distance < bestDistance) {
activeHeadingId = heading.id;
bestDistance = distance;
}
}
if (activeHeadingId !== lastActiveHeading) {
lastActiveHeading = activeHeadingId;
window.parent.postMessage({
type: "pdfjs-viewer-active-heading",
headingId: activeHeadingId
}, window.location.origin);
}
}
// Debounced scroll handler
let scrollTimeout: number | null = null;
const debouncedUpdate = () => {
if (scrollTimeout) {
clearTimeout(scrollTimeout);
}
scrollTimeout = window.setTimeout(updateActiveHeading, 100);
};
app.eventBus.on("pagechanging", debouncedUpdate);
// Also listen to scroll events for more granular updates within a page
const container = app.pdfViewer.container;
container.addEventListener("scroll", debouncedUpdate);
// Initial update
updateActiveHeading();
}

View File

@@ -14,32 +14,10 @@ declare global {
_readFromStorage: () => Promise<string>;
}
interface PdfJsDestination {
}
interface Window {
PDFViewerApplication?: {
initializedPromise: Promise<void>;
pdfDocument: PDFDocumentProxy;
pdfViewer: {
currentPageNumber: number;
optionalContentConfigPromise: {
setVisibility(groupId: string, visible: boolean);
getGroup(groupId: string): {
name: string;
usage: {};
};
getOrder(): {}[]
};
getPageView(pageIndex: number): {
div: HTMLDivElement;
};
container: HTMLElement;
};
pdfLinkService: {
goToDestination(dest: PdfJsDestination);
};
eventBus: {
on(event: string, listener: (...args: any[]) => void): void;
dispatch(event: string, data?: any): void;

View File

@@ -18609,7 +18609,7 @@ function getViewerConfiguration() {
imageAltTextSettingsSeparator: document.getElementById("imageAltTextSettingsSeparator"),
documentPropertiesButton: document.getElementById("documentProperties")
},
viewsManager: window.TRILIUM_HIDE_SIDEBAR ? null : {
viewsManager: {
outerContainer: document.getElementById("outerContainer"),
toggleButton: document.getElementById("viewsManagerToggleButton"),
sidebarContainer: document.getElementById("viewsManager"),

238
pnpm-lock.yaml generated
View File

@@ -134,8 +134,8 @@ importers:
apps/build-docs:
devDependencies:
'@redocly/cli':
specifier: 2.14.2
version: 2.14.2(@opentelemetry/api@1.9.0)(ajv@8.17.1)(bufferutil@4.0.9)(core-js@3.46.0)(encoding@0.1.13)(utf-8-validate@6.0.5)
specifier: 2.14.1
version: 2.14.1(@opentelemetry/api@1.9.0)(ajv@8.17.1)(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
@@ -1235,89 +1235,89 @@ importers:
specifier: 6.5.11
version: 6.5.11
'@codemirror/view':
specifier: 6.39.8
version: 6.39.8
specifier: 6.39.7
version: 6.39.7
'@fsegurai/codemirror-theme-abcdef':
specifier: 6.2.3
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)
'@fsegurai/codemirror-theme-abyss':
specifier: 6.2.3
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)
'@fsegurai/codemirror-theme-android-studio':
specifier: 6.2.3
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)
'@fsegurai/codemirror-theme-andromeda':
specifier: 6.2.3
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)
'@fsegurai/codemirror-theme-basic-dark':
specifier: 6.2.3
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)
'@fsegurai/codemirror-theme-basic-light':
specifier: 6.2.3
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)
'@fsegurai/codemirror-theme-cobalt2':
specifier: 6.0.3
version: 6.0.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)
version: 6.0.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)
'@fsegurai/codemirror-theme-forest':
specifier: 6.2.3
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)
'@fsegurai/codemirror-theme-github-dark':
specifier: 6.2.3
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)
'@fsegurai/codemirror-theme-github-light':
specifier: 6.2.3
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)
'@fsegurai/codemirror-theme-gruvbox-dark':
specifier: 6.2.3
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)
'@fsegurai/codemirror-theme-gruvbox-light':
specifier: 6.2.3
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)
'@fsegurai/codemirror-theme-material-dark':
specifier: 6.2.3
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)
'@fsegurai/codemirror-theme-material-light':
specifier: 6.2.3
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)
'@fsegurai/codemirror-theme-monokai':
specifier: 6.2.3
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)
'@fsegurai/codemirror-theme-nord':
specifier: 6.2.3
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)
'@fsegurai/codemirror-theme-palenight':
specifier: 6.2.3
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)
'@fsegurai/codemirror-theme-solarized-dark':
specifier: 6.2.3
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)
'@fsegurai/codemirror-theme-solarized-light':
specifier: 6.2.3
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)
'@fsegurai/codemirror-theme-tokyo-night-day':
specifier: 6.2.3
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)
'@fsegurai/codemirror-theme-tokyo-night-storm':
specifier: 6.2.3
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)
'@fsegurai/codemirror-theme-volcano':
specifier: 6.2.3
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)
'@fsegurai/codemirror-theme-vscode-dark':
specifier: 6.2.3
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)
'@fsegurai/codemirror-theme-vscode-light':
specifier: 6.2.3
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)
version: 6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)
'@replit/codemirror-indentation-markers':
specifier: 6.5.3
version: 6.5.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)
version: 6.5.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)
'@replit/codemirror-lang-nix':
specifier: 6.0.1
version: 6.0.1(@codemirror/autocomplete@6.18.6)(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/common@1.2.3)(@lezer/highlight@1.2.1)(@lezer/lr@1.4.2)
version: 6.0.1(@codemirror/autocomplete@6.18.6)(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/common@1.2.3)(@lezer/highlight@1.2.1)(@lezer/lr@1.4.2)
'@replit/codemirror-vim':
specifier: 6.3.0
version: 6.3.0(@codemirror/commands@6.10.1)(@codemirror/language@6.11.0)(@codemirror/search@6.5.11)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)
version: 6.3.0(@codemirror/commands@6.10.1)(@codemirror/language@6.11.0)(@codemirror/search@6.5.11)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)
'@ssddanbrown/codemirror-lang-smarty':
specifier: 1.0.0
version: 1.0.0
@@ -2122,8 +2122,8 @@ packages:
'@codemirror/theme-one-dark@6.1.2':
resolution: {integrity: sha512-F+sH0X16j/qFLMAfbciKTxVOwkdAS336b7AXTKOZhy8BR3eH/RelsnLgLFINrpST63mmN2OuwUt0W2ndUgYwUA==}
'@codemirror/view@6.39.8':
resolution: {integrity: sha512-1rASYd9Z/mE3tkbC9wInRlCNyCkSn+nLsiQKZhEDUUJiUfs/5FHDpCUDaQpoTIaNGeDc6/bhaEAyLmeEucEFPw==}
'@codemirror/view@6.39.7':
resolution: {integrity: sha512-3Vif9hnNHJnl2YgOtkR/wzGzhYcQ8gy3LGdUhkLUU8xSBbgsTxrE8he/CMTpeINm5TgxLe2FmzvF6IYQL/BSAg==}
'@colors/colors@1.5.0':
resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
@@ -4496,8 +4496,8 @@ packages:
'@redocly/ajv@8.17.1':
resolution: {integrity: sha512-EDtsGZS964mf9zAUXAl9Ew16eYbeyAFWhsPr0fX6oaJxgd8rApYlPBf0joyhnUHz88WxrigyFtTaqqzXNzPgqw==}
'@redocly/cli@2.14.2':
resolution: {integrity: sha512-M1DTJgElNYfBQS2jikfkCCNaRfa4HeF+mZpQCjHbhcvJogsIZTPL5LI6lKSnovDUTAKzUMnVlLa8hji1wx3OJQ==}
'@redocly/cli@2.14.1':
resolution: {integrity: sha512-Fz9qSkUz/CZgO4xnlPiRBMjwTqH1VxKlO3y8gOrEHOtr2v4peO2Fvbpz30iipTHkbrCt8UorZelXM1TEqkYHSQ==}
engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'}
hasBin: true
@@ -4511,12 +4511,12 @@ packages:
resolution: {integrity: sha512-0EbE8LRbkogtcCXU7liAyC00n9uNG9hJ+eMyHFdUsy9lB/WGqnEBgwjA9q2cyzAVcdTkQqTBBU1XePNnN3OijA==}
engines: {node: '>=18.17.0', npm: '>=9.5.0'}
'@redocly/openapi-core@2.14.2':
resolution: {integrity: sha512-KXHubrwRg10rv46c9pzn0slcbK1YaqYfqR26uhc/2KrZ2+ke106kWp1qU5rCwhkuYNXhycVLd4MrXISYAOVViw==}
'@redocly/openapi-core@2.14.1':
resolution: {integrity: sha512-WKjfdnUalQfNnQfbkCg+uXEDydq7/g8t1VbzK6Nm6M/4JBjQlnUdo5kV9CLWM3IcdVHm9idovIW6cej2NRb2eA==}
engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'}
'@redocly/respect-core@2.14.2':
resolution: {integrity: sha512-QMKkzlYv12k0CXezxrVlzuIvRMy8H/Eh5E7ILuEP763ydQaOd9JPO29YDCO8CJjxBqYV2R2USOAgB0Mkn5AmvQ==}
'@redocly/respect-core@2.14.1':
resolution: {integrity: sha512-WhAvjiEAbfd1unNAqVCTFny2PkuVOAK1eZ5JE6p1iHgLIoLZX0tdjM1+ht56AWE2hZ/TkcKDDP45GDqfBymi0w==}
engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'}
'@replit/codemirror-indentation-markers@6.5.3':
@@ -15357,6 +15357,8 @@ snapshots:
'@ckeditor/ckeditor5-core': 47.3.0
'@ckeditor/ckeditor5-utils': 47.3.0
ckeditor5: 47.3.0
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-code-block@47.3.0(patch_hash=2361d8caad7d6b5bddacc3a3b4aa37dbfba260b1c1b22a450413a79c1bb1ce95)':
dependencies:
@@ -15549,6 +15551,8 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.3.0
ckeditor5: 47.3.0
es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-editor-classic@47.3.0':
dependencies:
@@ -15558,6 +15562,8 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.3.0
ckeditor5: 47.3.0
es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-editor-decoupled@47.3.0':
dependencies:
@@ -15600,8 +15606,6 @@ snapshots:
'@ckeditor/ckeditor5-table': 47.3.0
'@ckeditor/ckeditor5-utils': 47.3.0
ckeditor5: 47.3.0
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-emoji@47.3.0':
dependencies:
@@ -15627,6 +15631,8 @@ snapshots:
'@ckeditor/ckeditor5-core': 47.3.0
'@ckeditor/ckeditor5-engine': 47.3.0
'@ckeditor/ckeditor5-utils': 47.3.0
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-essentials@47.3.0':
dependencies:
@@ -16087,6 +16093,8 @@ snapshots:
'@ckeditor/ckeditor5-ui': 47.3.0
'@ckeditor/ckeditor5-utils': 47.3.0
ckeditor5: 47.3.0
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-restricted-editing@47.3.0':
dependencies:
@@ -16162,7 +16170,7 @@ snapshots:
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@codemirror/theme-one-dark': 6.1.2
'@codemirror/view': 6.39.8
'@codemirror/view': 6.39.7
ckeditor5: 47.3.0
'@ckeditor/ckeditor5-source-editing@47.3.0':
@@ -16348,21 +16356,21 @@ snapshots:
dependencies:
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@codemirror/view': 6.39.8
'@codemirror/view': 6.39.7
'@lezer/common': 1.2.3
'@codemirror/commands@6.10.1':
dependencies:
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@codemirror/view': 6.39.8
'@codemirror/view': 6.39.7
'@lezer/common': 1.2.3
'@codemirror/commands@6.8.1':
dependencies:
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@codemirror/view': 6.39.8
'@codemirror/view': 6.39.7
'@lezer/common': 1.2.3
'@codemirror/lang-css@6.3.1':
@@ -16380,7 +16388,7 @@ snapshots:
'@codemirror/lang-javascript': 6.2.4
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@codemirror/view': 6.39.8
'@codemirror/view': 6.39.7
'@lezer/common': 1.2.3
'@lezer/css': 1.1.11
'@lezer/html': 1.3.12
@@ -16391,7 +16399,7 @@ snapshots:
'@codemirror/language': 6.11.0
'@codemirror/lint': 6.8.5
'@codemirror/state': 6.5.2
'@codemirror/view': 6.39.8
'@codemirror/view': 6.39.7
'@lezer/common': 1.2.3
'@lezer/javascript': 1.5.1
@@ -16406,7 +16414,7 @@ snapshots:
'@codemirror/lang-html': 6.4.11
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@codemirror/view': 6.39.8
'@codemirror/view': 6.39.7
'@lezer/common': 1.2.3
'@lezer/markdown': 1.4.3
@@ -16416,7 +16424,7 @@ snapshots:
'@codemirror/lang-html': 6.4.11
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@codemirror/view': 6.39.8
'@codemirror/view': 6.39.7
'@lezer/common': 1.2.3
'@lezer/markdown': 1.4.3
@@ -16442,14 +16450,14 @@ snapshots:
'@codemirror/autocomplete': 6.18.6
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@codemirror/view': 6.39.8
'@codemirror/view': 6.39.7
'@lezer/common': 1.2.3
'@lezer/xml': 1.0.6
'@codemirror/language@6.11.0':
dependencies:
'@codemirror/state': 6.5.2
'@codemirror/view': 6.39.8
'@codemirror/view': 6.39.7
'@lezer/common': 1.2.3
'@lezer/highlight': 1.2.1
'@lezer/lr': 1.4.2
@@ -16462,13 +16470,13 @@ snapshots:
'@codemirror/lint@6.8.5':
dependencies:
'@codemirror/state': 6.5.2
'@codemirror/view': 6.39.8
'@codemirror/view': 6.39.7
crelt: 1.0.6
'@codemirror/search@6.5.11':
dependencies:
'@codemirror/state': 6.5.2
'@codemirror/view': 6.39.8
'@codemirror/view': 6.39.7
crelt: 1.0.6
'@codemirror/state@6.5.2':
@@ -16479,10 +16487,10 @@ snapshots:
dependencies:
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@codemirror/view': 6.39.8
'@codemirror/view': 6.39.7
'@lezer/highlight': 1.2.1
'@codemirror/view@6.39.8':
'@codemirror/view@6.39.7':
dependencies:
'@codemirror/state': 6.5.2
crelt: 1.0.6
@@ -17466,172 +17474,172 @@ snapshots:
'@floating-ui/utils@0.2.9': {}
'@fsegurai/codemirror-theme-abcdef@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)':
'@fsegurai/codemirror-theme-abcdef@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)':
dependencies:
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@codemirror/view': 6.39.8
'@codemirror/view': 6.39.7
'@lezer/highlight': 1.2.1
'@fsegurai/codemirror-theme-abyss@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)':
'@fsegurai/codemirror-theme-abyss@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)':
dependencies:
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@codemirror/view': 6.39.8
'@codemirror/view': 6.39.7
'@lezer/highlight': 1.2.1
'@fsegurai/codemirror-theme-android-studio@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)':
'@fsegurai/codemirror-theme-android-studio@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)':
dependencies:
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@codemirror/view': 6.39.8
'@codemirror/view': 6.39.7
'@lezer/highlight': 1.2.1
'@fsegurai/codemirror-theme-andromeda@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)':
'@fsegurai/codemirror-theme-andromeda@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)':
dependencies:
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@codemirror/view': 6.39.8
'@codemirror/view': 6.39.7
'@lezer/highlight': 1.2.1
'@fsegurai/codemirror-theme-basic-dark@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)':
'@fsegurai/codemirror-theme-basic-dark@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)':
dependencies:
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@codemirror/view': 6.39.8
'@codemirror/view': 6.39.7
'@lezer/highlight': 1.2.1
'@fsegurai/codemirror-theme-basic-light@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)':
'@fsegurai/codemirror-theme-basic-light@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)':
dependencies:
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@codemirror/view': 6.39.8
'@codemirror/view': 6.39.7
'@lezer/highlight': 1.2.1
'@fsegurai/codemirror-theme-cobalt2@6.0.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)':
'@fsegurai/codemirror-theme-cobalt2@6.0.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)':
dependencies:
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@codemirror/view': 6.39.8
'@codemirror/view': 6.39.7
'@lezer/highlight': 1.2.1
'@fsegurai/codemirror-theme-forest@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)':
'@fsegurai/codemirror-theme-forest@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)':
dependencies:
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@codemirror/view': 6.39.8
'@codemirror/view': 6.39.7
'@lezer/highlight': 1.2.1
'@fsegurai/codemirror-theme-github-dark@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)':
'@fsegurai/codemirror-theme-github-dark@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)':
dependencies:
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@codemirror/view': 6.39.8
'@codemirror/view': 6.39.7
'@lezer/highlight': 1.2.1
'@fsegurai/codemirror-theme-github-light@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)':
'@fsegurai/codemirror-theme-github-light@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)':
dependencies:
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@codemirror/view': 6.39.8
'@codemirror/view': 6.39.7
'@lezer/highlight': 1.2.1
'@fsegurai/codemirror-theme-gruvbox-dark@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)':
'@fsegurai/codemirror-theme-gruvbox-dark@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)':
dependencies:
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@codemirror/view': 6.39.8
'@codemirror/view': 6.39.7
'@lezer/highlight': 1.2.1
'@fsegurai/codemirror-theme-gruvbox-light@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)':
'@fsegurai/codemirror-theme-gruvbox-light@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)':
dependencies:
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@codemirror/view': 6.39.8
'@codemirror/view': 6.39.7
'@lezer/highlight': 1.2.1
'@fsegurai/codemirror-theme-material-dark@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)':
'@fsegurai/codemirror-theme-material-dark@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)':
dependencies:
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@codemirror/view': 6.39.8
'@codemirror/view': 6.39.7
'@lezer/highlight': 1.2.1
'@fsegurai/codemirror-theme-material-light@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)':
'@fsegurai/codemirror-theme-material-light@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)':
dependencies:
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@codemirror/view': 6.39.8
'@codemirror/view': 6.39.7
'@lezer/highlight': 1.2.1
'@fsegurai/codemirror-theme-monokai@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)':
'@fsegurai/codemirror-theme-monokai@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)':
dependencies:
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@codemirror/view': 6.39.8
'@codemirror/view': 6.39.7
'@lezer/highlight': 1.2.1
'@fsegurai/codemirror-theme-nord@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)':
'@fsegurai/codemirror-theme-nord@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)':
dependencies:
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@codemirror/view': 6.39.8
'@codemirror/view': 6.39.7
'@lezer/highlight': 1.2.1
'@fsegurai/codemirror-theme-palenight@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)':
'@fsegurai/codemirror-theme-palenight@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)':
dependencies:
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@codemirror/view': 6.39.8
'@codemirror/view': 6.39.7
'@lezer/highlight': 1.2.1
'@fsegurai/codemirror-theme-solarized-dark@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)':
'@fsegurai/codemirror-theme-solarized-dark@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)':
dependencies:
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@codemirror/view': 6.39.8
'@codemirror/view': 6.39.7
'@lezer/highlight': 1.2.1
'@fsegurai/codemirror-theme-solarized-light@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)':
'@fsegurai/codemirror-theme-solarized-light@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)':
dependencies:
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@codemirror/view': 6.39.8
'@codemirror/view': 6.39.7
'@lezer/highlight': 1.2.1
'@fsegurai/codemirror-theme-tokyo-night-day@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)':
'@fsegurai/codemirror-theme-tokyo-night-day@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)':
dependencies:
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@codemirror/view': 6.39.8
'@codemirror/view': 6.39.7
'@lezer/highlight': 1.2.1
'@fsegurai/codemirror-theme-tokyo-night-storm@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)':
'@fsegurai/codemirror-theme-tokyo-night-storm@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)':
dependencies:
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@codemirror/view': 6.39.8
'@codemirror/view': 6.39.7
'@lezer/highlight': 1.2.1
'@fsegurai/codemirror-theme-volcano@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)':
'@fsegurai/codemirror-theme-volcano@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)':
dependencies:
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@codemirror/view': 6.39.8
'@codemirror/view': 6.39.7
'@lezer/highlight': 1.2.1
'@fsegurai/codemirror-theme-vscode-dark@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)':
'@fsegurai/codemirror-theme-vscode-dark@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)':
dependencies:
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@codemirror/view': 6.39.8
'@codemirror/view': 6.39.7
'@lezer/highlight': 1.2.1
'@fsegurai/codemirror-theme-vscode-light@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/highlight@1.2.1)':
'@fsegurai/codemirror-theme-vscode-light@6.2.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/highlight@1.2.1)':
dependencies:
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@codemirror/view': 6.39.8
'@codemirror/view': 6.39.7
'@lezer/highlight': 1.2.1
'@fullcalendar/core@6.1.20':
@@ -19031,14 +19039,14 @@ snapshots:
json-schema-traverse: 1.0.0
require-from-string: 2.0.2
'@redocly/cli@2.14.2(@opentelemetry/api@1.9.0)(ajv@8.17.1)(bufferutil@4.0.9)(core-js@3.46.0)(encoding@0.1.13)(utf-8-validate@6.0.5)':
'@redocly/cli@2.14.1(@opentelemetry/api@1.9.0)(ajv@8.17.1)(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/openapi-core': 2.14.2(ajv@8.17.1)
'@redocly/respect-core': 2.14.2(ajv@8.17.1)
'@redocly/openapi-core': 2.14.1(ajv@8.17.1)
'@redocly/respect-core': 2.14.1(ajv@8.17.1)
abort-controller: 3.0.0
chokidar: 3.6.0
colorette: 1.4.0
@@ -19090,7 +19098,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@redocly/openapi-core@2.14.2(ajv@8.17.1)':
'@redocly/openapi-core@2.14.1(ajv@8.17.1)':
dependencies:
'@redocly/ajv': 8.17.1
'@redocly/config': 0.41.1
@@ -19104,12 +19112,12 @@ snapshots:
transitivePeerDependencies:
- ajv
'@redocly/respect-core@2.14.2(ajv@8.17.1)':
'@redocly/respect-core@2.14.1(ajv@8.17.1)':
dependencies:
'@faker-js/faker': 7.6.0
'@noble/hashes': 1.8.0
'@redocly/ajv': 8.17.1
'@redocly/openapi-core': 2.14.2(ajv@8.17.1)
'@redocly/openapi-core': 2.14.1(ajv@8.17.1)
better-ajv-errors: 1.2.0(ajv@8.17.1)
colorette: 2.0.20
json-pointer: 0.6.2
@@ -19119,29 +19127,29 @@ snapshots:
transitivePeerDependencies:
- ajv
'@replit/codemirror-indentation-markers@6.5.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)':
'@replit/codemirror-indentation-markers@6.5.3(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)':
dependencies:
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@codemirror/view': 6.39.8
'@codemirror/view': 6.39.7
'@replit/codemirror-lang-nix@6.0.1(@codemirror/autocomplete@6.18.6)(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)(@lezer/common@1.2.3)(@lezer/highlight@1.2.1)(@lezer/lr@1.4.2)':
'@replit/codemirror-lang-nix@6.0.1(@codemirror/autocomplete@6.18.6)(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)(@lezer/common@1.2.3)(@lezer/highlight@1.2.1)(@lezer/lr@1.4.2)':
dependencies:
'@codemirror/autocomplete': 6.18.6
'@codemirror/language': 6.11.0
'@codemirror/state': 6.5.2
'@codemirror/view': 6.39.8
'@codemirror/view': 6.39.7
'@lezer/common': 1.2.3
'@lezer/highlight': 1.2.1
'@lezer/lr': 1.4.2
'@replit/codemirror-vim@6.3.0(@codemirror/commands@6.10.1)(@codemirror/language@6.11.0)(@codemirror/search@6.5.11)(@codemirror/state@6.5.2)(@codemirror/view@6.39.8)':
'@replit/codemirror-vim@6.3.0(@codemirror/commands@6.10.1)(@codemirror/language@6.11.0)(@codemirror/search@6.5.11)(@codemirror/state@6.5.2)(@codemirror/view@6.39.7)':
dependencies:
'@codemirror/commands': 6.10.1
'@codemirror/language': 6.11.0
'@codemirror/search': 6.5.11
'@codemirror/state': 6.5.2
'@codemirror/view': 6.39.8
'@codemirror/view': 6.39.7
'@rolldown/binding-android-arm64@1.0.0-beta.29':
optional: true