@file-viewer/renderer-text 2.4.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @file-viewer/renderer-text
2
2
 
3
- Standalone code, text, Markdown, patch, and git bundle renderer package for Flyfish File Viewer. It handles source highlighting, Markdown reading surfaces, embedded Mermaid diagrams in Markdown, side-by-side patch diffs, git bundle inspection, and unified zoom for `.txt`, `.json`, `.ts`, `.vue`, `.log`, `.md`, `.markdown`, `.patch`, `.bundle`, and other code delivery formats.
3
+ Base code, text, and Markdown renderer package for Flyfish File Viewer. Mermaid, side-by-side patch diff, and Git bundle inspection live in `@file-viewer/capability-mermaid` and `@file-viewer/capability-text-tools`; they are excluded from the standard/full default closure.
4
4
 
5
5
  ## Usage
6
6
 
@@ -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,8 +37,12 @@ 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`.
37
- - `patch` uses `diff2html` on demand for side-by-side review.
38
- - `bundle` / `bdl` parses Git bundle headers, refs, commit history, file trees, and readable blobs; regular OFS_DELTA / REF_DELTA objects are resolved in the browser, while very large packs or bundles that depend on external prerequisites surface a clear boundary notice.
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.
44
+ - With the text-tools capability installed, `patch` uses `diff2html` for side-by-side review and `bundle` / `bdl` enables Git bundle inspection.
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.
40
47
  - Markdown uses `marked` for a read-only reading surface with dark/light theme support, table scrolling, and a unified zoom provider.
41
48
  - Markdown no longer falls back to source because of the general large-text threshold. Set `options.text.markdownVirtualizeAboveBytes` only when an application must bound exceptionally large Markdown files.
@@ -43,4 +50,4 @@ const options = {
43
50
 
44
51
  ## Migration Note
45
52
 
46
- `@file-viewer/core` no longer bundles code / markdown renderers and no longer depends directly on `highlight.js`, `marked`, `diff2html`, or git bundle parsing dependencies. Install this package explicitly for code, text, Markdown, patch, or git bundle preview, or use `@file-viewer/preset-all`, which aggregates this renderer automatically.
53
+ Standard/full includes base code, text, and Markdown without installing `diff2html`, `pako`, or Mermaid. Optional uploads show `npx file-viewer-cli add text-tools --write` or `add mermaid-markdown --write`; `preset-all` is only for explicit all/debug use.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @file-viewer/renderer-text
2
2
 
3
- Flyfish File Viewer 的独立代码、文本、Markdown、patch git bundle renderer 包。它负责 `.txt`、`.json`、`.ts`、`.vue`、`.log`、`.md`、`.markdown`、`.patch`、`.bundle` 等文本与代码交付文件的源码高亮、Markdown 阅读面、Markdown 内嵌 Mermaid 图、左右比对、bundle 结构查看和统一缩放。
3
+ Flyfish File Viewer 的基础代码、文本和 Markdown renderer 包。Mermaid、patch 左右比对和 Git bundle 检查已拆到 `@file-viewer/capability-mermaid` `@file-viewer/capability-text-tools`,不进入 standard/full 默认闭包。
4
4
 
5
5
  ## 用法
6
6
 
@@ -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,8 +37,12 @@ const options = {
34
37
  - 代码和文本使用 `highlight.js` core + 按语言动态加载,避免一次性注册全部语言。
35
38
  - 代码、文本和超大 Markdown 源码视图默认显示文件类型、索引状态和行数元信息栏;传入 `options.text.toolbar: false` 可隐藏该 renderer 内部栏,不影响 Viewer 的下载、搜索、缩放等全局工具栏。
36
39
  - 普通代码和文本可通过 `options.text.lineNumbers: true` 显示行号;行号不会进入复制内容、搜索结果或无障碍朗读。超大文本保留原有的虚拟行号栏,可显式传 `false` 隐藏。
37
- - `patch` 使用 `diff2html` 按需渲染左右比对视图。
38
- - `bundle` / `bdl` 会解析 Git bundle headerrefscommit 历史、文件树和可读 blob;常规 OFS_DELTA / REF_DELTA 会在浏览器端还原,超大包或依赖外部 prerequisite 的包会给出清晰边界提示。
40
+ - `options.text.wrapLongLines: true` 仅改变布局,不向源码插入换行。普通预览会按逻辑行维护行号,超大文本仍使用有界虚拟窗口并按可用宽度换行。
41
+ - `options.text.prettyPrint: true` 会在支持的结构化文本上按需加载 Prettier 与对应 parser,仅格式化显示副本;工具栏会标明“格式化预览”,并可切回原始源码。JSON/JSONC/JSON5JavaScript/TypeScriptHTML/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-*`)。
44
+ - 安装 text-tools capability 后,`patch` 使用 `diff2html` 渲染左右比对视图,`bundle` / `bdl` 才启用 Git bundle 结构检查。
45
+ - 安装 Mermaid capability 后,Markdown 内嵌 Mermaid 图才会渲染;未安装时保留源码并显示精确 CLI 启用命令。
39
46
  - HTML / XML / Vue 等文件按源码方式转义展示,不执行脚本。
