@file-viewer/renderer-text 2.2.8 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/code.js CHANGED
@@ -1,4 +1,4 @@
1
- import { createFileViewerTranslator, createFileViewerZoomChangeEmitter as createZoomChangeEmitter, readFileViewerText as readText, registerFileViewerZoomProvider, unregisterFileViewerZoomProvider } from '@file-viewer/core';
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
4
  const languageMap = {
@@ -149,7 +149,7 @@ const createLineNumberText = (lineCount) => {
149
149
  * @param type 文件扩展名,用于选择 highlight.js 语言
150
150
  */
151
151
  export default async function renderText(buffer, target, type, context) {
152
- var _a, _b, _c, _d;
152
+ var _a, _b, _c, _d, _e, _f;
153
153
  const t = createFileViewerTranslator(context === null || context === void 0 ? void 0 : context.options);
154
154
  const extension = type || 'txt';
155
155
  const normalizedExtension = extension.trim().toLowerCase();
@@ -166,11 +166,11 @@ export default async function renderText(buffer, target, type, context) {
166
166
  const { default: renderGitBundle } = await import('./gitBundle.js');
167
167
  return renderGitBundle(buffer, target, extension, context);
168
168
  }
169
- const text = await readText(buffer);
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;
170
170
  const language = resolveLanguage(extension);
171
171
  const lineCount = lineCountOf(text);
172
- const showToolbar = ((_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.toolbar) !== false;
173
- const showLineNumbers = ((_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.lineNumbers) === true;
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;
174
174
  let disposed = false;
175
175
  let zoom = 1;
176
176
  const zoomEmitter = createZoomChangeEmitter();
package/dist/largeText.js CHANGED
@@ -1,4 +1,4 @@
1
- import { DEFAULT_FILE_VIEWER_SEARCH_MAX_MATCHES, createEmptyFileViewerSearchState, createFileViewerTranslator, createFileViewerZoomChangeEmitter as createZoomChangeEmitter, normalizeFileViewerSearchOptions, registerFileViewerSearchProvider, registerFileViewerZoomProvider, unregisterFileViewerSearchProvider, unregisterFileViewerZoomProvider } from '@file-viewer/core';
1
+ import { DEFAULT_FILE_VIEWER_SEARCH_MAX_MATCHES, createFileViewerTextDecoder, createEmptyFileViewerSearchState, createFileViewerTranslator, createFileViewerZoomChangeEmitter as createZoomChangeEmitter, normalizeFileViewerSearchOptions, resolveFileViewerTextEncoding, registerFileViewerSearchProvider, registerFileViewerZoomProvider, unregisterFileViewerSearchProvider, unregisterFileViewerZoomProvider } from '@file-viewer/core';
2
2
  import { codeStyle } from './codeStyle.js';
3
3
  export const DEFAULT_LARGE_TEXT_THRESHOLD_BYTES = 512 * 1024;
4
4
  export const DEFAULT_LARGE_TEXT_LINE_SEGMENT_BYTES = 16 * 1024;
@@ -42,6 +42,42 @@ const alignUtf8End = (bytes, offset, limit) => {
42
42
  }
43
43
  return nextOffset;
44
44
  };
45
+ const gb18030UnitLength = (bytes, offset, limit) => {
46
+ const first = bytes[offset];
47
+ const second = bytes[offset + 1];
48
+ const third = bytes[offset + 2];
49
+ const fourth = bytes[offset + 3];
50
+ if (first === undefined || first <= 0x7f) {
51
+ return 1;
52
+ }
53
+ if (first >= 0x81 && first <= 0xfe) {
54
+ if (second !== undefined && second >= 0x30 && second <= 0x39 &&
55
+ third !== undefined && third >= 0x81 && third <= 0xfe &&
56
+ fourth !== undefined && fourth >= 0x30 && fourth <= 0x39 &&
57
+ offset + 4 <= limit) {
58
+ return 4;
59
+ }
60
+ if (second !== undefined &&
61
+ ((second >= 0x40 && second <= 0x7e) || (second >= 0x80 && second <= 0xfe)) &&
62
+ offset + 2 <= limit) {
63
+ return 2;
64
+ }
65
+ }
66
+ return 1;
67
+ };
68
+ const alignGb18030Boundary = (bytes, knownBoundary, requestedOffset, limit) => {
69
+ let cursor = clamp(knownBoundary, 0, limit);
70
+ const requested = clamp(requestedOffset, cursor, limit);
71
+ while (cursor < requested) {
72
+ cursor = Math.min(limit, cursor + gb18030UnitLength(bytes, cursor, limit));
73
+ }
74
+ return cursor;
75
+ };
76
+ const alignTextBoundary = (index, knownBoundary, requestedOffset, limit) => {
77
+ return index.encoding === 'gb18030'
78
+ ? alignGb18030Boundary(index.bytes, knownBoundary, requestedOffset, limit)
79
+ : alignUtf8End(index.bytes, requestedOffset, limit);
80
+ };
45
81
  const isLineBreak = (bytes, offset) => {
46
82
  const byte = bytes[offset];
47
83
  if (byte === 13) {
@@ -49,7 +85,7 @@ const isLineBreak = (bytes, offset) => {
49
85
  }
50
86
  return byte === 10 ? 1 : 0;
51
87
  };
52
- const buildLargeTextIndex = async (bytes, target, onProgress) => {
88
+ const buildLargeTextIndex = async (bytes, encoding, target, onProgress) => {
53
89
  const checkpoints = [0];
54
90
  let lineIndex = 0;
55
91
  let offset = 0;
@@ -76,6 +112,7 @@ const buildLargeTextIndex = async (bytes, target, onProgress) => {
76
112
  return {
77
113
  bytes,
78
114
  checkpoints,
115
+ encoding,
79
116
  lineCount: lineIndex + 1
80
117
  };
81
118
  };
@@ -140,10 +177,12 @@ const decodeLargeTextSegment = (index, line, segmentIndex, segmentBytes) => {
140
177
  const normalizedSegment = clamp(Math.trunc(segmentIndex), 0, segmentCount - 1);
141
178
  const rawStart = line.start + (normalizedSegment * segmentBytes);
142
179
  const rawEnd = Math.min(line.end, rawStart + segmentBytes);
143
- const start = alignUtf8Start(index.bytes, rawStart, line.end);
144
- const end = alignUtf8End(index.bytes, rawEnd, line.end);
180
+ const start = index.encoding === 'gb18030'
181
+ ? alignTextBoundary(index, line.start, rawStart, line.end)
182
+ : alignUtf8Start(index.bytes, rawStart, line.end);
183
+ const end = alignTextBoundary(index, start, rawEnd, line.end);
145
184
  return {
146
- text: new TextDecoder('utf-8').decode(index.bytes.subarray(start, end)),
185
+ text: createFileViewerTextDecoder(index.encoding).decode(index.bytes.subarray(start, end)),
147
186
  segmentCount,
148
187
  segmentIndex: normalizedSegment
149
188
  };
@@ -167,20 +206,30 @@ const cloneLargeTextSearchState = (state) => ({
167
206
  matches: state.matches.map(match => ({ ...match }))
168
207
  });
169
208
  export const shouldVirtualizeTextBuffer = (buffer, context) => {
170
- var _a, _b;
209
+ var _a, _b, _c, _d;
171
210
  const configured = (_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.virtualizeAboveBytes;
172
211
  const threshold = Number.isFinite(configured)
173
212
  ? Math.max(0, Number(configured))
174
213
  : DEFAULT_LARGE_TEXT_THRESHOLD_BYTES;
175
- return buffer.byteLength > threshold;
214
+ if (buffer.byteLength <= threshold) {
215
+ return false;
216
+ }
217
+ const bytes = new Uint8Array(buffer);
218
+ const { encoding } = resolveFileViewerTextEncoding(bytes, (_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.encoding);
219
+ return encoding !== 'utf-16le' && encoding !== 'utf-16be';
176
220
  };
177
221
  export const shouldVirtualizeMarkdownBuffer = (buffer, context) => {
178
- var _a, _b;
222
+ var _a, _b, _c, _d;
179
223
  const configured = (_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.markdownVirtualizeAboveBytes;
180
224
  if (!Number.isFinite(configured)) {
181
225
  return false;
182
226
  }
183
- return buffer.byteLength > Math.max(0, Number(configured));
227
+ if (buffer.byteLength <= Math.max(0, Number(configured))) {
228
+ return false;
229
+ }
230
+ const bytes = new Uint8Array(buffer);
231
+ const { encoding } = resolveFileViewerTextEncoding(bytes, (_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.encoding);
232
+ return encoding !== 'utf-16le' && encoding !== 'utf-16be';
184
233
  };
185
234
  const largeTextStyle = `
186
235
  .code-viewer--virtual{height:100%;min-height:240px;display:flex;flex-direction:column;overflow:hidden}
@@ -201,22 +250,24 @@ const largeTextStyle = `
201
250
  .code-line-segments span{min-width:64px;color:var(--code-muted);font-size:11px;line-height:1;text-align:center}
202
251
  `;
203
252
  export default async function renderLargeText(buffer, target, type = 'txt', context) {
204
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
253
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
205
254
  const t = createFileViewerTranslator(context === null || context === void 0 ? void 0 : context.options);
206
255
  const documentRef = target.ownerDocument;
207
- const bytes = new Uint8Array(buffer);
208
- const configuredSegmentBytes = (_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.maxRenderedLineBytes;
256
+ const sourceBytes = new Uint8Array(buffer);
257
+ const source = resolveFileViewerTextEncoding(sourceBytes, (_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);
258
+ const bytes = sourceBytes.subarray(source.bomLength);
259
+ const configuredSegmentBytes = (_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.maxRenderedLineBytes;
209
260
  const segmentBytes = Number.isFinite(configuredSegmentBytes)
210
261
  ? clamp(Math.trunc(Number(configuredSegmentBytes)), 1024, 256 * 1024)
211
262
  : DEFAULT_LARGE_TEXT_LINE_SEGMENT_BYTES;
212
- const configuredOverscan = (_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.virtualOverscanLines;
263
+ const configuredOverscan = (_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.virtualOverscanLines;
213
264
  const overscan = Number.isFinite(configuredOverscan)
214
265
  ? clamp(Math.trunc(Number(configuredOverscan)), 2, 100)
215
266
  : DEFAULT_LARGE_TEXT_OVERSCAN_LINES;
216
- const showToolbar = ((_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.toolbar) !== false;
267
+ const showToolbar = ((_h = (_g = context === null || context === void 0 ? void 0 : context.options) === null || _g === void 0 ? void 0 : _g.text) === null || _h === void 0 ? void 0 : _h.toolbar) !== false;
217
268
  // Undefined preserves the large-text renderer's pre-option behavior. An
218
269
  // explicit boolean has the same meaning in both regular and virtual views.
219
- const showLineNumbers = ((_h = (_g = context === null || context === void 0 ? void 0 : context.options) === null || _g === void 0 ? void 0 : _g.text) === null || _h === void 0 ? void 0 : _h.lineNumbers) !== false;
270
+ 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;
220
271
  let disposed = false;
221
272
  let zoom = 1;
222
273
  let scheduledFrame = 0;
@@ -235,6 +286,7 @@ export default async function renderLargeText(buffer, target, type = 'txt', cont
235
286
  root.dataset.viewerSearchProvider = 'code-virtual';
236
287
  root.dataset.textToolbar = String(showToolbar);
237
288
  root.dataset.lineNumbers = String(showLineNumbers);
289
+ root.dataset.textEncoding = source.encoding;
238
290
  const toolbar = documentRef.createElement('div');
239
291
  toolbar.className = 'code-toolbar';
240
292
  const extensionLabel = documentRef.createElement('span');
@@ -250,8 +302,8 @@ export default async function renderLargeText(buffer, target, type = 'txt', cont
250
302
  root.append(toolbar);
251
303
  }
252
304
  target.replaceChildren(style, root);
253
- (_j = context === null || context === void 0 ? void 0 : context.onProgressiveRender) === null || _j === void 0 ? void 0 : _j.call(context);
254
- const index = await buildLargeTextIndex(bytes, target, progress => {
305
+ (_l = context === null || context === void 0 ? void 0 : context.onProgressiveRender) === null || _l === void 0 ? void 0 : _l.call(context);
306
+ const index = await buildLargeTextIndex(bytes, source.encoding, target, progress => {
255
307
  if (!disposed) {
256
308
  status.textContent = t('text.code.indexingLargeFile', { progress });
257
309
  }
@@ -461,13 +513,31 @@ export default async function renderLargeText(buffer, target, type = 'txt', cont
461
513
  const maxMatches = Math.max(1, options.maxMatches || DEFAULT_FILE_VIEWER_SEARCH_MAX_MATCHES);
462
514
  const expression = createLargeTextSearchRegExp(query, options);
463
515
  const encoder = new TextEncoder();
464
- const encodedQueryBytes = encoder.encode(query).byteLength;
516
+ const encodedQueryBytes = index.encoding === 'gb18030'
517
+ ? query.length * 4
518
+ : encoder.encode(query).byteLength;
465
519
  const overlap = clamp(encodedQueryBytes * 2, 256, 64 * 1024);
520
+ const advanceBytesForCharacters = (start, end, characterCount) => {
521
+ if (index.encoding !== 'gb18030') {
522
+ const decoded = createFileViewerTextDecoder(index.encoding).decode(index.bytes.subarray(start, end));
523
+ return encoder.encode(decoded.slice(0, characterCount)).byteLength;
524
+ }
525
+ let cursor = start;
526
+ let characters = 0;
527
+ while (cursor < end && characters < characterCount) {
528
+ const size = gb18030UnitLength(index.bytes, cursor, end);
529
+ characters += size === 4
530
+ ? createFileViewerTextDecoder('gb18030').decode(index.bytes.subarray(cursor, cursor + size)).length
531
+ : 1;
532
+ cursor += size;
533
+ }
534
+ return cursor - start;
535
+ };
466
536
  for (let primaryStart = 0; primaryStart < index.bytes.byteLength && matches.length < maxMatches;) {
467
- const primaryEnd = Math.min(index.bytes.byteLength, primaryStart + LARGE_TEXT_SEARCH_CHUNK_BYTES);
468
- const decodeStart = alignUtf8Start(index.bytes, Math.max(0, primaryStart - overlap), index.bytes.byteLength);
469
- const decodeEnd = alignUtf8End(index.bytes, Math.min(index.bytes.byteLength, primaryEnd + overlap), index.bytes.byteLength);
470
- const text = new TextDecoder('utf-8').decode(index.bytes.subarray(decodeStart, decodeEnd));
537
+ const primaryEnd = alignTextBoundary(index, primaryStart, Math.min(index.bytes.byteLength, primaryStart + LARGE_TEXT_SEARCH_CHUNK_BYTES), index.bytes.byteLength);
538
+ const decodeStart = primaryStart;
539
+ const decodeEnd = alignTextBoundary(index, primaryStart, Math.min(index.bytes.byteLength, primaryEnd + overlap), index.bytes.byteLength);
540
+ const text = createFileViewerTextDecoder(index.encoding).decode(index.bytes.subarray(decodeStart, decodeEnd));
471
541
  let lastCharacterOffset = 0;
472
542
  let byteCursor = decodeStart;
473
543
  expression.lastIndex = 0;
@@ -477,9 +547,9 @@ export default async function renderLargeText(buffer, target, type = 'txt', cont
477
547
  expression.lastIndex += 1;
478
548
  continue;
479
549
  }
480
- byteCursor += encoder.encode(text.slice(lastCharacterOffset, match.index)).byteLength;
550
+ byteCursor += advanceBytesForCharacters(byteCursor, decodeEnd, match.index - lastCharacterOffset);
481
551
  const matchByteOffset = byteCursor;
482
- byteCursor += encoder.encode(match[0]).byteLength;
552
+ byteCursor += advanceBytesForCharacters(byteCursor, decodeEnd, match[0].length);
483
553
  lastCharacterOffset = match.index + match[0].length;
484
554
  if (matchByteOffset < primaryStart || matchByteOffset >= primaryEnd) {
485
555
  continue;
@@ -542,7 +612,7 @@ export default async function renderLargeText(buffer, target, type = 'txt', cont
542
612
  getState: getZoomState,
543
613
  subscribe: zoomEmitter.subscribe
544
614
  });
545
- (_k = context === null || context === void 0 ? void 0 : context.registerExportAdapter) === null || _k === void 0 ? void 0 : _k.call(context, { print: false, exportHtml: false });
615
+ (_m = context === null || context === void 0 ? void 0 : context.registerExportAdapter) === null || _m === void 0 ? void 0 : _m.call(context, { print: false, exportHtml: false });
546
616
  viewport.addEventListener('scroll', scheduleRender, { passive: true });
547
617
  viewport.addEventListener('click', event => {
548
618
  var _a, _b;
@@ -568,7 +638,7 @@ export default async function renderLargeText(buffer, target, type = 'txt', cont
568
638
  lineSegments.set(lineIndex, clamp(next, 0, segmentCount - 1));
569
639
  renderWindow(true);
570
640
  });
571
- const ResizeObserverCtor = (_l = getWindow(target)) === null || _l === void 0 ? void 0 : _l.ResizeObserver;
641
+ const ResizeObserverCtor = (_o = getWindow(target)) === null || _o === void 0 ? void 0 : _o.ResizeObserver;
572
642
  const resizeObserver = ResizeObserverCtor
573
643
  ? new ResizeObserverCtor(() => {
574
644
  updateSpacerHeight();
package/dist/markdown.js CHANGED
@@ -1,5 +1,35 @@
1
1
  import { marked } from 'marked';
2
- import { createFileViewerZoomChangeEmitter as createZoomChangeEmitter, readFileViewerText as readText, registerFileViewerZoomProvider, resolveFileViewerFitScale, unregisterFileViewerZoomProvider, } from '@file-viewer/core';
2
+ import createDOMPurify from 'dompurify';
3
+ import { createFileViewerZoomChangeEmitter as createZoomChangeEmitter, decodeFileViewerTextBuffer, registerFileViewerZoomProvider, resolveFileViewerFitScale, unregisterFileViewerZoomProvider, } from '@file-viewer/core';
4
+ const createSafeTextFragment = (documentRef, value) => {
5
+ const fragment = documentRef.createDocumentFragment();
6
+ fragment.append(documentRef.createTextNode(value));
7
+ return fragment;
8
+ };
9
+ const sanitizeMarkdownHtml = (documentRef, html) => {
10
+ const windowRef = documentRef.defaultView;
11
+ if (!windowRef) {
12
+ return createSafeTextFragment(documentRef, html);
13
+ }
14
+ const purifier = createDOMPurify(windowRef);
15
+ if (!purifier.isSupported) {
16
+ return createSafeTextFragment(documentRef, html);
17
+ }
18
+ return purifier.sanitize(html, {
19
+ RETURN_DOM_FRAGMENT: true,
20
+ USE_PROFILES: { html: true },
21
+ ADD_ATTR: ['target'],
22
+ FORBID_TAGS: ['style', 'iframe', 'object', 'embed', 'form'],
23
+ FORBID_ATTR: ['style', 'srcdoc'],
24
+ });
25
+ };
26
+ const hardenMarkdownLinks = (root) => {
27
+ root.querySelectorAll('a[target]').forEach(anchor => {
28
+ if ((anchor.getAttribute('target') || '').trim().toLowerCase() === '_blank') {
29
+ anchor.rel = 'noopener noreferrer';
30
+ }
31
+ });
32
+ };
3
33
  const markdownStyle = `
4
34
  .markdown-viewer{min-height:100%;padding:28px 16px 48px;background:var(--file-viewer-render-surface-background,#eef1f4);overflow:auto;box-sizing:border-box}
5
35
  .markdown-body{color-scheme:light;--bgColor-default:#fff;--bgColor-muted:#f6f8fa;--bgColor-neutral-muted:#818b981f;--borderColor-default:#d1d9e0;--borderColor-muted:#d1d9e0b3;--borderColor-neutral-muted:#d1d9e0b3;--fgColor-default:#1f2328;--fgColor-muted:#59636e;--fgColor-accent:#0969da;background:var(--bgColor-default);border:1px solid rgba(20,35,53,.1);border-radius:12px;margin:0 auto;box-sizing:border-box;min-width:200px;max-width:var(--markdown-max-width,980px);padding:var(--markdown-padding,45px);color:var(--fgColor-default);font-size:var(--markdown-font-size,16px);box-shadow:0 18px 42px rgba(15,23,42,.1)}
@@ -166,8 +196,8 @@ export const stripMarkdownFrontmatter = (text) => {
166
196
  return input.slice(match.index + match[0].length).replace(/^\r?\n/, '');
167
197
  };
168
198
  export default async function renderMarkdown(buffer, target, context) {
169
- var _a;
170
- const text = await readText(buffer);
199
+ var _a, _b, _c;
200
+ 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;
171
201
  let zoom = 1;
172
202
  const zoomEmitter = createZoomChangeEmitter();
173
203
  const root = document.createElement('div');
@@ -175,11 +205,13 @@ export default async function renderMarkdown(buffer, target, context) {
175
205
  root.dataset.viewerZoomProvider = 'markdown';
176
206
  const article = document.createElement('article');
177
207
  article.className = 'markdown-body';
178
- article.innerHTML = await marked(stripMarkdownFrontmatter(text));
208
+ const renderedHtml = await marked(stripMarkdownFrontmatter(text));
209
+ article.append(sanitizeMarkdownHtml(article.ownerDocument, renderedHtml));
210
+ hardenMarkdownLinks(article);
179
211
  applyMarkdownZoom(root, zoom);
180
212
  root.append(article);
181
213
  target.replaceChildren(createStyle(), root);
182
- await renderEmbeddedMermaid(article, (_a = context === null || context === void 0 ? void 0 : context.options) === null || _a === void 0 ? void 0 : _a.theme);
214
+ await renderEmbeddedMermaid(article, (_c = context === null || context === void 0 ? void 0 : context.options) === null || _c === void 0 ? void 0 : _c.theme);
183
215
  const baseContentHeight = Math.max(article.scrollHeight || 0, article.offsetHeight || 0, 0);
184
216
  const getZoomState = () => ({
185
217
  scale: zoom,
package/dist/patch.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  import { type FileRenderContext, type FileViewerRenderedInstance } from '@file-viewer/core';
2
- export default function renderPatch(buffer: ArrayBuffer, target: HTMLDivElement, type?: string, _context?: FileRenderContext): Promise<FileViewerRenderedInstance>;
2
+ export default function renderPatch(buffer: ArrayBuffer, target: HTMLDivElement, type?: string, context?: FileRenderContext): Promise<FileViewerRenderedInstance>;
package/dist/patch.js CHANGED
@@ -1,4 +1,4 @@
1
- import { createFileViewerZoomChangeEmitter as createZoomChangeEmitter, readFileViewerText as readText, registerFileViewerZoomProvider, unregisterFileViewerZoomProvider } from '@file-viewer/core';
1
+ import { createFileViewerZoomChangeEmitter as createZoomChangeEmitter, decodeFileViewerTextBuffer, registerFileViewerZoomProvider, unregisterFileViewerZoomProvider } from '@file-viewer/core';
2
2
  import { html as diffToHtml } from 'diff2html';
3
3
  const patchStyle = `
4
4
  .patch-viewer{min-height:100%;--patch-bg:#f6f8fa;--patch-surface:#fff;--patch-border:rgba(31,35,40,.12);--patch-text:#24292f;--patch-muted:#57606a;--patch-add:#dafbe1;--patch-del:#ffebe9;--patch-info:#ddf4ff;--patch-font-size:13px;background:var(--patch-bg);color:var(--patch-text);box-sizing:border-box}
@@ -64,9 +64,10 @@ const escapeHtml = (value) => {
64
64
  return entities[char];
65
65
  });
66
66
  };
67
- export default async function renderPatch(buffer, target, type = 'patch', _context) {
67
+ export default async function renderPatch(buffer, target, type = 'patch', context) {
68
+ var _a, _b;
68
69
  const documentRef = target.ownerDocument || document;
69
- const text = await readText(buffer);
70
+ 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;
70
71
  let zoom = 1;
71
72
  const zoomEmitter = createZoomChangeEmitter();
72
73
  const root = createElement(documentRef, 'div', 'patch-viewer');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@file-viewer/renderer-text",
3
- "version": "2.2.8",
3
+ "version": "2.3.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Standalone code, text, Markdown, patch diff, and Git bundle renderer plugin for File Viewer.",
@@ -57,12 +57,13 @@
57
57
  "LICENSE"
58
58
  ],
59
59
  "dependencies": {
60
- "@file-viewer/core": "2.2.8",
60
+ "@file-viewer/core": "2.3.0",
61
61
  "diff2html": "^3.4.56",
62
62
  "highlight.js": "^11.11.1",
63
63
  "marked": "^18.0.5",
64
- "mermaid": "^11.15.0",
65
- "pako": "^2.1.0"
64
+ "mermaid": "^11.16.1",
65
+ "pako": "^2.1.0",
66
+ "dompurify": "^3.4.13"
66
67
  },
67
68
  "devDependencies": {
68
69
  "typescript": "^6.0.3"