@macrowing/filepilot-parser-viewer 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +228 -0
- package/dist/index.d.cts +20 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +197 -0
- package/package.json +41 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.tsx
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
default: () => ViewerParser
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(index_exports);
|
|
36
|
+
var import_react = require("react");
|
|
37
|
+
var import_react2 = __toESM(require("@file-viewer/react"), 1);
|
|
38
|
+
var import_preset_lite = __toESM(require("@file-viewer/preset-lite"), 1);
|
|
39
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
40
|
+
var isSourceUrl = (source) => /^(?:https?:|data:|blob:|\/|\.\/|\.\.\/)/i.test(source);
|
|
41
|
+
var toViewerProps = (source, name, textContent) => {
|
|
42
|
+
if (typeof source === "string" && !textContent && isSourceUrl(source)) return { url: source, name };
|
|
43
|
+
if (source instanceof Blob) return { file: source, name };
|
|
44
|
+
const buffer = typeof source === "string" ? new TextEncoder().encode(source).buffer : source instanceof Uint8Array ? source.slice().buffer : source;
|
|
45
|
+
return { buffer, name };
|
|
46
|
+
};
|
|
47
|
+
var HIGHLIGHT_ATTRIBUTE = "data-filepilot-line-range-highlight";
|
|
48
|
+
var HIGHLIGHT_OVERLAY_ATTRIBUTE = "data-filepilot-line-range-highlight-overlay";
|
|
49
|
+
var PREVIOUS_BACKGROUND_ATTRIBUTE = "data-filepilot-previous-background-color";
|
|
50
|
+
var PREVIOUS_BACKGROUND_PRIORITY_ATTRIBUTE = "data-filepilot-previous-background-priority";
|
|
51
|
+
var getViewerRoots = (container) => {
|
|
52
|
+
if (!container) return [];
|
|
53
|
+
const roots = [container];
|
|
54
|
+
if (container.shadowRoot) roots.push(container.shadowRoot);
|
|
55
|
+
container.querySelectorAll("*").forEach((element) => {
|
|
56
|
+
if (element.shadowRoot) roots.push(element.shadowRoot);
|
|
57
|
+
});
|
|
58
|
+
return roots;
|
|
59
|
+
};
|
|
60
|
+
var clearLineRangeHighlight = (container) => {
|
|
61
|
+
getViewerRoots(container).forEach((root) => {
|
|
62
|
+
root.querySelectorAll(`[${HIGHLIGHT_OVERLAY_ATTRIBUTE}]`).forEach((element) => element.remove());
|
|
63
|
+
root.querySelectorAll(`[${HIGHLIGHT_ATTRIBUTE}]`).forEach((element) => {
|
|
64
|
+
element.style.setProperty(
|
|
65
|
+
"background-color",
|
|
66
|
+
element.getAttribute(PREVIOUS_BACKGROUND_ATTRIBUTE) ?? "",
|
|
67
|
+
element.getAttribute(PREVIOUS_BACKGROUND_PRIORITY_ATTRIBUTE) ?? ""
|
|
68
|
+
);
|
|
69
|
+
element.removeAttribute(HIGHLIGHT_ATTRIBUTE);
|
|
70
|
+
element.removeAttribute(PREVIOUS_BACKGROUND_ATTRIBUTE);
|
|
71
|
+
element.removeAttribute(PREVIOUS_BACKGROUND_PRIORITY_ATTRIBUTE);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
var markLineRangeElement = (element, color) => {
|
|
76
|
+
if (!element.hasAttribute(HIGHLIGHT_ATTRIBUTE)) {
|
|
77
|
+
element.setAttribute(PREVIOUS_BACKGROUND_ATTRIBUTE, element.style.backgroundColor);
|
|
78
|
+
element.setAttribute(PREVIOUS_BACKGROUND_PRIORITY_ATTRIBUTE, element.style.getPropertyPriority("background-color"));
|
|
79
|
+
}
|
|
80
|
+
element.setAttribute(HIGHLIGHT_ATTRIBUTE, "true");
|
|
81
|
+
element.style.setProperty("background-color", color, "important");
|
|
82
|
+
};
|
|
83
|
+
var isLeafLineElement = (element) => {
|
|
84
|
+
if (element.matches("pre, code") && /\r\n|\r|\n/.test(element.textContent || "")) return false;
|
|
85
|
+
return !element.querySelector("[data-viewer-line], [data-line]");
|
|
86
|
+
};
|
|
87
|
+
var getLineElements = (root, line) => {
|
|
88
|
+
return Array.from(root.querySelectorAll(`[data-viewer-line="${line}"], [data-line="${line}"]`)).filter(isLeafLineElement);
|
|
89
|
+
};
|
|
90
|
+
var createCodeLineOverlays = (element, from, to, color) => {
|
|
91
|
+
const lines = (element.textContent || "").split(/\r\n|\r|\n/);
|
|
92
|
+
if (lines.length <= 1) return [];
|
|
93
|
+
const ownerDocument = element.ownerDocument;
|
|
94
|
+
const overlays = [];
|
|
95
|
+
const computedStyle = ownerDocument.defaultView?.getComputedStyle(element);
|
|
96
|
+
const lineHeight = parseFloat(computedStyle?.lineHeight ?? "") || parseFloat(computedStyle?.fontSize ?? "16") * 1.2;
|
|
97
|
+
const start = Math.max(1, from);
|
|
98
|
+
const end = Math.min(to, lines.length);
|
|
99
|
+
if (start > end) return [];
|
|
100
|
+
if (ownerDocument.defaultView?.getComputedStyle(element.parentElement ?? element).position === "static") {
|
|
101
|
+
(element.parentElement ?? element).style.position = "relative";
|
|
102
|
+
}
|
|
103
|
+
for (let line = start; line <= end; line += 1) {
|
|
104
|
+
const overlay = ownerDocument.createElement("span");
|
|
105
|
+
overlay.setAttribute(HIGHLIGHT_OVERLAY_ATTRIBUTE, "true");
|
|
106
|
+
overlay.style.position = "absolute";
|
|
107
|
+
overlay.style.left = "0";
|
|
108
|
+
overlay.style.right = "0";
|
|
109
|
+
overlay.style.top = `${(line - 1) * lineHeight}px`;
|
|
110
|
+
overlay.style.height = `${lineHeight}px`;
|
|
111
|
+
overlay.style.pointerEvents = "none";
|
|
112
|
+
overlay.style.backgroundColor = color;
|
|
113
|
+
overlay.style.zIndex = "0";
|
|
114
|
+
overlays.push(overlay);
|
|
115
|
+
}
|
|
116
|
+
return overlays;
|
|
117
|
+
};
|
|
118
|
+
var markCodeBlockRange = (element, from, to, color) => {
|
|
119
|
+
const parent = element.parentElement;
|
|
120
|
+
if (!parent) return;
|
|
121
|
+
createCodeLineOverlays(element, from, to, color).forEach((overlay) => parent.insertBefore(overlay, element));
|
|
122
|
+
element.style.position = "relative";
|
|
123
|
+
element.style.zIndex = "1";
|
|
124
|
+
};
|
|
125
|
+
var applyLineRangeHighlight = async (container, handle, range, color) => {
|
|
126
|
+
clearLineRangeHighlight(container);
|
|
127
|
+
if (!range || !color) return;
|
|
128
|
+
const from = Math.min(range.from, range.to);
|
|
129
|
+
const to = Math.max(range.from, range.to);
|
|
130
|
+
if (!Number.isFinite(from) || !Number.isFinite(to) || from < 1) return;
|
|
131
|
+
await handle?.collectDocumentAnchors();
|
|
132
|
+
getViewerRoots(container).forEach((root) => {
|
|
133
|
+
for (let line = from; line <= to; line += 1) {
|
|
134
|
+
getLineElements(root, line).forEach((element) => markLineRangeElement(element, color));
|
|
135
|
+
}
|
|
136
|
+
if (!root.querySelector(`[${HIGHLIGHT_ATTRIBUTE}]`)) {
|
|
137
|
+
const codeBlock = root.querySelector(".code-area code, pre code");
|
|
138
|
+
if (codeBlock) markCodeBlockRange(codeBlock, from, to, color);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
};
|
|
142
|
+
var useShallowStableValue = (value) => {
|
|
143
|
+
const ref = (0, import_react.useRef)(value);
|
|
144
|
+
const previous = ref.current;
|
|
145
|
+
const previousKeys = Object.keys(previous);
|
|
146
|
+
const nextKeys = Object.keys(value);
|
|
147
|
+
const isEqual = previousKeys.length === nextKeys.length && nextKeys.every((key) => previous[key] === value[key]);
|
|
148
|
+
if (!isEqual) ref.current = value;
|
|
149
|
+
return ref.current;
|
|
150
|
+
};
|
|
151
|
+
var toViewerLocale = (locale) => locale?.startsWith("en") ? "en-US" : "zh-CN";
|
|
152
|
+
var withViewerOptions = (options, locale) => {
|
|
153
|
+
const preset = options?.preset;
|
|
154
|
+
const viewerLocale = options?.locale ?? toViewerLocale(locale);
|
|
155
|
+
const i18nLocale = options?.i18n?.locale ?? viewerLocale;
|
|
156
|
+
const toolbar = typeof options?.toolbar === "object" && options.toolbar ? options.toolbar : {};
|
|
157
|
+
return {
|
|
158
|
+
...options,
|
|
159
|
+
locale: viewerLocale,
|
|
160
|
+
i18n: {
|
|
161
|
+
...options?.i18n,
|
|
162
|
+
locale: i18nLocale
|
|
163
|
+
},
|
|
164
|
+
toolbar: {
|
|
165
|
+
download: false,
|
|
166
|
+
print: false,
|
|
167
|
+
exportHtml: false,
|
|
168
|
+
theme: false,
|
|
169
|
+
...toolbar
|
|
170
|
+
},
|
|
171
|
+
preset: preset ? Array.isArray(preset) ? [import_preset_lite.default, ...preset] : [import_preset_lite.default, preset] : import_preset_lite.default
|
|
172
|
+
};
|
|
173
|
+
};
|
|
174
|
+
function ViewerParser({ file, className, style, options, locale }) {
|
|
175
|
+
const containerRef = (0, import_react.useRef)(null);
|
|
176
|
+
const handleRef = (0, import_react.useRef)(null);
|
|
177
|
+
const scrolledLineRef = (0, import_react.useRef)(void 0);
|
|
178
|
+
const highlightedRangeRef = (0, import_react.useRef)(void 0);
|
|
179
|
+
const navigationRef = (0, import_react.useRef)({});
|
|
180
|
+
const onStateChangeRef = (0, import_react.useRef)(void 0);
|
|
181
|
+
const onHandleReadyRef = (0, import_react.useRef)(void 0);
|
|
182
|
+
const typedOptions = options;
|
|
183
|
+
const { line, lineRange, lineRangeHighlightColor, textContent, onHandleReady, onStateChange, ...restViewerOptions } = typedOptions ?? {};
|
|
184
|
+
const stableViewerOptions = useShallowStableValue(restViewerOptions);
|
|
185
|
+
const viewerOptions = (0, import_react.useMemo)(() => withViewerOptions(stableViewerOptions, locale), [stableViewerOptions, locale]);
|
|
186
|
+
const viewerProps = (0, import_react.useMemo)(() => toViewerProps(file.source, file.name, textContent), [file.source, file.name, textContent]);
|
|
187
|
+
navigationRef.current = { line, lineRange, lineRangeHighlightColor };
|
|
188
|
+
onStateChangeRef.current = onStateChange;
|
|
189
|
+
onHandleReadyRef.current = onHandleReady;
|
|
190
|
+
const applyNavigation = (0, import_react.useCallback)((force = false) => {
|
|
191
|
+
const { line: line2, lineRange: lineRange2, lineRangeHighlightColor: lineRangeHighlightColor2 } = navigationRef.current;
|
|
192
|
+
const rangeKey = lineRange2 ? `${lineRange2.from}:${lineRange2.to}:${lineRangeHighlightColor2 ?? ""}` : "";
|
|
193
|
+
if (line2 && scrolledLineRef.current !== line2 && handleRef.current) {
|
|
194
|
+
void handleRef.current.scrollToLine(line2);
|
|
195
|
+
scrolledLineRef.current = line2;
|
|
196
|
+
}
|
|
197
|
+
if (!force && highlightedRangeRef.current === rangeKey) return;
|
|
198
|
+
highlightedRangeRef.current = rangeKey;
|
|
199
|
+
void applyLineRangeHighlight(containerRef.current, handleRef.current, lineRange2, lineRangeHighlightColor2);
|
|
200
|
+
}, []);
|
|
201
|
+
(0, import_react.useEffect)(() => {
|
|
202
|
+
scrolledLineRef.current = void 0;
|
|
203
|
+
highlightedRangeRef.current = void 0;
|
|
204
|
+
applyNavigation();
|
|
205
|
+
}, [file.source, line, lineRange?.from, lineRange?.to, lineRangeHighlightColor, applyNavigation]);
|
|
206
|
+
(0, import_react.useEffect)(() => {
|
|
207
|
+
if (handleRef.current && onHandleReadyRef.current) {
|
|
208
|
+
onHandleReadyRef.current(handleRef.current);
|
|
209
|
+
}
|
|
210
|
+
}, []);
|
|
211
|
+
const handleStateChange = (0, import_react.useCallback)((state, event) => {
|
|
212
|
+
onStateChangeRef.current?.(state, event);
|
|
213
|
+
if (state.ready && (!event || event.type === "load-complete")) {
|
|
214
|
+
scrolledLineRef.current = void 0;
|
|
215
|
+
highlightedRangeRef.current = void 0;
|
|
216
|
+
applyNavigation(true);
|
|
217
|
+
}
|
|
218
|
+
}, [applyNavigation]);
|
|
219
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: containerRef, className, style: { height: "100%", minHeight: 480, ...style }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
220
|
+
import_react2.default,
|
|
221
|
+
{
|
|
222
|
+
ref: handleRef,
|
|
223
|
+
...viewerProps,
|
|
224
|
+
options: viewerOptions,
|
|
225
|
+
onStateChange: handleStateChange
|
|
226
|
+
}
|
|
227
|
+
) });
|
|
228
|
+
}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ViewerMountOptions, FileViewerHandle } from '@file-viewer/react';
|
|
3
|
+
import { FilePilotRenderProps } from '@macrowing/filepilot-types';
|
|
4
|
+
|
|
5
|
+
type ViewerOptions = NonNullable<ViewerMountOptions['options']>;
|
|
6
|
+
type ViewerLineRange = {
|
|
7
|
+
from: number;
|
|
8
|
+
to: number;
|
|
9
|
+
};
|
|
10
|
+
type ViewerParserOptions = ViewerOptions & {
|
|
11
|
+
line?: number;
|
|
12
|
+
lineRange?: ViewerLineRange;
|
|
13
|
+
lineRangeHighlightColor?: string;
|
|
14
|
+
textContent?: boolean;
|
|
15
|
+
onHandleReady?: (handle: FileViewerHandle) => void;
|
|
16
|
+
onStateChange?: ViewerMountOptions['onStateChange'];
|
|
17
|
+
};
|
|
18
|
+
declare function ViewerParser({ file, className, style, options, locale }: FilePilotRenderProps<ViewerParserOptions>): React.JSX.Element;
|
|
19
|
+
|
|
20
|
+
export { type ViewerLineRange, type ViewerParserOptions, ViewerParser as default };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ViewerMountOptions, FileViewerHandle } from '@file-viewer/react';
|
|
3
|
+
import { FilePilotRenderProps } from '@macrowing/filepilot-types';
|
|
4
|
+
|
|
5
|
+
type ViewerOptions = NonNullable<ViewerMountOptions['options']>;
|
|
6
|
+
type ViewerLineRange = {
|
|
7
|
+
from: number;
|
|
8
|
+
to: number;
|
|
9
|
+
};
|
|
10
|
+
type ViewerParserOptions = ViewerOptions & {
|
|
11
|
+
line?: number;
|
|
12
|
+
lineRange?: ViewerLineRange;
|
|
13
|
+
lineRangeHighlightColor?: string;
|
|
14
|
+
textContent?: boolean;
|
|
15
|
+
onHandleReady?: (handle: FileViewerHandle) => void;
|
|
16
|
+
onStateChange?: ViewerMountOptions['onStateChange'];
|
|
17
|
+
};
|
|
18
|
+
declare function ViewerParser({ file, className, style, options, locale }: FilePilotRenderProps<ViewerParserOptions>): React.JSX.Element;
|
|
19
|
+
|
|
20
|
+
export { type ViewerLineRange, type ViewerParserOptions, ViewerParser as default };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
// src/index.tsx
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef } from "react";
|
|
3
|
+
import FileViewer from "@file-viewer/react";
|
|
4
|
+
import litePreset from "@file-viewer/preset-lite";
|
|
5
|
+
import { jsx } from "react/jsx-runtime";
|
|
6
|
+
var isSourceUrl = (source) => /^(?:https?:|data:|blob:|\/|\.\/|\.\.\/)/i.test(source);
|
|
7
|
+
var toViewerProps = (source, name, textContent) => {
|
|
8
|
+
if (typeof source === "string" && !textContent && isSourceUrl(source)) return { url: source, name };
|
|
9
|
+
if (source instanceof Blob) return { file: source, name };
|
|
10
|
+
const buffer = typeof source === "string" ? new TextEncoder().encode(source).buffer : source instanceof Uint8Array ? source.slice().buffer : source;
|
|
11
|
+
return { buffer, name };
|
|
12
|
+
};
|
|
13
|
+
var HIGHLIGHT_ATTRIBUTE = "data-filepilot-line-range-highlight";
|
|
14
|
+
var HIGHLIGHT_OVERLAY_ATTRIBUTE = "data-filepilot-line-range-highlight-overlay";
|
|
15
|
+
var PREVIOUS_BACKGROUND_ATTRIBUTE = "data-filepilot-previous-background-color";
|
|
16
|
+
var PREVIOUS_BACKGROUND_PRIORITY_ATTRIBUTE = "data-filepilot-previous-background-priority";
|
|
17
|
+
var getViewerRoots = (container) => {
|
|
18
|
+
if (!container) return [];
|
|
19
|
+
const roots = [container];
|
|
20
|
+
if (container.shadowRoot) roots.push(container.shadowRoot);
|
|
21
|
+
container.querySelectorAll("*").forEach((element) => {
|
|
22
|
+
if (element.shadowRoot) roots.push(element.shadowRoot);
|
|
23
|
+
});
|
|
24
|
+
return roots;
|
|
25
|
+
};
|
|
26
|
+
var clearLineRangeHighlight = (container) => {
|
|
27
|
+
getViewerRoots(container).forEach((root) => {
|
|
28
|
+
root.querySelectorAll(`[${HIGHLIGHT_OVERLAY_ATTRIBUTE}]`).forEach((element) => element.remove());
|
|
29
|
+
root.querySelectorAll(`[${HIGHLIGHT_ATTRIBUTE}]`).forEach((element) => {
|
|
30
|
+
element.style.setProperty(
|
|
31
|
+
"background-color",
|
|
32
|
+
element.getAttribute(PREVIOUS_BACKGROUND_ATTRIBUTE) ?? "",
|
|
33
|
+
element.getAttribute(PREVIOUS_BACKGROUND_PRIORITY_ATTRIBUTE) ?? ""
|
|
34
|
+
);
|
|
35
|
+
element.removeAttribute(HIGHLIGHT_ATTRIBUTE);
|
|
36
|
+
element.removeAttribute(PREVIOUS_BACKGROUND_ATTRIBUTE);
|
|
37
|
+
element.removeAttribute(PREVIOUS_BACKGROUND_PRIORITY_ATTRIBUTE);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
var markLineRangeElement = (element, color) => {
|
|
42
|
+
if (!element.hasAttribute(HIGHLIGHT_ATTRIBUTE)) {
|
|
43
|
+
element.setAttribute(PREVIOUS_BACKGROUND_ATTRIBUTE, element.style.backgroundColor);
|
|
44
|
+
element.setAttribute(PREVIOUS_BACKGROUND_PRIORITY_ATTRIBUTE, element.style.getPropertyPriority("background-color"));
|
|
45
|
+
}
|
|
46
|
+
element.setAttribute(HIGHLIGHT_ATTRIBUTE, "true");
|
|
47
|
+
element.style.setProperty("background-color", color, "important");
|
|
48
|
+
};
|
|
49
|
+
var isLeafLineElement = (element) => {
|
|
50
|
+
if (element.matches("pre, code") && /\r\n|\r|\n/.test(element.textContent || "")) return false;
|
|
51
|
+
return !element.querySelector("[data-viewer-line], [data-line]");
|
|
52
|
+
};
|
|
53
|
+
var getLineElements = (root, line) => {
|
|
54
|
+
return Array.from(root.querySelectorAll(`[data-viewer-line="${line}"], [data-line="${line}"]`)).filter(isLeafLineElement);
|
|
55
|
+
};
|
|
56
|
+
var createCodeLineOverlays = (element, from, to, color) => {
|
|
57
|
+
const lines = (element.textContent || "").split(/\r\n|\r|\n/);
|
|
58
|
+
if (lines.length <= 1) return [];
|
|
59
|
+
const ownerDocument = element.ownerDocument;
|
|
60
|
+
const overlays = [];
|
|
61
|
+
const computedStyle = ownerDocument.defaultView?.getComputedStyle(element);
|
|
62
|
+
const lineHeight = parseFloat(computedStyle?.lineHeight ?? "") || parseFloat(computedStyle?.fontSize ?? "16") * 1.2;
|
|
63
|
+
const start = Math.max(1, from);
|
|
64
|
+
const end = Math.min(to, lines.length);
|
|
65
|
+
if (start > end) return [];
|
|
66
|
+
if (ownerDocument.defaultView?.getComputedStyle(element.parentElement ?? element).position === "static") {
|
|
67
|
+
(element.parentElement ?? element).style.position = "relative";
|
|
68
|
+
}
|
|
69
|
+
for (let line = start; line <= end; line += 1) {
|
|
70
|
+
const overlay = ownerDocument.createElement("span");
|
|
71
|
+
overlay.setAttribute(HIGHLIGHT_OVERLAY_ATTRIBUTE, "true");
|
|
72
|
+
overlay.style.position = "absolute";
|
|
73
|
+
overlay.style.left = "0";
|
|
74
|
+
overlay.style.right = "0";
|
|
75
|
+
overlay.style.top = `${(line - 1) * lineHeight}px`;
|
|
76
|
+
overlay.style.height = `${lineHeight}px`;
|
|
77
|
+
overlay.style.pointerEvents = "none";
|
|
78
|
+
overlay.style.backgroundColor = color;
|
|
79
|
+
overlay.style.zIndex = "0";
|
|
80
|
+
overlays.push(overlay);
|
|
81
|
+
}
|
|
82
|
+
return overlays;
|
|
83
|
+
};
|
|
84
|
+
var markCodeBlockRange = (element, from, to, color) => {
|
|
85
|
+
const parent = element.parentElement;
|
|
86
|
+
if (!parent) return;
|
|
87
|
+
createCodeLineOverlays(element, from, to, color).forEach((overlay) => parent.insertBefore(overlay, element));
|
|
88
|
+
element.style.position = "relative";
|
|
89
|
+
element.style.zIndex = "1";
|
|
90
|
+
};
|
|
91
|
+
var applyLineRangeHighlight = async (container, handle, range, color) => {
|
|
92
|
+
clearLineRangeHighlight(container);
|
|
93
|
+
if (!range || !color) return;
|
|
94
|
+
const from = Math.min(range.from, range.to);
|
|
95
|
+
const to = Math.max(range.from, range.to);
|
|
96
|
+
if (!Number.isFinite(from) || !Number.isFinite(to) || from < 1) return;
|
|
97
|
+
await handle?.collectDocumentAnchors();
|
|
98
|
+
getViewerRoots(container).forEach((root) => {
|
|
99
|
+
for (let line = from; line <= to; line += 1) {
|
|
100
|
+
getLineElements(root, line).forEach((element) => markLineRangeElement(element, color));
|
|
101
|
+
}
|
|
102
|
+
if (!root.querySelector(`[${HIGHLIGHT_ATTRIBUTE}]`)) {
|
|
103
|
+
const codeBlock = root.querySelector(".code-area code, pre code");
|
|
104
|
+
if (codeBlock) markCodeBlockRange(codeBlock, from, to, color);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
};
|
|
108
|
+
var useShallowStableValue = (value) => {
|
|
109
|
+
const ref = useRef(value);
|
|
110
|
+
const previous = ref.current;
|
|
111
|
+
const previousKeys = Object.keys(previous);
|
|
112
|
+
const nextKeys = Object.keys(value);
|
|
113
|
+
const isEqual = previousKeys.length === nextKeys.length && nextKeys.every((key) => previous[key] === value[key]);
|
|
114
|
+
if (!isEqual) ref.current = value;
|
|
115
|
+
return ref.current;
|
|
116
|
+
};
|
|
117
|
+
var toViewerLocale = (locale) => locale?.startsWith("en") ? "en-US" : "zh-CN";
|
|
118
|
+
var withViewerOptions = (options, locale) => {
|
|
119
|
+
const preset = options?.preset;
|
|
120
|
+
const viewerLocale = options?.locale ?? toViewerLocale(locale);
|
|
121
|
+
const i18nLocale = options?.i18n?.locale ?? viewerLocale;
|
|
122
|
+
const toolbar = typeof options?.toolbar === "object" && options.toolbar ? options.toolbar : {};
|
|
123
|
+
return {
|
|
124
|
+
...options,
|
|
125
|
+
locale: viewerLocale,
|
|
126
|
+
i18n: {
|
|
127
|
+
...options?.i18n,
|
|
128
|
+
locale: i18nLocale
|
|
129
|
+
},
|
|
130
|
+
toolbar: {
|
|
131
|
+
download: false,
|
|
132
|
+
print: false,
|
|
133
|
+
exportHtml: false,
|
|
134
|
+
theme: false,
|
|
135
|
+
...toolbar
|
|
136
|
+
},
|
|
137
|
+
preset: preset ? Array.isArray(preset) ? [litePreset, ...preset] : [litePreset, preset] : litePreset
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
function ViewerParser({ file, className, style, options, locale }) {
|
|
141
|
+
const containerRef = useRef(null);
|
|
142
|
+
const handleRef = useRef(null);
|
|
143
|
+
const scrolledLineRef = useRef(void 0);
|
|
144
|
+
const highlightedRangeRef = useRef(void 0);
|
|
145
|
+
const navigationRef = useRef({});
|
|
146
|
+
const onStateChangeRef = useRef(void 0);
|
|
147
|
+
const onHandleReadyRef = useRef(void 0);
|
|
148
|
+
const typedOptions = options;
|
|
149
|
+
const { line, lineRange, lineRangeHighlightColor, textContent, onHandleReady, onStateChange, ...restViewerOptions } = typedOptions ?? {};
|
|
150
|
+
const stableViewerOptions = useShallowStableValue(restViewerOptions);
|
|
151
|
+
const viewerOptions = useMemo(() => withViewerOptions(stableViewerOptions, locale), [stableViewerOptions, locale]);
|
|
152
|
+
const viewerProps = useMemo(() => toViewerProps(file.source, file.name, textContent), [file.source, file.name, textContent]);
|
|
153
|
+
navigationRef.current = { line, lineRange, lineRangeHighlightColor };
|
|
154
|
+
onStateChangeRef.current = onStateChange;
|
|
155
|
+
onHandleReadyRef.current = onHandleReady;
|
|
156
|
+
const applyNavigation = useCallback((force = false) => {
|
|
157
|
+
const { line: line2, lineRange: lineRange2, lineRangeHighlightColor: lineRangeHighlightColor2 } = navigationRef.current;
|
|
158
|
+
const rangeKey = lineRange2 ? `${lineRange2.from}:${lineRange2.to}:${lineRangeHighlightColor2 ?? ""}` : "";
|
|
159
|
+
if (line2 && scrolledLineRef.current !== line2 && handleRef.current) {
|
|
160
|
+
void handleRef.current.scrollToLine(line2);
|
|
161
|
+
scrolledLineRef.current = line2;
|
|
162
|
+
}
|
|
163
|
+
if (!force && highlightedRangeRef.current === rangeKey) return;
|
|
164
|
+
highlightedRangeRef.current = rangeKey;
|
|
165
|
+
void applyLineRangeHighlight(containerRef.current, handleRef.current, lineRange2, lineRangeHighlightColor2);
|
|
166
|
+
}, []);
|
|
167
|
+
useEffect(() => {
|
|
168
|
+
scrolledLineRef.current = void 0;
|
|
169
|
+
highlightedRangeRef.current = void 0;
|
|
170
|
+
applyNavigation();
|
|
171
|
+
}, [file.source, line, lineRange?.from, lineRange?.to, lineRangeHighlightColor, applyNavigation]);
|
|
172
|
+
useEffect(() => {
|
|
173
|
+
if (handleRef.current && onHandleReadyRef.current) {
|
|
174
|
+
onHandleReadyRef.current(handleRef.current);
|
|
175
|
+
}
|
|
176
|
+
}, []);
|
|
177
|
+
const handleStateChange = useCallback((state, event) => {
|
|
178
|
+
onStateChangeRef.current?.(state, event);
|
|
179
|
+
if (state.ready && (!event || event.type === "load-complete")) {
|
|
180
|
+
scrolledLineRef.current = void 0;
|
|
181
|
+
highlightedRangeRef.current = void 0;
|
|
182
|
+
applyNavigation(true);
|
|
183
|
+
}
|
|
184
|
+
}, [applyNavigation]);
|
|
185
|
+
return /* @__PURE__ */ jsx("div", { ref: containerRef, className, style: { height: "100%", minHeight: 480, ...style }, children: /* @__PURE__ */ jsx(
|
|
186
|
+
FileViewer,
|
|
187
|
+
{
|
|
188
|
+
ref: handleRef,
|
|
189
|
+
...viewerProps,
|
|
190
|
+
options: viewerOptions,
|
|
191
|
+
onStateChange: handleStateChange
|
|
192
|
+
}
|
|
193
|
+
) });
|
|
194
|
+
}
|
|
195
|
+
export {
|
|
196
|
+
ViewerParser as default
|
|
197
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@macrowing/filepilot-parser-viewer",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Text and Markdown parser powered by @file-viewer/react for FilePilot.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"main": "./dist/index.cjs",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.cjs"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"react": ">=18.2.0 || >=19.0.0"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@file-viewer/preset-lite": "2.2.9",
|
|
25
|
+
"@file-viewer/react": "2.2.9",
|
|
26
|
+
"@macrowing/filepilot-types": "0.1.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/react": "^19.0.12",
|
|
30
|
+
"rimraf": "^6.0.1",
|
|
31
|
+
"tsup": "^8.4.0"
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsup src/index.tsx --format esm,cjs --dts --clean --external react",
|
|
38
|
+
"clean": "rimraf dist",
|
|
39
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
40
|
+
}
|
|
41
|
+
}
|