40
47
  - Markdown 使用 `marked` 输出只读阅读面,并保留明暗主题、表格滚动和统一缩放 provider。
41
48
  - Markdown 不再因为通用大文本阈值自动退化成源码;如业务必须限制超大 Markdown,可单独设置 `options.text.markdownVirtualizeAboveBytes`。
@@ -43,4 +50,4 @@ const options = {
43
50
 
44
51
  ## 迁移说明
45
52
 
46
- `@file-viewer/core` 已不再内置 code / markdown renderer,也不再直接依赖 `highlight.js`、`marked`、`diff2html` 或 Git bundle 解析依赖。需要代码、文本、Markdown、patch git bundle 预览时,请显式安装本包,或使用会自动聚合本包的 `@file-viewer/preset-all`。
53
+ standard/full 默认包含基础代码、文本和 Markdown,不安装 `diff2html`、`pako` 或 Mermaid。打开可选格式时会提示运行 `npx file-viewer-cli add text-tools --write` `add mermaid-markdown --write`;`preset-all` 仅用于显式全量/调试。
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 language definitions are loaded lazily by format. HTML
6
- * and XML are highlighted as escaped source text, never executed as real DOM.
7
- * @param buffer 文本二进制内容
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 = document.createElement(tagName);
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 = document.createElement('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 language definitions are loaded lazily by format. HTML
146
- * and XML are highlighted as escaped source text, never executed as real DOM.
147
- * @param buffer 文本二进制内容
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, _c, _d, _e, _f;
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
- if (normalizedExtension !== 'bundle' &&
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 text = decodeFileViewerTextBuffer(buffer, (_b = (_a = context === null || context === void 0 ? void 0 : context.options) === null || _a === void 0 ? void 0 : _a.text) === null || _b === void 0 ? void 0 : _b.encoding).text;
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 lineCount = lineCountOf(text);
172
- const showToolbar = ((_d = (_c = context === null || context === void 0 ? void 0 : context.options) === null || _c === void 0 ? void 0 : _c.text) === null || _d === void 0 ? void 0 : _d.toolbar) !== false;
173
- const showLineNumbers = ((_f = (_e = context === null || context === void 0 ? void 0 : context.options) === null || _e === void 0 ? void 0 : _e.text) === null || _f === void 0 ? void 0 : _f.lineNumbers) === true;
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
- const toolbar = createElement('div', 'code-toolbar');
182
- toolbar.append(createElement('span', '', extension.toUpperCase()), createElement('strong', '', `${lineCount} lines`));
183
- const pre = createElement('pre', showLineNumbers ? 'code-area code-area--line-numbers' : 'code-area');
184
- const code = createElement('code', `hljs language-${language}`);
185
- code.innerHTML = language === 'plaintext'
186
- ? escapeHtml(text)
187
- : t('text.code.loadingHighlight');
188
- if (showLineNumbers) {
189
- const gutter = createElement('span', 'code-line-numbers', createLineNumberText(lineCount));
190
- gutter.setAttribute('aria-hidden', 'true');
191
- pre.append(gutter);
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 updateHighlighted = async () => {
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
- code.innerHTML = hasLanguage
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.innerHTML = escapeHtml(text);
346
+ if (!disposed && generation === renderGeneration) {
347
+ mountCodeMarkup(pre, code, escapeHtml(text), lineCount, showLineNumbers, wrapLongLines);
217
348
  }
218
349
  }
219
350
  };
220
- void updateHighlighted();
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
  }
@@ -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;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-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-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}\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}}\n";
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:1;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}
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/gitBundle.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { createFileViewerTranslator, createFileViewerZoomChangeEmitter as createZoomChangeEmitter, registerFileViewerZoomProvider, unregisterFileViewerZoomProvider } from '@file-viewer/core';
2
- import { Inflate } from 'pako';
2
+ import { getFileViewerPakoLoader } from './optionalCapabilities.js';
3
+ let Inflate = null;
3
4
  const textDecoder = new TextDecoder('utf-8');
