@file-viewer/renderer-text 3.0.0 → 3.0.1
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/README.en.md +7 -0
- package/README.md +7 -0
- package/dist/code.d.ts +3 -5
- package/dist/code.js +176 -35
- package/dist/codeStyle.d.ts +1 -1
- package/dist/codeStyle.js +18 -4
- package/dist/largeText.js +170 -16
- package/dist/prettyPrint.d.ts +35 -0
- package/dist/prettyPrint.js +232 -0
- package/package.json +6 -4
package/README.en.md
CHANGED
|
@@ -13,6 +13,9 @@ const options = {
|
|
|
13
13
|
renderers: textRenderer,
|
|
14
14
|
text: {
|
|
15
15
|
lineNumbers: true,
|
|
16
|
+
wrapLongLines: true,
|
|
17
|
+
prettyPrint: true,
|
|
18
|
+
prettyPrintMaxBytes: 512 * 1024,
|
|
16
19
|
},
|
|
17
20
|
}
|
|
18
21
|
```
|
|
@@ -34,6 +37,10 @@ const options = {
|
|
|
34
37
|
- Code and text preview uses `highlight.js` core with per-language dynamic imports instead of registering every language up front.
|
|
35
38
|
- Code, text, and virtualized Markdown source views show their file type, indexing status, and line-count metadata bar by default. Set `options.text.toolbar: false` to hide this renderer-local bar without hiding the viewer-level download, search, or zoom toolbar.
|
|
36
39
|
- Regular code and text previews can show a line-number gutter with `options.text.lineNumbers: true`. The gutter is excluded from copied source, search matches, and assistive reading. Virtual large-text views keep their existing gutter unless it is explicitly set to `false`.
|
|
40
|
+
- `options.text.wrapLongLines: true` changes layout only and never inserts source newlines. Regular previews keep one gutter entry per logical line; large files remain bounded in the virtual window while wrapping to the available width.
|
|
41
|
+
- `options.text.prettyPrint: true` lazily loads Prettier and only the parser plugins needed by a supported structured format. It formats a display copy, labels the toolbar as a formatted preview, and provides a switch back to the original source. JSON/JSONC/JSON5, JavaScript/TypeScript, HTML/Vue, CSS, YAML, Markdown, GraphQL, and XML share this path.
|
|
42
|
+
- `prettyPrintMaxBytes` limits only Prettier and defaults to the effective `virtualizeAboveBytes` value (512 KiB when omitted). Oversized, malformed, and unsupported inputs fall back to the original source, which continues through the existing regular or virtual renderer. XML uses a conservative whitespace-preserving mode; mixed content and `xml:space="preserve"` stay on the original source path.
|
|
43
|
+
- The legacy `*-full` script-tag IIFE assets do not bundle Prettier, so `prettyPrint` falls back to the original source there. Use the ESM integration (standard component packages or `@file-viewer/preset-*`) for formatted previews.
|
|
37
44
|
- With the text-tools capability installed, `patch` uses `diff2html` for side-by-side review and `bundle` / `bdl` enables Git bundle inspection.
|
|
38
45
|
- With the Mermaid capability installed, fenced Mermaid blocks render as diagrams. Without it, the source stays visible with the exact CLI enablement command.
|
|
39
46
|
- HTML, XML, Vue, and similar files are escaped and shown as source, never executed.
|
package/README.md
CHANGED
|
@@ -13,6 +13,9 @@ const options = {
|
|
|
13
13
|
renderers: textRenderer,
|
|
14
14
|
text: {
|
|
15
15
|
lineNumbers: true,
|
|
16
|
+
wrapLongLines: true,
|
|
17
|
+
prettyPrint: true,
|
|
18
|
+
prettyPrintMaxBytes: 512 * 1024,
|
|
16
19
|
},
|
|
17
20
|
}
|
|
18
21
|
```
|
|
@@ -34,6 +37,10 @@ const options = {
|
|
|
34
37
|
- 代码和文本使用 `highlight.js` core + 按语言动态加载,避免一次性注册全部语言。
|
|
35
38
|
- 代码、文本和超大 Markdown 源码视图默认显示文件类型、索引状态和行数元信息栏;传入 `options.text.toolbar: false` 可隐藏该 renderer 内部栏,不影响 Viewer 的下载、搜索、缩放等全局工具栏。
|
|
36
39
|
- 普通代码和文本可通过 `options.text.lineNumbers: true` 显示行号;行号不会进入复制内容、搜索结果或无障碍朗读。超大文本保留原有的虚拟行号栏,可显式传 `false` 隐藏。
|
|
40
|
+
- `options.text.wrapLongLines: true` 仅改变布局,不向源码插入换行。普通预览会按逻辑行维护行号,超大文本仍使用有界虚拟窗口并按可用宽度换行。
|
|
41
|
+
- `options.text.prettyPrint: true` 会在支持的结构化文本上按需加载 Prettier 与对应 parser,仅格式化显示副本;工具栏会标明“格式化预览”,并可切回原始源码。JSON/JSONC/JSON5、JavaScript/TypeScript、HTML/Vue、CSS、YAML、Markdown、GraphQL 和 XML 共用同一路径。
|
|
42
|
+
- `prettyPrintMaxBytes` 只限制 Prettier,默认继承 `virtualizeAboveBytes`(未配置时为 512 KiB)。超限、语法错误或不支持的格式直接回退原始源码,之后仍由普通或虚拟文本 renderer 决定展示方式。XML 使用保守的 whitespace-preserving 模式;检测到混合内容或 `xml:space="preserve"` 时直接保留原文。
|
|
43
|
+
- 历史 `*-full` 包的 script 标签 IIFE 资源不打包 Prettier,该路径下 `prettyPrint` 无错误回退到原始源码;需要格式化预览时使用 ESM 集成(标准组件包或 `@file-viewer/preset-*`)。
|
|
37
44
|
- 安装 text-tools capability 后,`patch` 使用 `diff2html` 渲染左右比对视图,`bundle` / `bdl` 才启用 Git bundle 结构检查。
|
|
38
45
|
- 安装 Mermaid capability 后,Markdown 内嵌 Mermaid 图才会渲染;未安装时保留源码并显示精确 CLI 启用命令。
|
|
39
46
|
- HTML / XML / Vue 等文件按源码方式转义展示,不执行脚本。
|
package/dist/code.d.ts
CHANGED
|
@@ -2,10 +2,8 @@ import { type FileRenderContext, type FileViewerRenderedInstance } from '@file-v
|
|
|
2
2
|
/**
|
|
3
3
|
* Framework-neutral text/code renderer.
|
|
4
4
|
*
|
|
5
|
-
* highlight.js core and
|
|
6
|
-
* and XML are
|
|
7
|
-
*
|
|
8
|
-
* @param target 目标
|
|
9
|
-
* @param type 文件扩展名,用于选择 highlight.js 语言
|
|
5
|
+
* highlight.js core, Prettier standalone, and parser definitions are loaded
|
|
6
|
+
* lazily by format. HTML and XML are always mounted as escaped source text and
|
|
7
|
+
* never executed as real DOM.
|
|
10
8
|
*/
|
|
11
9
|
export default function renderText(buffer: ArrayBuffer, target: HTMLDivElement, type?: string, context?: FileRenderContext): Promise<FileViewerRenderedInstance>;
|
package/dist/code.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createFileViewerTranslator, createFileViewerZoomChangeEmitter as createZoomChangeEmitter, decodeFileViewerTextBuffer, registerFileViewerZoomProvider, unregisterFileViewerZoomProvider } from '@file-viewer/core';
|
|
2
2
|
import { codeStyle } from './codeStyle.js';
|
|
3
3
|
import renderLargeText, { shouldVirtualizeTextBuffer } from './largeText.js';
|
|
4
|
+
import { formatFileViewerTextForDisplay, resolveFileViewerPrettyPrintMaxBytes, supportsFileViewerPrettyPrint } from './prettyPrint.js';
|
|
4
5
|
const languageMap = {
|
|
5
6
|
bash: 'bash',
|
|
6
7
|
c: 'cpp',
|
|
@@ -15,6 +16,8 @@ const languageMap = {
|
|
|
15
16
|
bdl: 'plaintext',
|
|
16
17
|
gv: 'plaintext',
|
|
17
18
|
go: 'go',
|
|
19
|
+
graphql: 'graphql',
|
|
20
|
+
gql: 'graphql',
|
|
18
21
|
h: 'cpp',
|
|
19
22
|
hcl: 'plaintext',
|
|
20
23
|
hpp: 'cpp',
|
|
@@ -60,6 +63,7 @@ const languageLoaders = {
|
|
|
60
63
|
css: () => import('highlight.js/lib/languages/css'),
|
|
61
64
|
diff: () => import('highlight.js/lib/languages/diff'),
|
|
62
65
|
go: () => import('highlight.js/lib/languages/go'),
|
|
66
|
+
graphql: () => import('highlight.js/lib/languages/graphql'),
|
|
63
67
|
http: () => import('highlight.js/lib/languages/http'),
|
|
64
68
|
ini: () => import('highlight.js/lib/languages/ini'),
|
|
65
69
|
java: () => import('highlight.js/lib/languages/java'),
|
|
@@ -81,8 +85,8 @@ const languageLoaders = {
|
|
|
81
85
|
};
|
|
82
86
|
let highlighterPromise = null;
|
|
83
87
|
const registeredLanguages = new Set();
|
|
84
|
-
const createElement = (tagName, className, text) => {
|
|
85
|
-
const element =
|
|
88
|
+
const createElement = (documentRef, tagName, className, text) => {
|
|
89
|
+
const element = documentRef.createElement(tagName);
|
|
86
90
|
if (className) {
|
|
87
91
|
element.className = className;
|
|
88
92
|
}
|
|
@@ -91,8 +95,8 @@ const createElement = (tagName, className, text) => {
|
|
|
91
95
|
}
|
|
92
96
|
return element;
|
|
93
97
|
};
|
|
94
|
-
const createStyle = () => {
|
|
95
|
-
const style =
|
|
98
|
+
const createStyle = (documentRef) => {
|
|
99
|
+
const style = documentRef.createElement('style');
|
|
96
100
|
style.textContent = codeStyle;
|
|
97
101
|
return style;
|
|
98
102
|
};
|
|
@@ -139,21 +143,122 @@ const lineCountOf = (value) => {
|
|
|
139
143
|
const createLineNumberText = (lineCount) => {
|
|
140
144
|
return Array.from({ length: lineCount }, (_, index) => String(index + 1)).join('\n');
|
|
141
145
|
};
|
|
146
|
+
const createWrappedSourceLine = (documentRef, lineNumber) => {
|
|
147
|
+
const row = createElement(documentRef, 'span', 'code-source-line');
|
|
148
|
+
row.dataset.line = String(lineNumber);
|
|
149
|
+
const number = createElement(documentRef, 'span', 'code-source-line-number', String(lineNumber));
|
|
150
|
+
number.setAttribute('aria-hidden', 'true');
|
|
151
|
+
const content = createElement(documentRef, 'span', 'code-source-line-content');
|
|
152
|
+
row.append(number, content);
|
|
153
|
+
return { row, content };
|
|
154
|
+
};
|
|
155
|
+
/**
|
|
156
|
+
* Splits trusted highlight.js markup into logical source-line wrappers while
|
|
157
|
+
* recreating active token spans after every newline. This keeps multiline
|
|
158
|
+
* comments and strings highlighted without coupling visual wrapping to source
|
|
159
|
+
* line numbers.
|
|
160
|
+
*/
|
|
161
|
+
const mountWrappedHighlightedLines = (code, highlightedHtml, expectedLineCount) => {
|
|
162
|
+
const documentRef = code.ownerDocument;
|
|
163
|
+
const staging = createElement(documentRef, 'span');
|
|
164
|
+
staging.innerHTML = highlightedHtml;
|
|
165
|
+
const lines = [createWrappedSourceLine(documentRef, 1)];
|
|
166
|
+
let lineIndex = 0;
|
|
167
|
+
const activeElements = [];
|
|
168
|
+
let activeClones = [];
|
|
169
|
+
const currentContent = () => lines[lineIndex].content;
|
|
170
|
+
const currentParent = () => { var _a; return (_a = activeClones[activeClones.length - 1]) !== null && _a !== void 0 ? _a : currentContent(); };
|
|
171
|
+
const continueOnNextLine = () => {
|
|
172
|
+
lineIndex += 1;
|
|
173
|
+
lines.push(createWrappedSourceLine(documentRef, lineIndex + 1));
|
|
174
|
+
let parent = currentContent();
|
|
175
|
+
activeClones = activeElements.map(element => {
|
|
176
|
+
const clone = element.cloneNode(false);
|
|
177
|
+
parent.append(clone);
|
|
178
|
+
parent = clone;
|
|
179
|
+
return clone;
|
|
180
|
+
});
|
|
181
|
+
};
|
|
182
|
+
const visit = (node) => {
|
|
183
|
+
var _a;
|
|
184
|
+
if (node.nodeType === 3) {
|
|
185
|
+
const parts = ((_a = node.textContent) !== null && _a !== void 0 ? _a : '').split(/\r\n|\r|\n/);
|
|
186
|
+
parts.forEach((part, index) => {
|
|
187
|
+
if (part) {
|
|
188
|
+
currentParent().append(documentRef.createTextNode(part));
|
|
189
|
+
}
|
|
190
|
+
if (index < parts.length - 1) {
|
|
191
|
+
continueOnNextLine();
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
if (node.nodeType !== 1) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const element = node;
|
|
200
|
+
const clone = element.cloneNode(false);
|
|
201
|
+
currentParent().append(clone);
|
|
202
|
+
activeElements.push(element);
|
|
203
|
+
activeClones.push(clone);
|
|
204
|
+
Array.from(element.childNodes).forEach(visit);
|
|
205
|
+
activeElements.pop();
|
|
206
|
+
activeClones.pop();
|
|
207
|
+
};
|
|
208
|
+
Array.from(staging.childNodes).forEach(visit);
|
|
209
|
+
while (lines.length < expectedLineCount) {
|
|
210
|
+
lines.push(createWrappedSourceLine(documentRef, lines.length + 1));
|
|
211
|
+
}
|
|
212
|
+
code.replaceChildren(...lines.map(line => line.row));
|
|
213
|
+
};
|
|
214
|
+
const mountCodeMarkup = (pre, code, highlightedHtml, lineCount, showLineNumbers, wrapLongLines) => {
|
|
215
|
+
code.replaceChildren();
|
|
216
|
+
if (showLineNumbers && wrapLongLines) {
|
|
217
|
+
pre.className = 'code-area code-area--wrapped-line-numbers';
|
|
218
|
+
pre.replaceChildren(code);
|
|
219
|
+
mountWrappedHighlightedLines(code, highlightedHtml, lineCount);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
pre.className = showLineNumbers ? 'code-area code-area--line-numbers' : 'code-area';
|
|
223
|
+
if (showLineNumbers) {
|
|
224
|
+
const gutter = createElement(pre.ownerDocument, 'span', 'code-line-numbers', createLineNumberText(lineCount));
|
|
225
|
+
gutter.setAttribute('aria-hidden', 'true');
|
|
226
|
+
pre.replaceChildren(gutter, code);
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
pre.replaceChildren(code);
|
|
230
|
+
}
|
|
231
|
+
code.innerHTML = highlightedHtml;
|
|
232
|
+
};
|
|
233
|
+
const canPossiblyFitDecodedPrettyPrintLimit = (buffer, maxBytes) => {
|
|
234
|
+
// UTF-16 ASCII is the widest supported source encoding relative to its
|
|
235
|
+
// decoded UTF-8 representation. This fast guard avoids decoding enormous
|
|
236
|
+
// structured files that cannot possibly fit the configured decoded limit.
|
|
237
|
+
return buffer.byteLength <= (maxBytes * 2) + 4;
|
|
238
|
+
};
|
|
142
239
|
/**
|
|
143
240
|
* Framework-neutral text/code renderer.
|
|
144
241
|
*
|
|
145
|
-
* highlight.js core and
|
|
146
|
-
* and XML are
|
|
147
|
-
*
|
|
148
|
-
* @param target 目标
|
|
149
|
-
* @param type 文件扩展名,用于选择 highlight.js 语言
|
|
242
|
+
* highlight.js core, Prettier standalone, and parser definitions are loaded
|
|
243
|
+
* lazily by format. HTML and XML are always mounted as escaped source text and
|
|
244
|
+
* never executed as real DOM.
|
|
150
245
|
*/
|
|
151
246
|
export default async function renderText(buffer, target, type, context) {
|
|
152
|
-
var _a, _b
|
|
247
|
+
var _a, _b;
|
|
153
248
|
const t = createFileViewerTranslator(context === null || context === void 0 ? void 0 : context.options);
|
|
154
249
|
const extension = type || 'txt';
|
|
155
250
|
const normalizedExtension = extension.trim().toLowerCase();
|
|
156
|
-
|
|
251
|
+
const textOptions = (_a = context === null || context === void 0 ? void 0 : context.options) === null || _a === void 0 ? void 0 : _a.text;
|
|
252
|
+
let sourceText;
|
|
253
|
+
let prettyPrintResult;
|
|
254
|
+
if ((textOptions === null || textOptions === void 0 ? void 0 : textOptions.prettyPrint) === true &&
|
|
255
|
+
supportsFileViewerPrettyPrint(normalizedExtension) &&
|
|
256
|
+
canPossiblyFitDecodedPrettyPrintLimit(buffer, resolveFileViewerPrettyPrintMaxBytes(textOptions))) {
|
|
257
|
+
sourceText = decodeFileViewerTextBuffer(buffer, textOptions.encoding).text;
|
|
258
|
+
prettyPrintResult = await formatFileViewerTextForDisplay(sourceText, normalizedExtension, textOptions, context === null || context === void 0 ? void 0 : context.signal);
|
|
259
|
+
}
|
|
260
|
+
if (!(prettyPrintResult === null || prettyPrintResult === void 0 ? void 0 : prettyPrintResult.formatted) &&
|
|
261
|
+
normalizedExtension !== 'bundle' &&
|
|
157
262
|
normalizedExtension !== 'bdl' &&
|
|
158
263
|
shouldVirtualizeTextBuffer(buffer, context)) {
|
|
159
264
|
return renderLargeText(buffer, target, extension, context);
|
|
@@ -166,58 +271,92 @@ export default async function renderText(buffer, target, type, context) {
|
|
|
166
271
|
const { default: renderGitBundle } = await import('./gitBundle.js');
|
|
167
272
|
return renderGitBundle(buffer, target, extension, context);
|
|
168
273
|
}
|
|
169
|
-
const
|
|
274
|
+
const originalText = sourceText !== null && sourceText !== void 0 ? sourceText : decodeFileViewerTextBuffer(buffer, textOptions === null || textOptions === void 0 ? void 0 : textOptions.encoding).text;
|
|
275
|
+
const formattedText = (prettyPrintResult === null || prettyPrintResult === void 0 ? void 0 : prettyPrintResult.formatted) ? prettyPrintResult.text : null;
|
|
170
276
|
const language = resolveLanguage(extension);
|
|
171
|
-
const
|
|
172
|
-
const
|
|
173
|
-
const
|
|
277
|
+
const showToolbar = (textOptions === null || textOptions === void 0 ? void 0 : textOptions.toolbar) !== false;
|
|
278
|
+
const showLineNumbers = (textOptions === null || textOptions === void 0 ? void 0 : textOptions.lineNumbers) === true;
|
|
279
|
+
const wrapLongLines = (textOptions === null || textOptions === void 0 ? void 0 : textOptions.wrapLongLines) === true;
|
|
280
|
+
const documentRef = target.ownerDocument;
|
|
174
281
|
let disposed = false;
|
|
175
282
|
let zoom = 1;
|
|
283
|
+
let showingFormatted = formattedText !== null;
|
|
284
|
+
let renderGeneration = 0;
|
|
176
285
|
const zoomEmitter = createZoomChangeEmitter();
|
|
177
|
-
const root = createElement('div', 'code-viewer');
|
|
286
|
+
const root = createElement(documentRef, 'div', wrapLongLines ? 'code-viewer code-viewer--wrap-lines' : 'code-viewer');
|
|
178
287
|
root.dataset.viewerZoomProvider = 'code';
|
|
179
288
|
root.dataset.textToolbar = String(showToolbar);
|
|
180
289
|
root.dataset.lineNumbers = String(showLineNumbers);
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
290
|
+
root.dataset.wrapLongLines = String(wrapLongLines);
|
|
291
|
+
root.dataset.prettyPrint = (_b = prettyPrintResult === null || prettyPrintResult === void 0 ? void 0 : prettyPrintResult.reason) !== null && _b !== void 0 ? _b : 'disabled';
|
|
292
|
+
const toolbar = createElement(documentRef, 'div', 'code-toolbar');
|
|
293
|
+
const extensionLabel = createElement(documentRef, 'span', '', extension.toUpperCase());
|
|
294
|
+
const toolbarMeta = createElement(documentRef, 'div', 'code-toolbar-meta');
|
|
295
|
+
const representationStatus = createElement(documentRef, 'span', 'code-format-status');
|
|
296
|
+
const representationToggle = createElement(documentRef, 'button', 'code-format-toggle');
|
|
297
|
+
representationToggle.type = 'button';
|
|
298
|
+
const lineSummary = createElement(documentRef, 'strong');
|
|
299
|
+
toolbarMeta.append(representationStatus, representationToggle, lineSummary);
|
|
300
|
+
toolbar.append(extensionLabel, toolbarMeta);
|
|
301
|
+
const pre = createElement(documentRef, 'pre', 'code-area');
|
|
302
|
+
const code = createElement(documentRef, 'code', `hljs language-${language}`);
|
|
193
303
|
pre.append(code);
|
|
194
304
|
if (showToolbar) {
|
|
195
305
|
root.append(toolbar);
|
|
196
306
|
}
|
|
197
307
|
root.append(pre);
|
|
198
308
|
root.style.setProperty('--code-font-size', `${13 * zoom}px`);
|
|
199
|
-
target.replaceChildren(createStyle(), root);
|
|
200
|
-
const
|
|
309
|
+
target.replaceChildren(createStyle(documentRef), root);
|
|
310
|
+
const currentText = () => showingFormatted && formattedText !== null ? formattedText : originalText;
|
|
311
|
+
const syncRepresentationControls = (lineCount) => {
|
|
312
|
+
const formatted = showingFormatted && formattedText !== null;
|
|
313
|
+
root.dataset.textRepresentation = formatted ? 'formatted' : 'source';
|
|
314
|
+
lineSummary.textContent = `${lineCount} lines`;
|
|
315
|
+
representationStatus.hidden = !formatted;
|
|
316
|
+
representationStatus.textContent = formatted ? t('text.code.formattedPreview') : '';
|
|
317
|
+
representationToggle.hidden = formattedText === null;
|
|
318
|
+
representationToggle.textContent = formatted
|
|
319
|
+
? t('text.code.showOriginal')
|
|
320
|
+
: t('text.code.showFormatted');
|
|
321
|
+
representationToggle.setAttribute('aria-pressed', String(formatted));
|
|
322
|
+
};
|
|
323
|
+
const renderCurrentRepresentation = async () => {
|
|
324
|
+
const generation = renderGeneration + 1;
|
|
325
|
+
renderGeneration = generation;
|
|
326
|
+
const text = currentText();
|
|
327
|
+
const lineCount = lineCountOf(text);
|
|
328
|
+
syncRepresentationControls(lineCount);
|
|
201
329
|
if (language === 'plaintext') {
|
|
330
|
+
mountCodeMarkup(pre, code, escapeHtml(text), lineCount, showLineNumbers, wrapLongLines);
|
|
202
331
|
return;
|
|
203
332
|
}
|
|
333
|
+
code.textContent = t('text.code.loadingHighlight');
|
|
204
334
|
try {
|
|
205
335
|
const hljs = await loadHighlighter();
|
|
206
336
|
const hasLanguage = await registerLanguageOnce(hljs, language);
|
|
207
|
-
if (disposed) {
|
|
337
|
+
if (disposed || generation !== renderGeneration) {
|
|
208
338
|
return;
|
|
209
339
|
}
|
|
210
|
-
|
|
340
|
+
const highlighted = hasLanguage
|
|
211
341
|
? hljs.highlight(text, { language, ignoreIllegals: true }).value
|
|
212
342
|
: escapeHtml(text);
|
|
343
|
+
mountCodeMarkup(pre, code, highlighted, lineCount, showLineNumbers, wrapLongLines);
|
|
213
344
|
}
|
|
214
345
|
catch {
|
|
215
|
-
if (!disposed) {
|
|
216
|
-
code
|
|
346
|
+
if (!disposed && generation === renderGeneration) {
|
|
347
|
+
mountCodeMarkup(pre, code, escapeHtml(text), lineCount, showLineNumbers, wrapLongLines);
|
|
217
348
|
}
|
|
218
349
|
}
|
|
219
350
|
};
|
|
220
|
-
|
|
351
|
+
const toggleRepresentation = () => {
|
|
352
|
+
if (formattedText === null) {
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
showingFormatted = !showingFormatted;
|
|
356
|
+
void renderCurrentRepresentation();
|
|
357
|
+
};
|
|
358
|
+
representationToggle.addEventListener('click', toggleRepresentation);
|
|
359
|
+
await renderCurrentRepresentation();
|
|
221
360
|
const getZoomState = () => ({
|
|
222
361
|
scale: zoom,
|
|
223
362
|
label: `${Math.round(zoom * 100)}%`,
|
|
@@ -245,6 +384,8 @@ export default async function renderText(buffer, target, type, context) {
|
|
|
245
384
|
$el: target,
|
|
246
385
|
unmount() {
|
|
247
386
|
disposed = true;
|
|
387
|
+
renderGeneration += 1;
|
|
388
|
+
representationToggle.removeEventListener('click', toggleRepresentation);
|
|
248
389
|
unregisterFileViewerZoomProvider(root);
|
|
249
390
|
target.replaceChildren();
|
|
250
391
|
}
|
package/dist/codeStyle.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const codeStyle = "\n.code-viewer{min-height:100%;--code-bg:#f6f8fa;--code-toolbar-bg:rgba(255,255,255,.92);--code-border:rgba(31,35,40,.12);--code-text:#24292f;--code-muted:#57606a;--code-keyword:#cf222e;--code-title:#8250df;--code-string:#0a3069;--code-number:#0550ae;--code-comment:#6e7781;--code-attr:#953800;--code-built-in:#116329;background:var(--code-bg);color:var(--code-text);box-sizing:border-box}\n.code-toolbar{position:sticky;top:0;z-index:
|
|
1
|
+
export declare const codeStyle = "\n.code-viewer{min-height:100%;--code-bg:#f6f8fa;--code-toolbar-bg:rgba(255,255,255,.92);--code-border:rgba(31,35,40,.12);--code-text:#24292f;--code-muted:#57606a;--code-keyword:#cf222e;--code-title:#8250df;--code-string:#0a3069;--code-number:#0550ae;--code-comment:#6e7781;--code-attr:#953800;--code-built-in:#116329;--code-accent:#0969da;--code-accent-border:rgba(9,105,218,.28);--code-accent-soft:rgba(9,105,218,.1);background:var(--code-bg);color:var(--code-text);box-sizing:border-box}\n.code-toolbar{position:sticky;top:0;z-index:2;display:flex;height:42px;align-items:center;justify-content:space-between;gap:16px;padding:0 16px;border-bottom:1px solid var(--code-border);background:var(--code-toolbar-bg);backdrop-filter:blur(12px);box-sizing:border-box}\n.code-toolbar span,.code-toolbar strong{color:var(--code-muted);font-size:12px;font-weight:700;letter-spacing:0}\n.code-toolbar-meta{display:inline-flex;min-width:0;align-items:center;justify-content:flex-end;gap:10px;white-space:nowrap}\n.code-toolbar-meta>span{overflow:hidden;text-overflow:ellipsis}\n.code-format-status{display:inline-flex;align-items:center;min-height:22px;padding:0 8px;border:1px solid var(--code-accent-border);border-radius:999px;background:var(--code-accent-soft);color:var(--code-accent)!important;font-size:11px!important}\n.code-format-toggle{min-height:26px;padding:0 9px;border:1px solid var(--code-border);border-radius:6px;background:var(--code-bg);color:var(--code-text);font:600 11px/1.2 ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;cursor:pointer}\n.code-format-toggle:hover{border-color:var(--code-accent);color:var(--code-accent)}\n.code-format-toggle:focus-visible{outline:2px solid var(--code-accent);outline-offset:2px}\n.code-area{display:block;min-width:min-content;margin:0;padding:18px 20px 28px;overflow:auto;background:transparent;box-sizing:border-box}\n.code-area--line-numbers{display:grid;grid-template-columns:max-content max-content;padding:18px 0 28px}\n.code-line-numbers{position:sticky;left:0;z-index:1;display:block;min-width:4ch;padding:0 12px 0 16px;border-right:1px solid var(--code-border);background:var(--code-bg);color:var(--code-muted);font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,'Liberation Mono',monospace;font-size:var(--code-font-size,13px);line-height:1.7;text-align:right;white-space:pre;user-select:none;box-sizing:border-box}\n.code-area.code-area--line-numbers code{padding:0 20px}\n.code-area code{display:block;padding:0;overflow:visible;background:transparent;color:inherit;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,'Liberation Mono',monospace;font-size:var(--code-font-size,13px);line-height:1.7;tab-size:2;white-space:pre}\n.code-viewer--wrap-lines .code-area{width:100%;min-width:0;overflow-x:hidden}\n.code-viewer--wrap-lines .code-area code{min-width:0;white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word}\n.code-area--wrapped-line-numbers{display:block;padding:18px 0 28px}\n.code-area--wrapped-line-numbers code{width:100%;padding:0;white-space:normal!important}\n.code-source-line{display:grid;width:100%;min-width:0;grid-template-columns:max-content minmax(0,1fr);align-items:stretch;min-height:1.7em}\n.code-source-line-number{position:sticky;left:0;z-index:1;display:block;min-width:4ch;padding:0 12px 0 16px;border-right:1px solid var(--code-border);background:var(--code-bg);color:var(--code-muted);font:inherit;line-height:inherit;text-align:right;white-space:pre;user-select:none;box-sizing:border-box}\n.code-source-line-content{display:block;min-width:0;padding:0 20px;white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word;box-sizing:border-box}\n.code-source-line-content:empty::after{content:' '}\n.code-area .hljs-comment,.code-area .hljs-quote{color:var(--code-comment)}\n.code-area .hljs-keyword,.code-area .hljs-selector-tag,.code-area .hljs-subst{color:var(--code-keyword)}\n.code-area .hljs-string,.code-area .hljs-doctag,.code-area .hljs-regexp{color:var(--code-string)}\n.code-area .hljs-title,.code-area .hljs-section,.code-area .hljs-selector-id{color:var(--code-title);font-weight:700}\n.code-area .hljs-number,.code-area .hljs-literal,.code-area .hljs-variable,.code-area .hljs-template-variable{color:var(--code-number)}\n.code-area .hljs-attr,.code-area .hljs-attribute,.code-area .hljs-name,.code-area .hljs-selector-class{color:var(--code-attr)}\n.code-area .hljs-built_in,.code-area .hljs-type,.code-area .hljs-class .hljs-title{color:var(--code-built-in)}\n[data-viewer-theme='dark'] .code-viewer{--code-bg:#0d1117;--code-toolbar-bg:rgba(13,17,23,.92);--code-border:rgba(139,148,158,.24);--code-text:#e6edf3;--code-muted:#8b949e;--code-keyword:#ff7b72;--code-title:#d2a8ff;--code-string:#a5d6ff;--code-number:#79c0ff;--code-comment:#8b949e;--code-attr:#ffa657;--code-built-in:#7ee787;--code-accent:#58a6ff;--code-accent-border:rgba(88,166,255,.32);--code-accent-soft:rgba(56,139,253,.15)}\n@media (prefers-color-scheme:dark){[data-viewer-theme='system'] .code-viewer{--code-bg:#0d1117;--code-toolbar-bg:rgba(13,17,23,.92);--code-border:rgba(139,148,158,.24);--code-text:#e6edf3;--code-muted:#8b949e;--code-keyword:#ff7b72;--code-title:#d2a8ff;--code-string:#a5d6ff;--code-number:#79c0ff;--code-comment:#8b949e;--code-attr:#ffa657;--code-built-in:#7ee787;--code-accent:#58a6ff;--code-accent-border:rgba(88,166,255,.32);--code-accent-soft:rgba(56,139,253,.15)}}\n";
|
package/dist/codeStyle.js
CHANGED
|
@@ -1,12 +1,26 @@
|
|
|
1
1
|
export const codeStyle = `
|
|
2
|
-
.code-viewer{min-height:100%;--code-bg:#f6f8fa;--code-toolbar-bg:rgba(255,255,255,.92);--code-border:rgba(31,35,40,.12);--code-text:#24292f;--code-muted:#57606a;--code-keyword:#cf222e;--code-title:#8250df;--code-string:#0a3069;--code-number:#0550ae;--code-comment:#6e7781;--code-attr:#953800;--code-built-in:#116329;background:var(--code-bg);color:var(--code-text);box-sizing:border-box}
|
|
3
|
-
.code-toolbar{position:sticky;top:0;z-index:
|
|
2
|
+
.code-viewer{min-height:100%;--code-bg:#f6f8fa;--code-toolbar-bg:rgba(255,255,255,.92);--code-border:rgba(31,35,40,.12);--code-text:#24292f;--code-muted:#57606a;--code-keyword:#cf222e;--code-title:#8250df;--code-string:#0a3069;--code-number:#0550ae;--code-comment:#6e7781;--code-attr:#953800;--code-built-in:#116329;--code-accent:#0969da;--code-accent-border:rgba(9,105,218,.28);--code-accent-soft:rgba(9,105,218,.1);background:var(--code-bg);color:var(--code-text);box-sizing:border-box}
|
|
3
|
+
.code-toolbar{position:sticky;top:0;z-index:2;display:flex;height:42px;align-items:center;justify-content:space-between;gap:16px;padding:0 16px;border-bottom:1px solid var(--code-border);background:var(--code-toolbar-bg);backdrop-filter:blur(12px);box-sizing:border-box}
|
|
4
4
|
.code-toolbar span,.code-toolbar strong{color:var(--code-muted);font-size:12px;font-weight:700;letter-spacing:0}
|
|
5
|
+
.code-toolbar-meta{display:inline-flex;min-width:0;align-items:center;justify-content:flex-end;gap:10px;white-space:nowrap}
|
|
6
|
+
.code-toolbar-meta>span{overflow:hidden;text-overflow:ellipsis}
|
|
7
|
+
.code-format-status{display:inline-flex;align-items:center;min-height:22px;padding:0 8px;border:1px solid var(--code-accent-border);border-radius:999px;background:var(--code-accent-soft);color:var(--code-accent)!important;font-size:11px!important}
|
|
8
|
+
.code-format-toggle{min-height:26px;padding:0 9px;border:1px solid var(--code-border);border-radius:6px;background:var(--code-bg);color:var(--code-text);font:600 11px/1.2 ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;cursor:pointer}
|
|
9
|
+
.code-format-toggle:hover{border-color:var(--code-accent);color:var(--code-accent)}
|
|
10
|
+
.code-format-toggle:focus-visible{outline:2px solid var(--code-accent);outline-offset:2px}
|
|
5
11
|
.code-area{display:block;min-width:min-content;margin:0;padding:18px 20px 28px;overflow:auto;background:transparent;box-sizing:border-box}
|
|
6
12
|
.code-area--line-numbers{display:grid;grid-template-columns:max-content max-content;padding:18px 0 28px}
|
|
7
13
|
.code-line-numbers{position:sticky;left:0;z-index:1;display:block;min-width:4ch;padding:0 12px 0 16px;border-right:1px solid var(--code-border);background:var(--code-bg);color:var(--code-muted);font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,'Liberation Mono',monospace;font-size:var(--code-font-size,13px);line-height:1.7;text-align:right;white-space:pre;user-select:none;box-sizing:border-box}
|
|
8
14
|
.code-area.code-area--line-numbers code{padding:0 20px}
|
|
9
15
|
.code-area code{display:block;padding:0;overflow:visible;background:transparent;color:inherit;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,'Liberation Mono',monospace;font-size:var(--code-font-size,13px);line-height:1.7;tab-size:2;white-space:pre}
|
|
16
|
+
.code-viewer--wrap-lines .code-area{width:100%;min-width:0;overflow-x:hidden}
|
|
17
|
+
.code-viewer--wrap-lines .code-area code{min-width:0;white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word}
|
|
18
|
+
.code-area--wrapped-line-numbers{display:block;padding:18px 0 28px}
|
|
19
|
+
.code-area--wrapped-line-numbers code{width:100%;padding:0;white-space:normal!important}
|
|
20
|
+
.code-source-line{display:grid;width:100%;min-width:0;grid-template-columns:max-content minmax(0,1fr);align-items:stretch;min-height:1.7em}
|
|
21
|
+
.code-source-line-number{position:sticky;left:0;z-index:1;display:block;min-width:4ch;padding:0 12px 0 16px;border-right:1px solid var(--code-border);background:var(--code-bg);color:var(--code-muted);font:inherit;line-height:inherit;text-align:right;white-space:pre;user-select:none;box-sizing:border-box}
|
|
22
|
+
.code-source-line-content{display:block;min-width:0;padding:0 20px;white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word;box-sizing:border-box}
|
|
23
|
+
.code-source-line-content:empty::after{content:' '}
|
|
10
24
|
.code-area .hljs-comment,.code-area .hljs-quote{color:var(--code-comment)}
|
|
11
25
|
.code-area .hljs-keyword,.code-area .hljs-selector-tag,.code-area .hljs-subst{color:var(--code-keyword)}
|
|
12
26
|
.code-area .hljs-string,.code-area .hljs-doctag,.code-area .hljs-regexp{color:var(--code-string)}
|
|
@@ -14,6 +28,6 @@ export const codeStyle = `
|
|
|
14
28
|
.code-area .hljs-number,.code-area .hljs-literal,.code-area .hljs-variable,.code-area .hljs-template-variable{color:var(--code-number)}
|
|
15
29
|
.code-area .hljs-attr,.code-area .hljs-attribute,.code-area .hljs-name,.code-area .hljs-selector-class{color:var(--code-attr)}
|
|
16
30
|
.code-area .hljs-built_in,.code-area .hljs-type,.code-area .hljs-class .hljs-title{color:var(--code-built-in)}
|
|
17
|
-
[data-viewer-theme='dark'] .code-viewer{--code-bg:#0d1117;--code-toolbar-bg:rgba(13,17,23,.92);--code-border:rgba(139,148,158,.24);--code-text:#e6edf3;--code-muted:#8b949e;--code-keyword:#ff7b72;--code-title:#d2a8ff;--code-string:#a5d6ff;--code-number:#79c0ff;--code-comment:#8b949e;--code-attr:#ffa657;--code-built-in:#7ee787}
|
|
18
|
-
@media (prefers-color-scheme:dark){[data-viewer-theme='system'] .code-viewer{--code-bg:#0d1117;--code-toolbar-bg:rgba(13,17,23,.92);--code-border:rgba(139,148,158,.24);--code-text:#e6edf3;--code-muted:#8b949e;--code-keyword:#ff7b72;--code-title:#d2a8ff;--code-string:#a5d6ff;--code-number:#79c0ff;--code-comment:#8b949e;--code-attr:#ffa657;--code-built-in:#7ee787}}
|
|
31
|
+
[data-viewer-theme='dark'] .code-viewer{--code-bg:#0d1117;--code-toolbar-bg:rgba(13,17,23,.92);--code-border:rgba(139,148,158,.24);--code-text:#e6edf3;--code-muted:#8b949e;--code-keyword:#ff7b72;--code-title:#d2a8ff;--code-string:#a5d6ff;--code-number:#79c0ff;--code-comment:#8b949e;--code-attr:#ffa657;--code-built-in:#7ee787;--code-accent:#58a6ff;--code-accent-border:rgba(88,166,255,.32);--code-accent-soft:rgba(56,139,253,.15)}
|
|
32
|
+
@media (prefers-color-scheme:dark){[data-viewer-theme='system'] .code-viewer{--code-bg:#0d1117;--code-toolbar-bg:rgba(13,17,23,.92);--code-border:rgba(139,148,158,.24);--code-text:#e6edf3;--code-muted:#8b949e;--code-keyword:#ff7b72;--code-title:#d2a8ff;--code-string:#a5d6ff;--code-number:#79c0ff;--code-comment:#8b949e;--code-attr:#ffa657;--code-built-in:#7ee787;--code-accent:#58a6ff;--code-accent-border:rgba(88,166,255,.32);--code-accent-soft:rgba(56,139,253,.15)}}
|
|
19
33
|
`;
|
package/dist/largeText.js
CHANGED
|
@@ -8,6 +8,7 @@ const LARGE_TEXT_INDEX_YIELD_BYTES = 4 * 1024 * 1024;
|
|
|
8
8
|
const LARGE_TEXT_SEARCH_CHUNK_BYTES = 256 * 1024;
|
|
9
9
|
const LARGE_TEXT_MAX_SCROLL_HEIGHT = 8000000;
|
|
10
10
|
const LARGE_TEXT_BASE_LINE_HEIGHT = 22.1;
|
|
11
|
+
const LARGE_TEXT_MEASUREMENT_BLOCK_LINES = 256;
|
|
11
12
|
const clamp = (value, minimum, maximum) => {
|
|
12
13
|
return Number.isFinite(value)
|
|
13
14
|
? Math.max(minimum, Math.min(maximum, value))
|
|
@@ -234,8 +235,6 @@ export const shouldVirtualizeMarkdownBuffer = (buffer, context) => {
|
|
|
234
235
|
const largeTextStyle = `
|
|
235
236
|
.code-viewer--virtual{height:100%;min-height:240px;display:flex;flex-direction:column;overflow:hidden}
|
|
236
237
|
.code-viewer--virtual .code-toolbar{flex:0 0 42px}
|
|
237
|
-
.code-toolbar-meta{display:inline-flex;min-width:0;align-items:center;justify-content:flex-end;gap:10px;white-space:nowrap}
|
|
238
|
-
.code-toolbar-meta span{overflow:hidden;text-overflow:ellipsis}
|
|
239
238
|
.code-virtual-scroll{position:relative;flex:1 1 auto;min-width:0;min-height:0;overflow:auto;overscroll-behavior:contain;scrollbar-gutter:stable;contain:strict;background:var(--code-bg)}
|
|
240
239
|
.code-virtual-spacer{position:relative;min-width:100%}
|
|
241
240
|
.code-virtual-window{position:absolute;top:0;left:0;min-width:100%;will-change:transform}
|
|
@@ -248,9 +247,15 @@ const largeTextStyle = `
|
|
|
248
247
|
.code-line-segments button{width:22px;height:18px;padding:0;border:1px solid var(--code-border);border-radius:4px;background:var(--code-bg);color:var(--code-muted);font:700 11px/1 ui-monospace,SFMono-Regular,Menlo,monospace;cursor:pointer}
|
|
249
248
|
.code-line-segments button:disabled{cursor:not-allowed;opacity:.4}
|
|
250
249
|
.code-line-segments span{min-width:64px;color:var(--code-muted);font-size:11px;line-height:1;text-align:center}
|
|
250
|
+
.code-viewer--virtual.code-viewer--wrap-lines .code-virtual-scroll{overflow-x:hidden}
|
|
251
|
+
.code-viewer--virtual.code-viewer--wrap-lines .code-virtual-spacer,.code-viewer--virtual.code-viewer--wrap-lines .code-virtual-window{width:100%;min-width:0}
|
|
252
|
+
.code-viewer--virtual.code-viewer--wrap-lines .code-virtual-line{width:100%;height:auto;min-height:var(--code-line-height,22.1px);min-width:0;align-items:flex-start;white-space:normal;contain:layout paint style}
|
|
253
|
+
.code-viewer--virtual.code-viewer--wrap-lines .code-virtual-number{align-self:stretch}
|
|
254
|
+
.code-viewer--virtual.code-viewer--wrap-lines .code-line-segments{flex:0 0 auto;align-self:stretch;height:auto}
|
|
255
|
+
.code-viewer--virtual.code-viewer--wrap-lines .code-virtual-content{display:block;min-width:0;flex:1 1 auto;padding:0 18px;white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word}
|
|
251
256
|
`;
|
|
252
257
|
export default async function renderLargeText(buffer, target, type = 'txt', context) {
|
|
253
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
258
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
|
|
254
259
|
const t = createFileViewerTranslator(context === null || context === void 0 ? void 0 : context.options);
|
|
255
260
|
const documentRef = target.ownerDocument;
|
|
256
261
|
const sourceBytes = new Uint8Array(buffer);
|
|
@@ -268,9 +273,11 @@ export default async function renderLargeText(buffer, target, type = 'txt', cont
|
|
|
268
273
|
// Undefined preserves the large-text renderer's pre-option behavior. An
|
|
269
274
|
// explicit boolean has the same meaning in both regular and virtual views.
|
|
270
275
|
const showLineNumbers = ((_k = (_j = context === null || context === void 0 ? void 0 : context.options) === null || _j === void 0 ? void 0 : _j.text) === null || _k === void 0 ? void 0 : _k.lineNumbers) !== false;
|
|
276
|
+
const wrapLongLines = ((_m = (_l = context === null || context === void 0 ? void 0 : context.options) === null || _l === void 0 ? void 0 : _l.text) === null || _m === void 0 ? void 0 : _m.wrapLongLines) === true;
|
|
271
277
|
let disposed = false;
|
|
272
278
|
let zoom = 1;
|
|
273
279
|
let scheduledFrame = 0;
|
|
280
|
+
let measurementFrame = 0;
|
|
274
281
|
let lastWindowStart = -1;
|
|
275
282
|
let activeLine = -1;
|
|
276
283
|
let searchGeneration = 0;
|
|
@@ -279,13 +286,17 @@ export default async function renderLargeText(buffer, target, type = 'txt', cont
|
|
|
279
286
|
const style = documentRef.createElement('style');
|
|
280
287
|
style.textContent = `${codeStyle}\n${largeTextStyle}`;
|
|
281
288
|
const root = documentRef.createElement('div');
|
|
282
|
-
root.className =
|
|
283
|
-
|
|
284
|
-
|
|
289
|
+
root.className = [
|
|
290
|
+
'code-viewer',
|
|
291
|
+
'code-viewer--virtual',
|
|
292
|
+
showLineNumbers ? 'code-viewer--line-numbers' : '',
|
|
293
|
+
wrapLongLines ? 'code-viewer--wrap-lines' : ''
|
|
294
|
+
].filter(Boolean).join(' ');
|
|
285
295
|
root.dataset.viewerZoomProvider = 'code';
|
|
286
296
|
root.dataset.viewerSearchProvider = 'code-virtual';
|
|
287
297
|
root.dataset.textToolbar = String(showToolbar);
|
|
288
298
|
root.dataset.lineNumbers = String(showLineNumbers);
|
|
299
|
+
root.dataset.wrapLongLines = String(wrapLongLines);
|
|
289
300
|
root.dataset.textEncoding = source.encoding;
|
|
290
301
|
const toolbar = documentRef.createElement('div');
|
|
291
302
|
toolbar.className = 'code-toolbar';
|
|
@@ -302,7 +313,7 @@ export default async function renderLargeText(buffer, target, type = 'txt', cont
|
|
|
302
313
|
root.append(toolbar);
|
|
303
314
|
}
|
|
304
315
|
target.replaceChildren(style, root);
|
|
305
|
-
(
|
|
316
|
+
(_o = context === null || context === void 0 ? void 0 : context.onProgressiveRender) === null || _o === void 0 ? void 0 : _o.call(context);
|
|
306
317
|
const index = await buildLargeTextIndex(bytes, source.encoding, target, progress => {
|
|
307
318
|
if (!disposed) {
|
|
308
319
|
status.textContent = t('text.code.indexingLargeFile', { progress });
|
|
@@ -329,29 +340,154 @@ export default async function renderLargeText(buffer, target, type = 'txt', cont
|
|
|
329
340
|
root.append(viewport);
|
|
330
341
|
const getLineHeight = () => LARGE_TEXT_BASE_LINE_HEIGHT * zoom;
|
|
331
342
|
const getViewportHeight = () => Math.max(240, viewport.clientHeight || 600);
|
|
343
|
+
const measuredLineHeights = new Map();
|
|
344
|
+
const measuredLineHeightsByBlock = new Map();
|
|
345
|
+
const heightCorrectionTree = new Float64Array(Math.ceil(index.lineCount / LARGE_TEXT_MEASUREMENT_BLOCK_LINES) + 1);
|
|
346
|
+
const updateHeightCorrection = (blockIndex, delta) => {
|
|
347
|
+
for (let treeIndex = blockIndex + 1; treeIndex < heightCorrectionTree.length; treeIndex += treeIndex & -treeIndex) {
|
|
348
|
+
heightCorrectionTree[treeIndex] += delta;
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
const getHeightCorrectionBeforeBlock = (blockIndex) => {
|
|
352
|
+
let correction = 0;
|
|
353
|
+
for (let treeIndex = blockIndex; treeIndex > 0; treeIndex -= treeIndex & -treeIndex) {
|
|
354
|
+
correction += heightCorrectionTree[treeIndex];
|
|
355
|
+
}
|
|
356
|
+
return correction;
|
|
357
|
+
};
|
|
358
|
+
const getOffsetForLine = (requestedLine) => {
|
|
359
|
+
const lineIndex = clamp(Math.trunc(requestedLine), 0, index.lineCount);
|
|
360
|
+
if (!wrapLongLines || lineIndex === 0) {
|
|
361
|
+
return lineIndex * getLineHeight();
|
|
362
|
+
}
|
|
363
|
+
const blockIndex = Math.floor(lineIndex / LARGE_TEXT_MEASUREMENT_BLOCK_LINES);
|
|
364
|
+
let correction = getHeightCorrectionBeforeBlock(blockIndex);
|
|
365
|
+
const blockMeasurements = measuredLineHeightsByBlock.get(blockIndex);
|
|
366
|
+
if (blockMeasurements) {
|
|
367
|
+
for (const [measuredLine, height] of blockMeasurements) {
|
|
368
|
+
if (measuredLine >= lineIndex) {
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
correction += height - getLineHeight();
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return (lineIndex * getLineHeight()) + correction;
|
|
375
|
+
};
|
|
376
|
+
const getTotalContentHeight = () => getOffsetForLine(index.lineCount);
|
|
332
377
|
const getWindowLineCount = () => Math.min(index.lineCount, Math.ceil(getViewportHeight() / getLineHeight()) + (overscan * 2) + 2);
|
|
333
|
-
const getSpacerHeight = () => Math.min(LARGE_TEXT_MAX_SCROLL_HEIGHT, Math.max(getViewportHeight(),
|
|
334
|
-
const usesCappedScrollHeight = () =>
|
|
378
|
+
const getSpacerHeight = () => Math.min(LARGE_TEXT_MAX_SCROLL_HEIGHT, Math.max(getViewportHeight(), getTotalContentHeight()));
|
|
379
|
+
const usesCappedScrollHeight = () => getTotalContentHeight() > LARGE_TEXT_MAX_SCROLL_HEIGHT;
|
|
335
380
|
const updateSpacerHeight = () => {
|
|
336
381
|
root.style.setProperty('--code-font-size', `${13 * zoom}px`);
|
|
337
382
|
root.style.setProperty('--code-line-height', `${getLineHeight()}px`);
|
|
338
383
|
spacer.style.height = `${getSpacerHeight()}px`;
|
|
339
384
|
};
|
|
385
|
+
const getLineAtOffset = (requestedOffset) => {
|
|
386
|
+
const offset = clamp(requestedOffset, 0, Math.max(0, getTotalContentHeight() - 1));
|
|
387
|
+
let low = 0;
|
|
388
|
+
let high = Math.max(0, index.lineCount - 1);
|
|
389
|
+
while (low < high) {
|
|
390
|
+
const middle = Math.ceil((low + high) / 2);
|
|
391
|
+
if (getOffsetForLine(middle) <= offset) {
|
|
392
|
+
low = middle;
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
high = middle - 1;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return low;
|
|
399
|
+
};
|
|
340
400
|
const getFirstVisibleLine = () => {
|
|
341
401
|
if (!usesCappedScrollHeight()) {
|
|
342
|
-
return
|
|
402
|
+
return wrapLongLines
|
|
403
|
+
? getLineAtOffset(viewport.scrollTop)
|
|
404
|
+
: clamp(Math.floor(viewport.scrollTop / getLineHeight()), 0, index.lineCount - 1);
|
|
343
405
|
}
|
|
344
406
|
const maxScrollTop = Math.max(1, getSpacerHeight() - getViewportHeight());
|
|
345
407
|
return clamp(Math.round((viewport.scrollTop / maxScrollTop) * (index.lineCount - 1)), 0, index.lineCount - 1);
|
|
346
408
|
};
|
|
347
409
|
const getWindowOffset = (startLine, renderedLineCount) => {
|
|
348
410
|
if (!usesCappedScrollHeight()) {
|
|
349
|
-
return startLine
|
|
411
|
+
return getOffsetForLine(startLine);
|
|
350
412
|
}
|
|
351
413
|
const maxStart = Math.max(1, index.lineCount - renderedLineCount);
|
|
352
414
|
const maxOffset = Math.max(0, getSpacerHeight() - (renderedLineCount * getLineHeight()));
|
|
353
415
|
return (startLine / maxStart) * maxOffset;
|
|
354
416
|
};
|
|
417
|
+
const setMeasuredLineHeight = (lineIndex, measuredHeight) => {
|
|
418
|
+
var _a;
|
|
419
|
+
const nextHeight = Math.max(getLineHeight(), measuredHeight);
|
|
420
|
+
const previousHeight = (_a = measuredLineHeights.get(lineIndex)) !== null && _a !== void 0 ? _a : getLineHeight();
|
|
421
|
+
if (Math.abs(nextHeight - previousHeight) < 0.5) {
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
424
|
+
const blockIndex = Math.floor(lineIndex / LARGE_TEXT_MEASUREMENT_BLOCK_LINES);
|
|
425
|
+
measuredLineHeights.set(lineIndex, nextHeight);
|
|
426
|
+
let blockMeasurements = measuredLineHeightsByBlock.get(blockIndex);
|
|
427
|
+
if (!blockMeasurements) {
|
|
428
|
+
blockMeasurements = new Map();
|
|
429
|
+
measuredLineHeightsByBlock.set(blockIndex, blockMeasurements);
|
|
430
|
+
}
|
|
431
|
+
blockMeasurements.set(lineIndex, nextHeight);
|
|
432
|
+
updateHeightCorrection(blockIndex, nextHeight - previousHeight);
|
|
433
|
+
return true;
|
|
434
|
+
};
|
|
435
|
+
const clearMeasuredLineHeights = () => {
|
|
436
|
+
measuredLineHeights.clear();
|
|
437
|
+
measuredLineHeightsByBlock.clear();
|
|
438
|
+
heightCorrectionTree.fill(0);
|
|
439
|
+
};
|
|
440
|
+
const scheduleWrappedMeasurement = (startLine, renderedLineCount) => {
|
|
441
|
+
var _a, _b, _c;
|
|
442
|
+
if (!wrapLongLines || disposed || renderedLineCount === 0) {
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
const view = getWindow(target);
|
|
446
|
+
if (measurementFrame && (view === null || view === void 0 ? void 0 : view.cancelAnimationFrame)) {
|
|
447
|
+
view.cancelAnimationFrame(measurementFrame);
|
|
448
|
+
}
|
|
449
|
+
else if (measurementFrame) {
|
|
450
|
+
(_a = view === null || view === void 0 ? void 0 : view.clearTimeout) === null || _a === void 0 ? void 0 : _a.call(view, measurementFrame);
|
|
451
|
+
}
|
|
452
|
+
const measure = () => {
|
|
453
|
+
measurementFrame = 0;
|
|
454
|
+
if (disposed) {
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
const anchorLine = getFirstVisibleLine();
|
|
458
|
+
const anchorOffset = usesCappedScrollHeight()
|
|
459
|
+
? 0
|
|
460
|
+
: viewport.scrollTop - getOffsetForLine(anchorLine);
|
|
461
|
+
let changed = false;
|
|
462
|
+
const rows = Array.from(windowElement.querySelectorAll('.code-virtual-line'));
|
|
463
|
+
for (const row of rows) {
|
|
464
|
+
const lineIndex = Number(row.dataset.line) - 1;
|
|
465
|
+
if (!Number.isInteger(lineIndex) || lineIndex < 0) {
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
const measuredHeight = row.getBoundingClientRect().height || row.offsetHeight || 0;
|
|
469
|
+
if (measuredHeight) {
|
|
470
|
+
changed = setMeasuredLineHeight(lineIndex, measuredHeight) || changed;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
if (!changed) {
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
updateSpacerHeight();
|
|
477
|
+
if (!usesCappedScrollHeight()) {
|
|
478
|
+
viewport.scrollTop = getOffsetForLine(anchorLine) + anchorOffset;
|
|
479
|
+
}
|
|
480
|
+
windowElement.style.transform = `translateY(${getWindowOffset(startLine, renderedLineCount)}px)`;
|
|
481
|
+
lastWindowStart = -1;
|
|
482
|
+
scheduleRender();
|
|
483
|
+
};
|
|
484
|
+
if (view === null || view === void 0 ? void 0 : view.requestAnimationFrame) {
|
|
485
|
+
measurementFrame = view.requestAnimationFrame(measure);
|
|
486
|
+
}
|
|
487
|
+
else {
|
|
488
|
+
measurementFrame = Number((_c = (_b = view === null || view === void 0 ? void 0 : view.setTimeout) === null || _b === void 0 ? void 0 : _b.call(view, measure, 0)) !== null && _c !== void 0 ? _c : setTimeout(measure, 0));
|
|
489
|
+
}
|
|
490
|
+
};
|
|
355
491
|
const appendHighlightedContent = (content, text, query) => {
|
|
356
492
|
if (!query) {
|
|
357
493
|
content.textContent = text || ' ';
|
|
@@ -383,6 +519,7 @@ export default async function renderLargeText(buffer, target, type = 'txt', cont
|
|
|
383
519
|
const row = documentRef.createElement('div');
|
|
384
520
|
row.className = 'code-virtual-line';
|
|
385
521
|
row.dataset.line = String(line.lineIndex + 1);
|
|
522
|
+
row.dataset.logicalLine = String(line.lineIndex + 1);
|
|
386
523
|
if (line.lineIndex === activeLine) {
|
|
387
524
|
row.classList.add('code-virtual-line--match');
|
|
388
525
|
}
|
|
@@ -439,6 +576,7 @@ export default async function renderLargeText(buffer, target, type = 'txt', cont
|
|
|
439
576
|
}
|
|
440
577
|
windowElement.replaceChildren(fragment);
|
|
441
578
|
windowElement.style.transform = `translateY(${getWindowOffset(startLine, lines.length)}px)`;
|
|
579
|
+
scheduleWrappedMeasurement(startLine, lines.length);
|
|
442
580
|
};
|
|
443
581
|
const scheduleRender = () => {
|
|
444
582
|
var _a, _b;
|
|
@@ -470,7 +608,7 @@ export default async function renderLargeText(buffer, target, type = 'txt', cont
|
|
|
470
608
|
: 0;
|
|
471
609
|
}
|
|
472
610
|
else {
|
|
473
|
-
viewport.scrollTop = lineIndex
|
|
611
|
+
viewport.scrollTop = getOffsetForLine(lineIndex);
|
|
474
612
|
}
|
|
475
613
|
lastWindowStart = -1;
|
|
476
614
|
renderWindow(true);
|
|
@@ -592,6 +730,9 @@ export default async function renderLargeText(buffer, target, type = 'txt', cont
|
|
|
592
730
|
const setZoom = (scale) => {
|
|
593
731
|
const firstVisibleLine = getFirstVisibleLine();
|
|
594
732
|
zoom = clampZoom(scale);
|
|
733
|
+
if (wrapLongLines) {
|
|
734
|
+
clearMeasuredLineHeights();
|
|
735
|
+
}
|
|
595
736
|
updateSpacerHeight();
|
|
596
737
|
scrollToLine(firstVisibleLine);
|
|
597
738
|
zoomEmitter.emit();
|
|
@@ -612,7 +753,7 @@ export default async function renderLargeText(buffer, target, type = 'txt', cont
|
|
|
612
753
|
getState: getZoomState,
|
|
613
754
|
subscribe: zoomEmitter.subscribe
|
|
614
755
|
});
|
|
615
|
-
(
|
|
756
|
+
(_p = context === null || context === void 0 ? void 0 : context.registerExportAdapter) === null || _p === void 0 ? void 0 : _p.call(context, { print: false, exportHtml: false });
|
|
616
757
|
viewport.addEventListener('scroll', scheduleRender, { passive: true });
|
|
617
758
|
viewport.addEventListener('click', event => {
|
|
618
759
|
var _a, _b;
|
|
@@ -638,9 +779,16 @@ export default async function renderLargeText(buffer, target, type = 'txt', cont
|
|
|
638
779
|
lineSegments.set(lineIndex, clamp(next, 0, segmentCount - 1));
|
|
639
780
|
renderWindow(true);
|
|
640
781
|
});
|
|
641
|
-
const ResizeObserverCtor = (
|
|
782
|
+
const ResizeObserverCtor = (_q = getWindow(target)) === null || _q === void 0 ? void 0 : _q.ResizeObserver;
|
|
642
783
|
const resizeObserver = ResizeObserverCtor
|
|
643
784
|
? new ResizeObserverCtor(() => {
|
|
785
|
+
if (wrapLongLines) {
|
|
786
|
+
const firstVisibleLine = getFirstVisibleLine();
|
|
787
|
+
clearMeasuredLineHeights();
|
|
788
|
+
updateSpacerHeight();
|
|
789
|
+
scrollToLine(firstVisibleLine);
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
644
792
|
updateSpacerHeight();
|
|
645
793
|
renderWindow(true);
|
|
646
794
|
})
|
|
@@ -651,7 +799,7 @@ export default async function renderLargeText(buffer, target, type = 'txt', cont
|
|
|
651
799
|
return {
|
|
652
800
|
$el: target,
|
|
653
801
|
unmount() {
|
|
654
|
-
var _a, _b;
|
|
802
|
+
var _a, _b, _c;
|
|
655
803
|
disposed = true;
|
|
656
804
|
searchGeneration += 1;
|
|
657
805
|
const view = getWindow(target);
|
|
@@ -661,11 +809,17 @@ export default async function renderLargeText(buffer, target, type = 'txt', cont
|
|
|
661
809
|
else if (scheduledFrame) {
|
|
662
810
|
(_a = view === null || view === void 0 ? void 0 : view.clearTimeout) === null || _a === void 0 ? void 0 : _a.call(view, scheduledFrame);
|
|
663
811
|
}
|
|
812
|
+
if (measurementFrame && (view === null || view === void 0 ? void 0 : view.cancelAnimationFrame)) {
|
|
813
|
+
view.cancelAnimationFrame(measurementFrame);
|
|
814
|
+
}
|
|
815
|
+
else if (measurementFrame) {
|
|
816
|
+
(_b = view === null || view === void 0 ? void 0 : view.clearTimeout) === null || _b === void 0 ? void 0 : _b.call(view, measurementFrame);
|
|
817
|
+
}
|
|
664
818
|
resizeObserver === null || resizeObserver === void 0 ? void 0 : resizeObserver.disconnect();
|
|
665
819
|
viewport.removeEventListener('scroll', scheduleRender);
|
|
666
820
|
unregisterFileViewerSearchProvider(root);
|
|
667
821
|
unregisterFileViewerZoomProvider(root);
|
|
668
|
-
(
|
|
822
|
+
(_c = context === null || context === void 0 ? void 0 : context.registerExportAdapter) === null || _c === void 0 ? void 0 : _c.call(context, null);
|
|
669
823
|
target.replaceChildren();
|
|
670
824
|
}
|
|
671
825
|
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { FileViewerTextOptions } from '@file-viewer/core';
|
|
2
|
+
export declare const DEFAULT_PRETTY_PRINT_MAX_BYTES: number;
|
|
3
|
+
export type FileViewerPrettyPrintReason = 'formatted' | 'disabled' | 'unsupported' | 'too-large' | 'whitespace-sensitive' | 'failed' | 'aborted';
|
|
4
|
+
export interface FileViewerPrettyPrintResult {
|
|
5
|
+
text: string;
|
|
6
|
+
formatted: boolean;
|
|
7
|
+
reason: FileViewerPrettyPrintReason;
|
|
8
|
+
parser?: string;
|
|
9
|
+
sourceByteLength: number;
|
|
10
|
+
maxBytes: number;
|
|
11
|
+
}
|
|
12
|
+
type PrettierPlugin = Record<string, unknown>;
|
|
13
|
+
type PrettierRuntime = {
|
|
14
|
+
format: (source: string, options: Record<string, unknown>) => string | Promise<string>;
|
|
15
|
+
plugins: PrettierPlugin[];
|
|
16
|
+
};
|
|
17
|
+
type PrettierPluginName = 'babel' | 'estree' | 'typescript' | 'postcss' | 'html' | 'markdown' | 'yaml' | 'graphql' | 'xml';
|
|
18
|
+
interface PrettierLanguageDefinition {
|
|
19
|
+
parser: string;
|
|
20
|
+
plugins: readonly PrettierPluginName[];
|
|
21
|
+
resolvePlugins?: (source: string) => readonly PrettierPluginName[];
|
|
22
|
+
options?: Readonly<Record<string, unknown>>;
|
|
23
|
+
}
|
|
24
|
+
export type FileViewerPrettierRuntimeLoader = (definition: PrettierLanguageDefinition, source: string) => Promise<PrettierRuntime>;
|
|
25
|
+
export declare const resolveFileViewerPrettyPrintMaxBytes: (options?: FileViewerTextOptions) => number;
|
|
26
|
+
export declare const supportsFileViewerPrettyPrint: (extension: string) => boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Formats a decoded display representation without mutating the source buffer.
|
|
29
|
+
*
|
|
30
|
+
* Parser support and byte limits are resolved before the Prettier runtime or
|
|
31
|
+
* any parser plugin is imported. Failures intentionally return the original
|
|
32
|
+
* source so malformed or unsupported uploads remain previewable.
|
|
33
|
+
*/
|
|
34
|
+
export declare const formatFileViewerTextForDisplay: (source: string, extension: string, options?: FileViewerTextOptions, signal?: AbortSignal, runtimeLoader?: FileViewerPrettierRuntimeLoader) => Promise<FileViewerPrettyPrintResult>;
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
export const DEFAULT_PRETTY_PRINT_MAX_BYTES = 512 * 1024;
|
|
2
|
+
const htmlEmbeddedLanguagePattern = /<(?:script|style)(?:\s|>)/i;
|
|
3
|
+
const htmlScriptPattern = /<script(?:\s|>)/i;
|
|
4
|
+
const htmlStylePattern = /<style(?:\s|>)/i;
|
|
5
|
+
const withHtmlEmbeddedPlugins = (source) => {
|
|
6
|
+
if (!htmlEmbeddedLanguagePattern.test(source)) {
|
|
7
|
+
return ['html'];
|
|
8
|
+
}
|
|
9
|
+
const plugins = ['html'];
|
|
10
|
+
if (htmlScriptPattern.test(source)) {
|
|
11
|
+
plugins.push('babel', 'estree', 'typescript');
|
|
12
|
+
}
|
|
13
|
+
if (htmlStylePattern.test(source)) {
|
|
14
|
+
plugins.push('postcss');
|
|
15
|
+
}
|
|
16
|
+
return plugins;
|
|
17
|
+
};
|
|
18
|
+
const prettierLanguages = {
|
|
19
|
+
cjs: { parser: 'babel', plugins: ['babel', 'estree'] },
|
|
20
|
+
css: { parser: 'css', plugins: ['postcss'] },
|
|
21
|
+
graphql: { parser: 'graphql', plugins: ['graphql'] },
|
|
22
|
+
gql: { parser: 'graphql', plugins: ['graphql'] },
|
|
23
|
+
html: { parser: 'html', plugins: ['html'], resolvePlugins: withHtmlEmbeddedPlugins },
|
|
24
|
+
htm: { parser: 'html', plugins: ['html'], resolvePlugins: withHtmlEmbeddedPlugins },
|
|
25
|
+
ipynb: { parser: 'json', plugins: ['babel', 'estree'] },
|
|
26
|
+
js: { parser: 'babel', plugins: ['babel', 'estree'] },
|
|
27
|
+
json: { parser: 'json', plugins: ['babel', 'estree'] },
|
|
28
|
+
json5: { parser: 'json5', plugins: ['babel', 'estree'] },
|
|
29
|
+
jsonc: { parser: 'jsonc', plugins: ['babel', 'estree'] },
|
|
30
|
+
jsx: { parser: 'babel', plugins: ['babel', 'estree'] },
|
|
31
|
+
markdown: { parser: 'markdown', plugins: ['markdown'] },
|
|
32
|
+
md: { parser: 'markdown', plugins: ['markdown'] },
|
|
33
|
+
mjs: { parser: 'babel', plugins: ['babel', 'estree'] },
|
|
34
|
+
react: { parser: 'babel', plugins: ['babel', 'estree'] },
|
|
35
|
+
ts: { parser: 'typescript', plugins: ['typescript', 'estree'] },
|
|
36
|
+
tsx: { parser: 'typescript', plugins: ['typescript', 'estree'] },
|
|
37
|
+
vue: {
|
|
38
|
+
parser: 'vue',
|
|
39
|
+
plugins: ['html', 'babel', 'estree', 'typescript', 'postcss']
|
|
40
|
+
},
|
|
41
|
+
xml: {
|
|
42
|
+
parser: 'xml',
|
|
43
|
+
plugins: ['xml'],
|
|
44
|
+
options: {
|
|
45
|
+
xmlWhitespaceSensitivity: 'preserve',
|
|
46
|
+
xmlQuoteAttributes: 'preserve',
|
|
47
|
+
xmlSortAttributesByKey: false
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
yaml: { parser: 'yaml', plugins: ['yaml'] },
|
|
51
|
+
yml: { parser: 'yaml', plugins: ['yaml'] }
|
|
52
|
+
};
|
|
53
|
+
const XML_NAMESPACE = 'http://www.w3.org/XML/1998/namespace';
|
|
54
|
+
const hasWhitespaceSensitiveXml = (source) => {
|
|
55
|
+
if (/\bxml:space\s*=\s*(["'])preserve\1/i.test(source)) {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
const Parser = globalThis.DOMParser;
|
|
59
|
+
if (typeof Parser !== 'function') {
|
|
60
|
+
// The text renderer itself is browser-facing, but fail closed when this
|
|
61
|
+
// helper is evaluated in a non-DOM runtime.
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
const documentRef = new Parser().parseFromString(source, 'application/xml');
|
|
66
|
+
if (documentRef.querySelector('parsererror')) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
const containsMixedContent = (element) => {
|
|
70
|
+
var _a, _b, _c;
|
|
71
|
+
const preservesWhitespace = ((_a = element.getAttributeNS(XML_NAMESPACE, 'space')) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === 'preserve' ||
|
|
72
|
+
((_b = element.getAttribute('xml:space')) === null || _b === void 0 ? void 0 : _b.toLowerCase()) === 'preserve';
|
|
73
|
+
if (preservesWhitespace) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
let hasElementChild = false;
|
|
77
|
+
let hasDirectText = false;
|
|
78
|
+
for (const child of Array.from(element.childNodes)) {
|
|
79
|
+
if (child.nodeType === 1) {
|
|
80
|
+
hasElementChild = true;
|
|
81
|
+
if (containsMixedContent(child)) {
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
else if ((child.nodeType === 3 || child.nodeType === 4) && ((_c = child.textContent) === null || _c === void 0 ? void 0 : _c.trim())) {
|
|
86
|
+
hasDirectText = true;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return hasElementChild && hasDirectText;
|
|
90
|
+
};
|
|
91
|
+
return documentRef.documentElement
|
|
92
|
+
? containsMixedContent(documentRef.documentElement)
|
|
93
|
+
: false;
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
const pluginLoaders = {
|
|
100
|
+
babel: () => import('prettier/plugins/babel').then(module => module.default),
|
|
101
|
+
estree: () => import('prettier/plugins/estree').then(module => module.default),
|
|
102
|
+
typescript: () => import('prettier/plugins/typescript').then(module => module.default),
|
|
103
|
+
postcss: () => import('prettier/plugins/postcss').then(module => module.default),
|
|
104
|
+
html: () => import('prettier/plugins/html').then(module => module.default),
|
|
105
|
+
markdown: () => import('prettier/plugins/markdown').then(module => module.default),
|
|
106
|
+
yaml: () => import('prettier/plugins/yaml').then(module => module.default),
|
|
107
|
+
graphql: () => import('prettier/plugins/graphql').then(module => module.default),
|
|
108
|
+
xml: () => import('@prettier/plugin-xml').then(module => module.default)
|
|
109
|
+
};
|
|
110
|
+
const loadPrettierRuntime = async (definition, source) => {
|
|
111
|
+
var _a, _b;
|
|
112
|
+
const prettierPromise = import('prettier/standalone');
|
|
113
|
+
const pluginNames = (_b = (_a = definition.resolvePlugins) === null || _a === void 0 ? void 0 : _a.call(definition, source)) !== null && _b !== void 0 ? _b : definition.plugins;
|
|
114
|
+
const [prettier, ...plugins] = await Promise.all([
|
|
115
|
+
prettierPromise,
|
|
116
|
+
...pluginNames.map(pluginName => pluginLoaders[pluginName]())
|
|
117
|
+
]);
|
|
118
|
+
return {
|
|
119
|
+
format: prettier.format,
|
|
120
|
+
plugins
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
const utf8ByteLength = (source) => new TextEncoder().encode(source).byteLength;
|
|
124
|
+
export const resolveFileViewerPrettyPrintMaxBytes = (options) => {
|
|
125
|
+
var _a;
|
|
126
|
+
const configured = (_a = options === null || options === void 0 ? void 0 : options.prettyPrintMaxBytes) !== null && _a !== void 0 ? _a : options === null || options === void 0 ? void 0 : options.virtualizeAboveBytes;
|
|
127
|
+
if (!Number.isFinite(configured)) {
|
|
128
|
+
return DEFAULT_PRETTY_PRINT_MAX_BYTES;
|
|
129
|
+
}
|
|
130
|
+
return Math.max(0, Math.trunc(Number(configured)));
|
|
131
|
+
};
|
|
132
|
+
export const supportsFileViewerPrettyPrint = (extension) => {
|
|
133
|
+
return Boolean(prettierLanguages[extension.trim().toLowerCase()]);
|
|
134
|
+
};
|
|
135
|
+
/**
|
|
136
|
+
* Formats a decoded display representation without mutating the source buffer.
|
|
137
|
+
*
|
|
138
|
+
* Parser support and byte limits are resolved before the Prettier runtime or
|
|
139
|
+
* any parser plugin is imported. Failures intentionally return the original
|
|
140
|
+
* source so malformed or unsupported uploads remain previewable.
|
|
141
|
+
*/
|
|
142
|
+
export const formatFileViewerTextForDisplay = async (source, extension, options, signal, runtimeLoader = loadPrettierRuntime) => {
|
|
143
|
+
const maxBytes = resolveFileViewerPrettyPrintMaxBytes(options);
|
|
144
|
+
const sourceByteLength = utf8ByteLength(source);
|
|
145
|
+
if ((options === null || options === void 0 ? void 0 : options.prettyPrint) !== true) {
|
|
146
|
+
return { text: source, formatted: false, reason: 'disabled', sourceByteLength, maxBytes };
|
|
147
|
+
}
|
|
148
|
+
const definition = prettierLanguages[extension.trim().toLowerCase()];
|
|
149
|
+
if (!definition) {
|
|
150
|
+
return { text: source, formatted: false, reason: 'unsupported', sourceByteLength, maxBytes };
|
|
151
|
+
}
|
|
152
|
+
if (sourceByteLength > maxBytes) {
|
|
153
|
+
return {
|
|
154
|
+
text: source,
|
|
155
|
+
formatted: false,
|
|
156
|
+
reason: 'too-large',
|
|
157
|
+
parser: definition.parser,
|
|
158
|
+
sourceByteLength,
|
|
159
|
+
maxBytes
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
163
|
+
return {
|
|
164
|
+
text: source,
|
|
165
|
+
formatted: false,
|
|
166
|
+
reason: 'aborted',
|
|
167
|
+
parser: definition.parser,
|
|
168
|
+
sourceByteLength,
|
|
169
|
+
maxBytes
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
if (definition.parser === 'xml' && hasWhitespaceSensitiveXml(source)) {
|
|
173
|
+
return {
|
|
174
|
+
text: source,
|
|
175
|
+
formatted: false,
|
|
176
|
+
reason: 'whitespace-sensitive',
|
|
177
|
+
parser: definition.parser,
|
|
178
|
+
sourceByteLength,
|
|
179
|
+
maxBytes
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
const runtime = await runtimeLoader(definition, source);
|
|
184
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
185
|
+
return {
|
|
186
|
+
text: source,
|
|
187
|
+
formatted: false,
|
|
188
|
+
reason: 'aborted',
|
|
189
|
+
parser: definition.parser,
|
|
190
|
+
sourceByteLength,
|
|
191
|
+
maxBytes
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
const formatted = await runtime.format(source, {
|
|
195
|
+
parser: definition.parser,
|
|
196
|
+
plugins: runtime.plugins,
|
|
197
|
+
tabWidth: 2,
|
|
198
|
+
useTabs: false,
|
|
199
|
+
printWidth: 80,
|
|
200
|
+
endOfLine: 'lf',
|
|
201
|
+
...definition.options
|
|
202
|
+
});
|
|
203
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
204
|
+
return {
|
|
205
|
+
text: source,
|
|
206
|
+
formatted: false,
|
|
207
|
+
reason: 'aborted',
|
|
208
|
+
parser: definition.parser,
|
|
209
|
+
sourceByteLength,
|
|
210
|
+
maxBytes
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
return {
|
|
214
|
+
text: formatted.replace(/\n$/, ''),
|
|
215
|
+
formatted: true,
|
|
216
|
+
reason: 'formatted',
|
|
217
|
+
parser: definition.parser,
|
|
218
|
+
sourceByteLength,
|
|
219
|
+
maxBytes
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
return {
|
|
224
|
+
text: source,
|
|
225
|
+
formatted: false,
|
|
226
|
+
reason: 'failed',
|
|
227
|
+
parser: definition.parser,
|
|
228
|
+
sourceByteLength,
|
|
229
|
+
maxBytes
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@file-viewer/renderer-text",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Standalone code, text, and Markdown renderer for File Viewer; Mermaid, patch diff, and Git bundle tools are explicit opt-in capabilities.",
|
|
@@ -58,14 +58,16 @@
|
|
|
58
58
|
"LICENSE"
|
|
59
59
|
],
|
|
60
60
|
"dependencies": {
|
|
61
|
-
"@file-viewer/core": "3.0.
|
|
61
|
+
"@file-viewer/core": "3.0.1",
|
|
62
|
+
"@prettier/plugin-xml": "3.4.2",
|
|
63
|
+
"dompurify": "^3.4.14",
|
|
62
64
|
"highlight.js": "^11.11.1",
|
|
63
65
|
"marked": "^18.0.5",
|
|
64
|
-
"
|
|
66
|
+
"prettier": "3.8.3"
|
|
65
67
|
},
|
|
66
68
|
"devDependencies": {
|
|
67
69
|
"diff2html": "^3.4.56",
|
|
68
|
-
"mermaid": "^11.
|
|
70
|
+
"mermaid": "^11.17.2",
|
|
69
71
|
"pako": "^2.1.0",
|
|
70
72
|
"jsdom": "^27.4.0",
|
|
71
73
|
"typescript": "^6.0.3"
|