@file-viewer/doc 3.0.1 → 3.0.3

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.
@@ -232,11 +232,11 @@ function buildInlineNodes(segments, resolveAsset) {
232
232
  continue;
233
233
  }
234
234
  if (ch === DOC_CONTROL.hardLineBreak) {
235
- emitInline(fieldStack, output, { type: 'lineBreak' });
235
+ emitInline(fieldStack, output, { type: 'lineBreak', style: segment.state });
236
236
  continue;
237
237
  }
238
238
  if (ch === DOC_CONTROL.pageBreak) {
239
- emitInline(fieldStack, output, { type: 'pageBreak' });
239
+ emitInline(fieldStack, output, { type: 'pageBreak', style: segment.state });
240
240
  continue;
241
241
  }
242
242
  if (ch === DOC_CONTROL.picture) {
@@ -263,6 +263,21 @@ function buildInlineNodes(segments, resolveAsset) {
263
263
  }
264
264
  return output;
265
265
  }
266
+ function resolveCharacterFonts(state, resolveFont, defaults) {
267
+ for (const [id, name, fallback] of [
268
+ ['fontFamilyId', 'fontFamily', defaults?.ftcAsci],
269
+ ['fontFamilyEastAsiaId', 'fontFamilyEastAsia', defaults?.ftcFE],
270
+ ['fontFamilyOtherId', 'fontFamilyOther', defaults?.ftcOther],
271
+ ['fontFamilyBiId', 'fontFamilyBi', undefined],
272
+ ]) {
273
+ const index = state[id] ?? fallback;
274
+ const font = resolveFont(index);
275
+ if (font) {
276
+ state[id] = index;
277
+ state[name] = font.name || font.altName || undefined;
278
+ }
279
+ }
280
+ }
266
281
  function buildCharSegments(range, paragraphText, chpxRuns, styles, baseCharProps, resolveFont, cursorRef, forceReadableColors = false) {
267
282
  const overlaps = getOverlappingRuns(chpxRuns, range.cpStart, range.cpEnd, cursorRef);
268
283
  const boundaries = new Set([range.cpStart, range.cpEnd]);
@@ -285,9 +300,7 @@ function buildCharSegments(range, paragraphText, chpxRuns, styles, baseCharProps
285
300
  const state = charPropsToState(finalProps);
286
301
  if (forceReadableColors && state.colorIndex === 8)
287
302
  state.colorIndex = 1;
288
- const font = resolveFont(state.fontFamilyId);
289
- if (font)
290
- state.fontFamily = font.name || font.altName || undefined;
303
+ resolveCharacterFonts(state, resolveFont, styles.header);
291
304
  const localStart = cpStart - range.cpStart;
292
305
  const localEnd = cpEnd - range.cpStart;
293
306
  const text = paragraphText.slice(localStart, localEnd);
@@ -305,9 +318,7 @@ function buildCharSegments(range, paragraphText, chpxRuns, styles, baseCharProps
305
318
  const state = charPropsToState(baseCharProps);
306
319
  if (forceReadableColors && state.colorIndex === 8)
307
320
  state.colorIndex = 1;
308
- const font = resolveFont(state.fontFamilyId);
309
- if (font)
310
- state.fontFamily = font.name || font.altName || undefined;
321
+ resolveCharacterFonts(state, resolveFont, styles.header);
311
322
  segments.push({ cpStart: range.cpStart, cpEnd: range.cpEnd, text: paragraphText, state });
312
323
  }
313
324
  return segments;
@@ -326,11 +337,20 @@ function buildParagraphModel(range, paragraphText, styles, fonts, chpxRuns, reso
326
337
  const resolveFont = (fontId) => fonts.byIndex(fontId);
327
338
  const segments = buildCharSegments(range, paragraphText, chpxRuns, styles, baseCharProps, resolveFont, chpxCursor, range.storyKind === 'textbox' || range.storyKind === 'header-textbox');
328
339
  const inlines = buildInlineNodes(segments, resolveAsset);
340
+ // The terminator has its own CHPX run and is not part of paragraphText.
341
+ const markState = range.terminator === DOC_CONTROL.paragraph
342
+ ? buildCharSegments({ ...range, cpStart: range.cpEnd - 1 }, range.terminator, chpxRuns, styles, baseCharProps, resolveFont, { index: chpxCursor.index })[0]?.state
343
+ : undefined;
329
344
  return {
330
345
  id: uniqueId('para'),
331
346
  cpStart: range.cpStart,
332
347
  cpEnd: range.cpEnd,
333
348
  terminator: range.terminator || '',
349
+ paragraphMark: markState && {
350
+ revisionDeleted: markState.revisionDeleted,
351
+ revisionInserted: markState.revisionInserted,
352
+ },
353
+ storyKind: range.storyKind,
334
354
  text: paragraphText,
335
355
  rawProperties: range.properties || [],
336
356
  styleId: paraStyleId,
@@ -482,6 +502,8 @@ function paragraphToBlock(paragraph) {
482
502
  paraState: paragraph.paraState,
483
503
  inlines: paragraph.inlines,
484
504
  text: paragraph.text,
505
+ paragraphMark: paragraph.paragraphMark,
506
+ storyKind: paragraph.storyKind,
485
507
  };
486
508
  }
487
509
  function tableGridSignature(paragraph) {
@@ -50,6 +50,8 @@ export function charPropsToState(properties) {
50
50
  }
51
51
  break;
52
52
  case 'bold':
53
+ case 'revisionDeleted':
54
+ case 'revisionInserted':
53
55
  case 'italic':
54
56
  case 'strike':
55
57
  case 'hidden':
@@ -110,6 +112,7 @@ export function paraPropsToState(properties) {
110
112
  spacingBefore: 0,
111
113
  spacingAfter: 0,
112
114
  lineSpacing: 0,
115
+ lineSpacingRule: 'auto',
113
116
  leftIndent: 0,
114
117
  rightIndent: 0,
115
118
  firstLineIndent: 0,
@@ -150,9 +153,18 @@ export function paraPropsToState(properties) {
150
153
  case 'spacingAfter':
151
154
  state.spacingAfter = prop.value || 0;
152
155
  break;
153
- case 'lineSpacing':
156
+ case 'lineSpacing': {
154
157
  state.lineSpacing = prop.value || 0;
158
+ // LSPD's second short distinguishes 1/240-line multiples from signed twips.
159
+ const bytes = prop.operandBytes;
160
+ const absolute = bytes && bytes.length >= 4
161
+ ? bytes[2] === 0 && bytes[3] === 0
162
+ : state.lineSpacing < 0;
163
+ state.lineSpacingRule = absolute
164
+ ? state.lineSpacing < 0 ? 'exact' : 'atLeast'
165
+ : 'auto';
155
166
  break;
167
+ }
156
168
  case 'leftIndent':
157
169
  state.leftIndent = prop.value || 0;
158
170
  break;
@@ -486,6 +486,8 @@ export function decodeSprm(sprm, operandBytes) {
486
486
  const bytes = operandBytes;
487
487
  const raw = sprm;
488
488
  switch (sprm) {
489
+ case SprmCodes.sprmCFRMarkDel: return setMeta('char', 'revisionDeleted', boolValue(bytes), raw, bytes);
490
+ case SprmCodes.sprmCFRMark: return setMeta('char', 'revisionInserted', boolValue(bytes), raw, bytes);
489
491
  case SprmCodes.sprmCPicLocation: return setMeta('char', 'pictureOffset', u32(bytes, 0) >>> 0, raw, bytes);
490
492
  case SprmCodes.sprmCFData: return setMeta('char', 'data', boolValue(bytes), raw, bytes);
491
493
  case SprmCodes.sprmCFOle2: return setMeta('char', 'ole2', boolValue(bytes), raw, bytes);
@@ -512,10 +514,10 @@ export function decodeSprm(sprm, operandBytes) {
512
514
  case SprmCodes.sprmCHpsBi: return setMeta('char', 'fontSizeHalfPoints', u16(bytes, 0), raw, bytes);
513
515
  case SprmCodes.sprmCHpsPos: return setMeta('char', 'positionHalfPoints', i16(bytes, 0), raw, bytes);
514
516
  case SprmCodes.sprmCSymbol: return setMeta('char', 'symbol', { font: u16(bytes, 0), charCode: u16(bytes, 2) }, raw, bytes);
515
- case SprmCodes.sprmCRgFtc0:
516
- case SprmCodes.sprmCRgFtc1:
517
- case SprmCodes.sprmCRgFtc2:
518
- case SprmCodes.sprmCFtcBi: return setMeta('char', 'fontFamilyId', u16(bytes, 0), raw, bytes);
517
+ case SprmCodes.sprmCRgFtc0: return setMeta('char', 'fontFamilyId', u16(bytes, 0), raw, bytes);
518
+ case SprmCodes.sprmCRgFtc1: return setMeta('char', 'fontFamilyEastAsiaId', u16(bytes, 0), raw, bytes);
519
+ case SprmCodes.sprmCRgFtc2: return setMeta('char', 'fontFamilyOtherId', u16(bytes, 0), raw, bytes);
520
+ case SprmCodes.sprmCFtcBi: return setMeta('char', 'fontFamilyBiId', u16(bytes, 0), raw, bytes);
519
521
  case SprmCodes.sprmCCharScale: return setMeta('char', 'scale', u16(bytes, 0), raw, bytes);
520
522
  case SprmCodes.sprmCFDStrike: return setMeta('char', 'doubleStrike', boolValue(bytes), raw, bytes);
521
523
  case SprmCodes.sprmCFImprint: return setMeta('char', 'imprint', boolValue(bytes), raw, bytes);
@@ -179,9 +179,11 @@ export function parseStyles(tableBytes, fibRgFcLcb) {
179
179
  const stshiOffset = 2;
180
180
  const cstd = reader.u16(stshiOffset + 0);
181
181
  const cbSTDBaseInFile = reader.u16(stshiOffset + 2);
182
- const ftcAsci = reader.u16(stshiOffset + 10);
183
- const ftcFE = reader.u16(stshiOffset + 12);
184
- const ftcOther = reader.u16(stshiOffset + 14);
182
+ const fontAt = (offset) => cbStshi >= offset + 2 && bytes.length >= stshiOffset + offset + 2
183
+ ? reader.i16(stshiOffset + offset) : -1;
184
+ const ftcAsci = fontAt(12);
185
+ const ftcFE = fontAt(14);
186
+ const ftcOther = fontAt(16);
185
187
  const header = { cbStshi, cstd, cbSTDBaseInFile, ftcAsci, ftcFE, ftcOther };
186
188
  let offset = 2 + cbStshi;
187
189
  const styles = new Map();
@@ -0,0 +1,5 @@
1
+ import type { CharState } from '../types.js';
2
+ export declare function textFontRuns(text: string, style: CharState): Array<{
3
+ text: string;
4
+ fontFamily?: string;
5
+ }>;
@@ -0,0 +1,25 @@
1
+ const EAST_ASIAN = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}\p{Script=Bopomofo}\u3000-\u303f\uff00-\uffef]/u;
2
+ const INHERIT_FONT = /[\p{Mark}\u200c\u200d]/u;
3
+ export function textFontRuns(text, style) {
4
+ if (!text)
5
+ return [];
6
+ if (style.rtl && style.fontFamilyBi)
7
+ return [{ text, fontFamily: style.fontFamilyBi }];
8
+ const base = style.fontFamily;
9
+ const eastAsia = style.fontFamilyEastAsia || base;
10
+ const other = style.fontFamilyOther || base;
11
+ if (eastAsia === base && other === base)
12
+ return [{ text, fontFamily: base }];
13
+ const runs = [];
14
+ for (const character of text) {
15
+ const previous = runs[runs.length - 1];
16
+ const fontFamily = previous && INHERIT_FONT.test(character) ? previous.fontFamily
17
+ : EAST_ASIAN.test(character) ? eastAsia
18
+ : character.codePointAt(0) < 0x80 ? base : other;
19
+ if (previous && previous.fontFamily === fontFamily)
20
+ previous.text += character;
21
+ else
22
+ runs.push({ text: character, fontFamily });
23
+ }
24
+ return runs;
25
+ }
@@ -1,6 +1,7 @@
1
1
  import { escapeHtml, slugify, twipsToPx } from '../core/utils.js';
2
2
  import { HIGHLIGHT_COLORS } from '../msdoc/constants.js';
3
3
  import { cssTextAlign, cssUnderline, cssVerticalAlign } from '../msdoc/properties.js';
4
+ import { textFontRuns } from './fonts.js';
4
5
  const COLOR_INDEX_MAP = {
5
6
  1: '#000000',
6
7
  2: '#0000ff',
@@ -154,9 +155,14 @@ function paragraphStyleToCss(paraState) {
154
155
  if (textIndent)
155
156
  style['text-indent'] = `${textIndent}px`;
156
157
  if (paraState.lineSpacing) {
157
- const lineHeight = Math.abs(paraState.lineSpacing) / 240;
158
- if (lineHeight)
159
- style['line-height'] = String(Math.max(1, lineHeight));
158
+ if (paraState.lineSpacingRule === 'exact' || paraState.lineSpacingRule === 'atLeast') {
159
+ style['line-height'] = `${twipsToPx(Math.abs(paraState.lineSpacing))}px`;
160
+ }
161
+ else {
162
+ const multiple = Math.abs(paraState.lineSpacing) / 240;
163
+ // A font's single line includes its metrics and leading, not just a 1em box.
164
+ style['line-height'] = multiple === 1 ? 'normal' : String(multiple);
165
+ }
160
166
  }
161
167
  if (paraState.keepLines)
162
168
  style['break-inside'] = 'avoid';
@@ -239,11 +245,30 @@ function inlineStyleToCss(styleState) {
239
245
  return style;
240
246
  }
241
247
  function renderTextNode(node, context) {
242
- const content = escapeHtml(node.text);
248
+ if (context.reviewMode === 'final' && node.style.revisionDeleted)
249
+ return '';
250
+ if (context.reviewMode === 'original' && node.style.revisionInserted)
251
+ return '';
252
+ const content = textFontRuns(node.text, node.style).map(run => {
253
+ const text = escapeHtml(run.text);
254
+ if (!run.fontFamily || run.fontFamily === node.style.fontFamily)
255
+ return text;
256
+ const font = styleObjectToCss({ 'font-family': `${quoteCssString(run.fontFamily)},sans-serif` });
257
+ return `<span style="${font}">${text}</span>`;
258
+ }).join('');
243
259
  const inlineStyle = inlineStyleToCss(node.style);
244
260
  inlineStyle['white-space'] = 'break-spaces';
261
+ const revision = context.reviewMode === 'all'
262
+ ? node.style.revisionDeleted ? 'delete' : node.style.revisionInserted ? 'insert' : null
263
+ : null;
264
+ if (revision) {
265
+ inlineStyle.color = '#c62828';
266
+ inlineStyle['text-decoration-line'] = revision === 'delete' ? 'line-through' : 'underline';
267
+ }
245
268
  const style = styleObjectToCss(inlineStyle);
246
- const inner = `<span${style ? ` style="${style}"` : ''}>${content}</span>`;
269
+ const tag = revision === 'delete' ? 'del' : revision === 'insert' ? 'ins' : 'span';
270
+ const change = revision ? ` data-msdoc-change="${revision}"` : '';
271
+ const inner = `<${tag}${change}${style ? ` style="${style}"` : ''}>${content}</${tag}>`;
247
272
  return renderLink(inner, sanitizeMsDocLinkHref(node.href, context.externalLinkPolicy));
248
273
  }
249
274
  function inlineImageDisplaySizePx(asset) {
@@ -382,6 +407,8 @@ function renderAttachmentNode(node, context) {
382
407
  }
383
408
  function renderInlineNodes(nodes, context) {
384
409
  return nodes.map((node) => {
410
+ if (hiddenRevision(node.style, context))
411
+ return '';
385
412
  if (node.type === 'text')
386
413
  return renderTextNode(node, context);
387
414
  if (node.type === 'image')
@@ -395,12 +422,37 @@ function renderInlineNodes(nodes, context) {
395
422
  return '';
396
423
  }).join('');
397
424
  }
425
+ function hiddenRevision(revision, context) {
426
+ return !!((context.reviewMode === 'final' && revision?.revisionDeleted)
427
+ || (context.reviewMode === 'original' && revision?.revisionInserted));
428
+ }
429
+ function reviewParagraphs(paragraphs, context) {
430
+ const result = [];
431
+ for (const paragraph of paragraphs) {
432
+ const previous = result[result.length - 1];
433
+ if (previous && previous.storyKind === paragraph.storyKind
434
+ && hiddenRevision(previous.paragraphMark, context)) {
435
+ // The surviving paragraph mark owns the merged paragraph's formatting.
436
+ result[result.length - 1] = {
437
+ ...paragraph,
438
+ inlines: [...previous.inlines, ...paragraph.inlines],
439
+ text: previous.text + paragraph.text,
440
+ };
441
+ }
442
+ else {
443
+ result.push(paragraph);
444
+ }
445
+ }
446
+ return result;
447
+ }
398
448
  function renderParagraphBlock(block, context, options = {}) {
399
449
  const tag = options.inline ? 'div' : 'p';
400
450
  const style = styleObjectToCss(paragraphStyleToCss(block.paraState));
401
451
  const body = renderInlineNodes(block.inlines || [], context);
402
452
  const empty = body || '<br>';
403
453
  const classNames = ['msdoc-paragraph'];
454
+ if (block.paraState.lineSpacingRule === 'atLeast')
455
+ classNames.push('msdoc-line-at-least');
404
456
  if (block.styleName)
405
457
  classNames.push(`msdoc-style-${slugify(block.styleName)}`);
406
458
  return `<${tag} class="${classNames.join(' ')}"${style ? ` style="${style}"` : ''}>${empty}</${tag}>`;
@@ -492,7 +544,8 @@ function renderTableBlock(block, context) {
492
544
  if ((cell.rowspan ?? 1) > 1)
493
545
  attrs.push(` rowspan="${cell.rowspan}"`);
494
546
  const style = styleObjectToCss(cellStyle(cell));
495
- const body = cell.paragraphs.map((paragraph) => renderParagraphBlock(paragraph, context, { inline: true })).join('');
547
+ const body = reviewParagraphs(cell.paragraphs, context)
548
+ .map((paragraph) => renderParagraphBlock(paragraph, context, { inline: true })).join('');
496
549
  return `<td class="msdoc-cell"${attrs.join('')}${style ? ` style="${style}"` : ''}>${body || '<div class="msdoc-paragraph"><br></div>'}</td>`;
497
550
  })
498
551
  .join('');
@@ -516,6 +569,7 @@ export function defaultMsDocCss() {
516
569
  .msdoc-root{box-sizing:border-box;max-width:100%;padding:24px;background:#fff;color:#111;font:14px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
517
570
  .msdoc-root *{box-sizing:border-box}
518
571
  .msdoc-paragraph{margin:0 0 8px;white-space:normal;word-break:break-word;overflow-wrap:anywhere}
572
+ .msdoc-line-at-least span,.msdoc-line-at-least ins,.msdoc-line-at-least del{line-height:normal}
519
573
  .msdoc-paragraph:last-child{margin-bottom:0}
520
574
  .msdoc-table{margin:12px 0;border-collapse:collapse;border-spacing:0;max-width:100%}
521
575
  .msdoc-cell{padding:6px 8px;vertical-align:top;word-break:break-word;overflow-wrap:anywhere}
@@ -537,18 +591,29 @@ export function defaultMsDocCss() {
537
591
  export function renderMsDoc(parsed, options = {}) {
538
592
  const css = options.css ?? defaultMsDocCss();
539
593
  const context = {
594
+ reviewMode: options.reviewMode ?? 'all',
540
595
  externalLinkPolicy: options.externalLinkPolicy ?? 'block',
541
596
  externalResourcePolicy: options.externalResourcePolicy ?? 'block',
542
597
  };
543
- const html = parsed.blocks.map((block) => {
544
- if (block.type === 'paragraph')
545
- return renderParagraphBlock(block, context);
598
+ const parts = [];
599
+ let paragraphs = [];
600
+ const flushParagraphs = () => {
601
+ parts.push(...reviewParagraphs(paragraphs, context).map(block => renderParagraphBlock(block, context)));
602
+ paragraphs = [];
603
+ };
604
+ for (const block of parsed.blocks) {
605
+ if (block.type === 'paragraph') {
606
+ paragraphs.push(block);
607
+ continue;
608
+ }
609
+ flushParagraphs();
546
610
  if (block.type === 'table')
547
- return renderTableBlock(block, context);
611
+ parts.push(renderTableBlock(block, context));
548
612
  if (block.type === 'attachments')
549
- return renderAttachmentsBlock(block);
550
- return '';
551
- }).join('');
613
+ parts.push(renderAttachmentsBlock(block));
614
+ }
615
+ flushParagraphs();
616
+ const html = parts.join('');
552
617
  return {
553
618
  html,
554
619
  css,
package/dist/types.d.ts CHANGED
@@ -246,6 +246,8 @@ export interface SymbolInfo {
246
246
  [key: string]: unknown;
247
247
  }
248
248
  export interface CharState {
249
+ revisionDeleted?: boolean;
250
+ revisionInserted?: boolean;
249
251
  bold: boolean;
250
252
  italic: boolean;
251
253
  strike: boolean;
@@ -253,6 +255,12 @@ export interface CharState {
253
255
  fontSizeHalfPoints?: number;
254
256
  fontFamilyId?: number;
255
257
  fontFamily?: string;
258
+ fontFamilyEastAsiaId?: number;
259
+ fontFamilyEastAsia?: string;
260
+ fontFamilyOtherId?: number;
261
+ fontFamilyOther?: string;
262
+ fontFamilyBiId?: number;
263
+ fontFamilyBi?: string;
256
264
  colorIndex?: number;
257
265
  highlight?: HighlightInfo | number;
258
266
  spacing: number;
@@ -293,6 +301,7 @@ export interface ParaState {
293
301
  spacingBefore: number;
294
302
  spacingAfter: number;
295
303
  lineSpacing: number;
304
+ lineSpacingRule?: 'auto' | 'atLeast' | 'exact';
296
305
  leftIndent: number;
297
306
  rightIndent: number;
298
307
  firstLineIndent: number;
@@ -552,9 +561,11 @@ export interface AttachmentInlineNode {
552
561
  }
553
562
  export interface LineBreakInlineNode {
554
563
  type: 'lineBreak';
564
+ style?: CharState;
555
565
  }
556
566
  export interface PageBreakInlineNode {
557
567
  type: 'pageBreak';
568
+ style?: CharState;
558
569
  }
559
570
  export type InlineNode = TextInlineNode | ImageInlineNode | AttachmentInlineNode | LineBreakInlineNode | PageBreakInlineNode;
560
571
  export interface ParagraphBlock {
@@ -565,6 +576,8 @@ export interface ParagraphBlock {
565
576
  paraState: ParaState;
566
577
  inlines: InlineNode[];
567
578
  text: string;
579
+ paragraphMark?: Pick<CharState, 'revisionDeleted' | 'revisionInserted'>;
580
+ storyKind?: string;
568
581
  }
569
582
  export interface TableCellBlock {
570
583
  id: string;
@@ -600,6 +613,8 @@ export interface ParagraphModel {
600
613
  cpStart: number;
601
614
  cpEnd: number;
602
615
  terminator: string;
616
+ paragraphMark?: Pick<CharState, 'revisionDeleted' | 'revisionInserted'>;
617
+ storyKind?: string;
603
618
  text: string;
604
619
  rawProperties: DecodedProperty[];
605
620
  styleId: number;
@@ -617,6 +632,8 @@ export interface MsDocParseOptions {
617
632
  }
618
633
  export interface MsDocRenderOptions {
619
634
  css?: string;
635
+ /** Preserve insertion/deletion marks by default; explicitly select final or original text. */
636
+ reviewMode?: 'all' | 'final' | 'original';
620
637
  /** External links are blocked by default; internal document bookmarks remain active. */
621
638
  externalLinkPolicy?: 'allow' | 'block';
622
639
  /** Linked image resources are blocked by default; embedded data/blob images remain available. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@file-viewer/doc",
3
- "version": "3.0.1",
3
+ "version": "3.0.3",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Browser-native legacy MS-DOC (.doc) parser and HTML renderer extracted from the public msdoc-viewer 0.2.0 line.",
@@ -27,12 +27,12 @@
27
27
  },
28
28
  "repository": {
29
29
  "type": "git",
30
- "url": "git+https://github.com/flyfish-dev/docjs.git",
30
+ "url": "git+https://github.com/flyfish-dev/file-viewer.git",
31
31
  "directory": "packages/renderers/doc"
32
32
  },
33
33
  "homepage": "https://github.com/flyfish-dev/docjs",
34
34
  "bugs": {
35
- "url": "https://github.com/flyfish-dev/docjs/issues"
35
+ "url": "https://github.com/flyfish-dev/file-viewer/issues"
36
36
  },
37
37
  "funding": {
38
38
  "type": "individual",
@@ -62,7 +62,7 @@
62
62
  "LICENSE"
63
63
  ],
64
64
  "dependencies": {
65
- "dompurify": "^3.4.14"
65
+ "dompurify": "3.4.15"
66
66
  },
67
67
  "devDependencies": {
68
68
  "typescript": "^6.0.3"