4
5
  const asciiDecoder = new TextDecoder('latin1');
5
6
  const bundleStyle = `
@@ -135,6 +136,8 @@ const readOfsDeltaBase = (bytes, start) => {
135
136
  return { value, offset };
136
137
  };
137
138
  const inflateObject = (bytes, start) => {
139
+ if (!Inflate)
140
+ throw new Error('Git bundle support is opt-in. Run `npx file-viewer-cli add text-tools --write`, then `npx file-viewer-cli install --yes`.');
138
141
  const inflator = new Inflate();
139
142
  let offset = start;
140
143
  while (!inflator.ended && offset < bytes.length) {
@@ -476,6 +479,12 @@ const renderMeta = (documentRef, panel, model, t) => {
476
479
  }
477
480
  };
478
481
  export default async function renderGitBundle(buffer, target, type = 'bundle', context) {
482
+ const loadPako = getFileViewerPakoLoader();
483
+ if (!loadPako) {
484
+ throw new Error('Git bundle support is opt-in. Run `npx file-viewer-cli add text-tools --write`, then `npx file-viewer-cli install --yes`.');
485
+ }
486
+ ;
487
+ ({ Inflate } = await loadPako());
479
488
  const documentRef = target.ownerDocument || document;
480
489
  const t = createFileViewerTranslator(context === null || context === void 0 ? void 0 : context.options);
481
490
  const model = await parseBundleModel(buffer, t);
package/dist/index.d.ts CHANGED
@@ -1,4 +1,7 @@
1
1
  import { type FileRenderHandler, type FileViewerRenderedInstance, type FileViewerRendererPlugin, type RendererDefinition } from '@file-viewer/core';
2
+ export { getFileViewerMermaidLoader, getFileViewerDiffToHtml, getFileViewerPakoLoader, registerFileViewerDiffToHtml, registerFileViewerMermaidLoader, registerFileViewerPakoLoader, } from './optionalCapabilities.js';
3
+ export type { FileViewerMermaidLoader, FileViewerMermaidModule, FileViewerDiffToHtml, FileViewerPakoModule, } from './optionalCapabilities.js';
4
+ export { sanitizeFileViewerRichHtml } from './sanitizeHtml.js';
2
5
  export declare const textRendererDefinitions: RendererDefinition[];
3
6
  export declare const renderFileViewerCode: FileRenderHandler<FileViewerRenderedInstance, HTMLDivElement>;
4
7
  export declare const renderFileViewerMarkdown: FileRenderHandler<FileViewerRenderedInstance, HTMLDivElement>;
package/dist/index.js CHANGED
@@ -1,4 +1,6 @@
1
1
  import { DEFAULT_RENDERER_DEFINITIONS, } from '@file-viewer/core';
2
+ export { getFileViewerMermaidLoader, getFileViewerDiffToHtml, getFileViewerPakoLoader, registerFileViewerDiffToHtml, registerFileViewerMermaidLoader, registerFileViewerPakoLoader, } from './optionalCapabilities.js';
3
+ export { sanitizeFileViewerRichHtml } from './sanitizeHtml.js';
2
4
  const textRendererIds = ['code', 'markdown'];
3
5
  const textDefinitions = DEFAULT_RENDERER_DEFINITIONS.filter(definition => textRendererIds.includes(definition.id));
4
6
  if (textDefinitions.length !== textRendererIds.length) {