@cvfile/viewer-web 0.1.0 → 0.3.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.
@@ -0,0 +1,14 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __decorateClass = (decorators, target, key, kind) => {
4
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
5
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
6
+ if (decorator = decorators[i])
7
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
8
+ if (kind && result) __defProp(target, key, result);
9
+ return result;
10
+ };
11
+
12
+ export { __decorateClass };
13
+ //# sourceMappingURL=chunk-EUXUH3YW.js.map
14
+ //# sourceMappingURL=chunk-EUXUH3YW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-EUXUH3YW.js"}
package/dist/index.d.ts CHANGED
@@ -11,22 +11,42 @@ declare class CvEmbed extends LitElement {
11
11
  language: string;
12
12
  tabBar: boolean;
13
13
  theme: Theme;
14
+ /** Optional override for the pdf.js worker URL (defaults to a bundler-portable resolution). */
15
+ workerSrc: string;
14
16
  private file;
15
17
  private error;
16
18
  private activeTab;
17
19
  private loading;
18
20
  private pdfPage;
19
21
  private pdfPageCount;
22
+ /**
23
+ * The extracted crawler-friendly clean text (the selected markdown payload,
24
+ * or HTML as a fallback). Exposed read-only so hosts and tests can read it,
25
+ * and mirrored into the light DOM in {@link projectCleanText} so search
26
+ * engines can index it (shadow-DOM content is not indexed).
27
+ */
28
+ get cleanText(): string;
29
+ private _cleanText;
30
+ private cleanTextNode;
20
31
  private pdfModulePromise;
21
32
  connectedCallback(): Promise<void>;
22
33
  willUpdate(changed: ChangedKeys): void;
23
34
  updated(changed: ChangedKeys): void;
35
+ disconnectedCallback(): void;
24
36
  loadFromBytes(bytes: Uint8Array): Promise<void>;
25
37
  /** Public API: re-attempt the last load (used by error retry button). */
26
38
  retry(): Promise<void>;
27
39
  private loadFromSrc;
28
40
  private processBytes;
29
41
  private pickInitialTab;
42
+ /**
43
+ * Mirror the extracted clean text into the LIGHT DOM. Shadow-DOM content is
44
+ * invisible to crawlers, which defeats the purpose of shipping clean text, so
45
+ * we maintain a single light-DOM child holding the selected markdown (or HTML)
46
+ * payload. It is visually hidden but remains in the accessibility tree and is
47
+ * indexable. Hosts can also read it via the `cleanText` property.
48
+ */
49
+ private projectCleanText;
30
50
  private renderPdf;
31
51
  private switchTab;
32
52
  /** Keyboard navigation per WAI-ARIA tablist authoring practices. */
package/dist/index.js CHANGED
@@ -1,79 +1,42 @@
1
- import * as pdfjsLib from 'pdfjs-dist';
2
- import workerUrl from 'pdfjs-dist/build/pdf.worker.min.mjs?url';
1
+ import { __decorateClass } from './chunk-EUXUH3YW.js';
3
2
  import { LitElement, css, html } from 'lit';
4
3
  import { property, state } from 'lit/decorators.js';
5
4
  import { extract } from '@cvfile/sdk';
6
5
  import DOMPurify from 'dompurify';
7
6
  import { marked } from 'marked';
8
7
 
9
- var __defProp = Object.defineProperty;
10
- var __getOwnPropNames = Object.getOwnPropertyNames;
11
- var __esm = (fn, res) => function __init() {
12
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
13
- };
14
- var __export = (target, all) => {
15
- for (var name in all)
16
- __defProp(target, name, { get: all[name], enumerable: true });
17
- };
18
- var __decorateClass = (decorators, target, key, kind) => {
19
- var result = void 0 ;
20
- for (var i = decorators.length - 1, decorator; i >= 0; i--)
21
- if (decorator = decorators[i])
22
- result = (decorator(target, key, result) ) || result;
23
- if (result) __defProp(target, key, result);
24
- return result;
25
- };
26
-
27
- // src/render-pdf.ts
28
- var render_pdf_exports = {};
29
- __export(render_pdf_exports, {
30
- renderPdfPage: () => renderPdfPage
31
- });
32
- function ensureWorker() {
33
- if (workerConfigured) return;
34
- pdfjsLib.GlobalWorkerOptions.workerSrc = workerUrl;
35
- workerConfigured = true;
36
- }
37
- async function renderPdfPage(bytes, pageNumber, canvas) {
38
- ensureWorker();
39
- const view = new Uint8Array(bytes.byteLength);
40
- view.set(bytes);
41
- const loadingTask = pdfjsLib.getDocument({ data: view });
42
- const pdf = await loadingTask.promise;
43
- const page = await pdf.getPage(pageNumber);
44
- const dpr = window.devicePixelRatio || 1;
45
- const baseViewport = page.getViewport({ scale: 1 });
46
- const containerWidth = canvas.parentElement?.clientWidth ?? baseViewport.width;
47
- const targetWidth = Math.min(containerWidth - 32, 900);
48
- const scale = targetWidth / baseViewport.width * dpr;
49
- const viewport = page.getViewport({ scale });
50
- canvas.width = Math.floor(viewport.width);
51
- canvas.height = Math.floor(viewport.height);
52
- canvas.style.width = `${Math.floor(viewport.width / dpr)}px`;
53
- canvas.style.height = `${Math.floor(viewport.height / dpr)}px`;
54
- const ctx = canvas.getContext("2d");
55
- if (!ctx) {
56
- throw new Error("Could not acquire 2D canvas context");
57
- }
58
- await page.render({ canvasContext: ctx, viewport }).promise;
59
- return { numPages: pdf.numPages };
8
+ var hooked = false;
9
+ function ensureLinkHardening() {
10
+ if (hooked) return;
11
+ DOMPurify.addHook("afterSanitizeAttributes", (node) => {
12
+ if (node instanceof HTMLAnchorElement) {
13
+ node.setAttribute("rel", "noopener noreferrer");
14
+ }
15
+ });
16
+ hooked = true;
60
17
  }
61
- var workerConfigured;
62
- var init_render_pdf = __esm({
63
- "src/render-pdf.ts"() {
64
- workerConfigured = false;
65
- }
66
- });
67
18
  function renderMarkdown(source) {
19
+ ensureLinkHardening();
68
20
  const raw = marked.parse(source, { async: false });
69
21
  return DOMPurify.sanitize(raw, {
70
- USE_PROFILES: { html: true },
71
- FORBID_TAGS: ["script", "style", "iframe", "object", "embed"],
72
- FORBID_ATTR: ["onerror", "onload", "onclick", "onmouseover"]
22
+ USE_PROFILES: { html: true }
73
23
  });
74
24
  }
75
25
 
26
+ // src/payload-selection.ts
27
+ function pickPayloadByLanguage(file, mimeType, language) {
28
+ const matches = file.payloads.filter((p) => p.mimeType === mimeType);
29
+ if (matches.length === 0) return void 0;
30
+ const preferred = language || file.metadata.primaryLanguage;
31
+ return matches.find((p) => p.language === preferred) ?? matches[0];
32
+ }
33
+ function selectCleanText(file, language) {
34
+ return pickPayloadByLanguage(file, "text/markdown", language)?.text() ?? pickPayloadByLanguage(file, "text/html", language)?.text() ?? "";
35
+ }
36
+
76
37
  // src/cv-embed.ts
38
+ var FETCH_TIMEOUT_MS = 15e3;
39
+ var MAX_FILE_BYTES = 50 * 1024 * 1024;
77
40
  var CvEmbed = class extends LitElement {
78
41
  constructor() {
79
42
  super(...arguments);
@@ -82,12 +45,15 @@ var CvEmbed = class extends LitElement {
82
45
  this.language = "";
83
46
  this.tabBar = true;
84
47
  this.theme = "auto";
48
+ this.workerSrc = "";
85
49
  this.file = null;
86
50
  this.error = null;
87
51
  this.activeTab = "pdf";
88
52
  this.loading = true;
89
53
  this.pdfPage = 1;
90
54
  this.pdfPageCount = 1;
55
+ this._cleanText = "";
56
+ this.cleanTextNode = null;
91
57
  this.pdfModulePromise = null;
92
58
  }
93
59
  static {
@@ -319,6 +285,15 @@ var CvEmbed = class extends LitElement {
319
285
  }
320
286
  `;
321
287
  }
288
+ /**
289
+ * The extracted crawler-friendly clean text (the selected markdown payload,
290
+ * or HTML as a fallback). Exposed read-only so hosts and tests can read it,
291
+ * and mirrored into the light DOM in {@link projectCleanText} so search
292
+ * engines can index it (shadow-DOM content is not indexed).
293
+ */
294
+ get cleanText() {
295
+ return this._cleanText;
296
+ }
322
297
  async connectedCallback() {
323
298
  super.connectedCallback();
324
299
  if (this.src) {
@@ -339,6 +314,14 @@ var CvEmbed = class extends LitElement {
339
314
  void this.renderPdf();
340
315
  }
341
316
  }
317
+ if (changed.has("file") || changed.has("language")) {
318
+ this.projectCleanText();
319
+ }
320
+ }
321
+ disconnectedCallback() {
322
+ super.disconnectedCallback();
323
+ this.cleanTextNode?.remove();
324
+ this.cleanTextNode = null;
342
325
  }
343
326
  loadFromBytes(bytes) {
344
327
  return this.processBytes(bytes);
@@ -355,11 +338,22 @@ var CvEmbed = class extends LitElement {
355
338
  this.error = null;
356
339
  this.file = null;
357
340
  try {
358
- const res = await fetch(this.src);
341
+ const res = await fetch(this.src, {
342
+ credentials: "omit",
343
+ redirect: "follow",
344
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
345
+ });
359
346
  if (!res.ok) {
360
347
  throw new Error(`Fetch failed: ${res.status} ${res.statusText}`);
361
348
  }
349
+ const declared = Number(res.headers.get("content-length"));
350
+ if (Number.isFinite(declared) && declared > MAX_FILE_BYTES) {
351
+ throw new Error(`File too large: ${declared} bytes exceeds limit of ${MAX_FILE_BYTES}`);
352
+ }
362
353
  const buf = new Uint8Array(await res.arrayBuffer());
354
+ if (buf.byteLength > MAX_FILE_BYTES) {
355
+ throw new Error(`File too large: ${buf.byteLength} bytes exceeds limit of ${MAX_FILE_BYTES}`);
356
+ }
363
357
  await this.processBytes(buf);
364
358
  } catch (err) {
365
359
  this.error = err.message;
@@ -382,15 +376,52 @@ var CvEmbed = class extends LitElement {
382
376
  if (this.view !== "auto") return this.view;
383
377
  return "pdf";
384
378
  }
379
+ /**
380
+ * Mirror the extracted clean text into the LIGHT DOM. Shadow-DOM content is
381
+ * invisible to crawlers, which defeats the purpose of shipping clean text, so
382
+ * we maintain a single light-DOM child holding the selected markdown (or HTML)
383
+ * payload. It is visually hidden but remains in the accessibility tree and is
384
+ * indexable. Hosts can also read it via the `cleanText` property.
385
+ */
386
+ projectCleanText() {
387
+ const text = this.file ? selectCleanText(this.file, this.language) : "";
388
+ this._cleanText = text;
389
+ if (!text) {
390
+ this.cleanTextNode?.remove();
391
+ this.cleanTextNode = null;
392
+ return;
393
+ }
394
+ if (!this.cleanTextNode) {
395
+ const node = document.createElement("div");
396
+ node.setAttribute("data-cv-clean-text", "");
397
+ node.setAttribute("aria-hidden", "false");
398
+ Object.assign(node.style, {
399
+ position: "absolute",
400
+ width: "1px",
401
+ height: "1px",
402
+ margin: "-1px",
403
+ padding: "0",
404
+ overflow: "hidden",
405
+ clip: "rect(0 0 0 0)",
406
+ clipPath: "inset(50%)",
407
+ whiteSpace: "pre-wrap",
408
+ border: "0"
409
+ });
410
+ this.cleanTextNode = node;
411
+ this.appendChild(node);
412
+ }
413
+ this.cleanTextNode.textContent = text;
414
+ }
385
415
  async renderPdf() {
386
416
  if (!this.file) return;
387
417
  const canvas = this.renderRoot.querySelector(".pdf-canvas");
388
418
  if (!canvas) return;
389
419
  if (!this.pdfModulePromise) {
390
- this.pdfModulePromise = Promise.resolve().then(() => (init_render_pdf(), render_pdf_exports));
420
+ this.pdfModulePromise = import('./render-pdf.js');
391
421
  }
392
422
  try {
393
423
  const mod = await this.pdfModulePromise;
424
+ if (this.workerSrc) mod.setWorkerSrc(this.workerSrc);
394
425
  const { numPages } = await mod.renderPdfPage(this.file.bytes, this.pdfPage, canvas);
395
426
  if (numPages !== this.pdfPageCount) {
396
427
  this.pdfPageCount = numPages;
@@ -454,8 +485,8 @@ var CvEmbed = class extends LitElement {
454
485
  </div>`;
455
486
  }
456
487
  const file = this.file;
457
- const md = file.payloads.find((p) => p.mimeType === "text/markdown");
458
- const htmlPayload = file.payloads.find((p) => p.mimeType === "text/html");
488
+ const md = pickPayloadByLanguage(file, "text/markdown", this.language);
489
+ const htmlPayload = pickPayloadByLanguage(file, "text/html", this.language);
459
490
  const available = {
460
491
  pdf: true,
461
492
  md: !!md,
@@ -539,37 +570,40 @@ var CvEmbed = class extends LitElement {
539
570
  };
540
571
  __decorateClass([
541
572
  property({ type: String })
542
- ], CvEmbed.prototype, "src");
573
+ ], CvEmbed.prototype, "src", 2);
543
574
  __decorateClass([
544
575
  property({ type: String })
545
- ], CvEmbed.prototype, "view");
576
+ ], CvEmbed.prototype, "view", 2);
546
577
  __decorateClass([
547
578
  property({ type: String, attribute: "language" })
548
- ], CvEmbed.prototype, "language");
579
+ ], CvEmbed.prototype, "language", 2);
549
580
  __decorateClass([
550
581
  property({ type: Boolean, attribute: "tab-bar" })
551
- ], CvEmbed.prototype, "tabBar");
582
+ ], CvEmbed.prototype, "tabBar", 2);
552
583
  __decorateClass([
553
584
  property({ type: String, reflect: true })
554
- ], CvEmbed.prototype, "theme");
585
+ ], CvEmbed.prototype, "theme", 2);
586
+ __decorateClass([
587
+ property({ type: String, attribute: "worker-src" })
588
+ ], CvEmbed.prototype, "workerSrc", 2);
555
589
  __decorateClass([
556
590
  state()
557
- ], CvEmbed.prototype, "file");
591
+ ], CvEmbed.prototype, "file", 2);
558
592
  __decorateClass([
559
593
  state()
560
- ], CvEmbed.prototype, "error");
594
+ ], CvEmbed.prototype, "error", 2);
561
595
  __decorateClass([
562
596
  state()
563
- ], CvEmbed.prototype, "activeTab");
597
+ ], CvEmbed.prototype, "activeTab", 2);
564
598
  __decorateClass([
565
599
  state()
566
- ], CvEmbed.prototype, "loading");
600
+ ], CvEmbed.prototype, "loading", 2);
567
601
  __decorateClass([
568
602
  state()
569
- ], CvEmbed.prototype, "pdfPage");
603
+ ], CvEmbed.prototype, "pdfPage", 2);
570
604
  __decorateClass([
571
605
  state()
572
- ], CvEmbed.prototype, "pdfPageCount");
606
+ ], CvEmbed.prototype, "pdfPageCount", 2);
573
607
 
574
608
  // src/index.ts
575
609
  if (typeof window !== "undefined" && !customElements.get("cv-embed")) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/render-pdf.ts","../src/render-markdown.ts","../src/cv-embed.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import * as pdfjsLib from 'pdfjs-dist';\nimport workerUrl from 'pdfjs-dist/build/pdf.worker.min.mjs?url';\n\nlet workerConfigured = false;\n\nfunction ensureWorker(): void {\n if (workerConfigured) return;\n pdfjsLib.GlobalWorkerOptions.workerSrc = workerUrl;\n workerConfigured = true;\n}\n\nexport interface RenderResult {\n numPages: number;\n}\n\nexport async function renderPdfPage(\n bytes: Uint8Array,\n pageNumber: number,\n canvas: HTMLCanvasElement,\n): Promise<RenderResult> {\n ensureWorker();\n const view = new Uint8Array(bytes.byteLength);\n view.set(bytes);\n const loadingTask = pdfjsLib.getDocument({ data: view });\n const pdf = await loadingTask.promise;\n const page = await pdf.getPage(pageNumber);\n\n const dpr = window.devicePixelRatio || 1;\n const baseViewport = page.getViewport({ scale: 1 });\n const containerWidth = canvas.parentElement?.clientWidth ?? baseViewport.width;\n const targetWidth = Math.min(containerWidth - 32, 900);\n const scale = (targetWidth / baseViewport.width) * dpr;\n const viewport = page.getViewport({ scale });\n\n canvas.width = Math.floor(viewport.width);\n canvas.height = Math.floor(viewport.height);\n canvas.style.width = `${Math.floor(viewport.width / dpr)}px`;\n canvas.style.height = `${Math.floor(viewport.height / dpr)}px`;\n\n const ctx = canvas.getContext('2d');\n if (!ctx) {\n throw new Error('Could not acquire 2D canvas context');\n }\n await page.render({ canvasContext: ctx, viewport }).promise;\n return { numPages: pdf.numPages };\n}\n","import DOMPurify from 'dompurify';\nimport { marked } from 'marked';\n\nexport function renderMarkdown(source: string): string {\n const raw = marked.parse(source, { async: false }) as string;\n return DOMPurify.sanitize(raw, {\n USE_PROFILES: { html: true },\n FORBID_TAGS: ['script', 'style', 'iframe', 'object', 'embed'],\n FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover'],\n });\n}\n","import { LitElement, css, html, type TemplateResult } from 'lit';\nimport { property, state } from 'lit/decorators.js';\nimport { extract } from '@cvfile/sdk';\nimport type { CvFile } from '@cvfile/sdk';\nimport { renderMarkdown } from './render-markdown.js';\nimport { renderPdfPage } from './render-pdf.js';\n\ntype Tab = 'pdf' | 'md' | 'html';\ntype Theme = 'auto' | 'light' | 'dark';\ntype ChangedKeys = Map<PropertyKey, unknown>;\n\nexport class CvEmbed extends LitElement {\n static override styles = css`\n :host {\n --cv-color: #111;\n --cv-bg: #fafaf9;\n --cv-border: #e5e5e3;\n --cv-toolbar-bg: #ffffff;\n --cv-stage-bg: #f4f4f2;\n --cv-meta: #6b7280;\n --cv-tab-hover: rgba(0, 0, 0, 0.04);\n --cv-tab-active: rgba(0, 0, 0, 0.08);\n --cv-card-bg: #ffffff;\n --cv-card-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);\n --cv-focus-ring: #4f46e5;\n --cv-error: #b91c1c;\n --cv-error-bg: #fef2f2;\n\n display: block;\n font-family: ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", sans-serif;\n color: var(--cv-color);\n background: var(--cv-bg);\n border: 1px solid var(--cv-border);\n border-radius: 12px;\n overflow: hidden;\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);\n width: 100%;\n min-height: 480px;\n contain: paint;\n }\n\n :host([theme='dark']),\n :host([theme='auto']) {\n color-scheme: light dark;\n }\n :host([theme='dark']) {\n --cv-color: #f3f4f6;\n --cv-bg: #0b0b0d;\n --cv-border: #2a2a2e;\n --cv-toolbar-bg: #15151a;\n --cv-stage-bg: #0f0f12;\n --cv-meta: #9ca3af;\n --cv-tab-hover: rgba(255, 255, 255, 0.05);\n --cv-tab-active: rgba(255, 255, 255, 0.10);\n --cv-card-bg: #15151a;\n --cv-card-shadow: 0 2px 12px rgba(0, 0, 0, 0.4);\n --cv-error-bg: #2a1414;\n }\n @media (prefers-color-scheme: dark) {\n :host([theme='auto']) {\n --cv-color: #f3f4f6;\n --cv-bg: #0b0b0d;\n --cv-border: #2a2a2e;\n --cv-toolbar-bg: #15151a;\n --cv-stage-bg: #0f0f12;\n --cv-meta: #9ca3af;\n --cv-tab-hover: rgba(255, 255, 255, 0.05);\n --cv-tab-active: rgba(255, 255, 255, 0.10);\n --cv-card-bg: #15151a;\n --cv-card-shadow: 0 2px 12px rgba(0, 0, 0, 0.4);\n --cv-error-bg: #2a1414;\n }\n }\n\n .toolbar {\n display: flex;\n align-items: center;\n gap: 0.25rem;\n padding: 0.5rem 0.75rem;\n border-bottom: 1px solid var(--cv-border);\n background: var(--cv-toolbar-bg);\n flex-wrap: wrap;\n }\n .tab {\n appearance: none;\n border: 0;\n background: transparent;\n padding: 0.4rem 0.8rem;\n border-radius: 6px;\n font: inherit;\n color: inherit;\n cursor: pointer;\n opacity: 0.6;\n transition: background 120ms, opacity 120ms;\n }\n .tab:hover:not([disabled]) { background: var(--cv-tab-hover); opacity: 0.95; }\n .tab[aria-selected='true'] {\n background: var(--cv-tab-active);\n opacity: 1;\n font-weight: 600;\n }\n .tab[disabled] { opacity: 0.3; cursor: not-allowed; }\n .tab:focus-visible {\n outline: 2px solid var(--cv-focus-ring);\n outline-offset: 2px;\n }\n .meta {\n margin-left: auto;\n font-size: 0.8rem;\n color: var(--cv-meta);\n display: flex;\n gap: 0.75rem;\n flex-wrap: wrap;\n }\n .meta code { font: inherit; opacity: 0.85; }\n .stage {\n position: relative;\n min-height: 420px;\n max-height: var(--cv-max-height, 80vh);\n overflow: auto;\n padding: 0;\n background: var(--cv-stage-bg);\n }\n .pdf-canvas {\n display: block;\n margin: 1rem auto;\n box-shadow: var(--cv-card-shadow);\n background: #fff;\n max-width: 100%;\n }\n .pager {\n display: flex;\n justify-content: center;\n align-items: center;\n gap: 0.5rem;\n padding: 0.5rem;\n background: var(--cv-toolbar-bg);\n border-top: 1px solid var(--cv-border);\n font-size: 0.85rem;\n }\n .pager button {\n appearance: none;\n border: 1px solid var(--cv-border);\n background: var(--cv-card-bg);\n color: inherit;\n padding: 0.25rem 0.6rem;\n border-radius: 6px;\n cursor: pointer;\n font: inherit;\n }\n .pager button:focus-visible {\n outline: 2px solid var(--cv-focus-ring);\n outline-offset: 2px;\n }\n .pager button[disabled] { opacity: 0.4; cursor: not-allowed; }\n .md, .html {\n padding: 1.5rem 2rem;\n background: var(--cv-card-bg);\n max-width: 760px;\n margin: 1rem auto;\n box-shadow: var(--cv-card-shadow);\n border-radius: 8px;\n line-height: 1.6;\n }\n .html iframe {\n width: 100%;\n min-height: 420px;\n border: 0;\n background: #fff;\n border-radius: 4px;\n }\n .md :first-child { margin-top: 0; }\n .md h1 { font-size: 1.75rem; }\n .md h2 { font-size: 1.25rem; border-bottom: 1px solid var(--cv-border); padding-bottom: 0.25rem; margin-top: 1.5rem; }\n .md ul { padding-left: 1.25rem; }\n .md a { color: var(--cv-focus-ring); }\n\n .skeleton {\n display: flex;\n flex-direction: column;\n gap: 1rem;\n padding: 1.5rem 2rem;\n max-width: 760px;\n margin: 1rem auto;\n }\n .skeleton .bar {\n height: 1rem;\n background: linear-gradient(90deg, var(--cv-tab-hover) 0%, var(--cv-tab-active) 50%, var(--cv-tab-hover) 100%);\n background-size: 200% 100%;\n border-radius: 4px;\n animation: shimmer 1.4s infinite;\n }\n .skeleton .bar.short { width: 40%; }\n .skeleton .bar.medium { width: 70%; }\n @keyframes shimmer {\n 0% { background-position: 200% 0; }\n 100% { background-position: -200% 0; }\n }\n\n .empty {\n padding: 2rem;\n text-align: center;\n color: var(--cv-meta);\n }\n\n .err {\n margin: 1rem;\n padding: 1.5rem;\n background: var(--cv-error-bg);\n color: var(--cv-error);\n border-radius: 8px;\n border: 1px solid var(--cv-error);\n }\n .err button {\n margin-top: 0.75rem;\n appearance: none;\n border: 1px solid var(--cv-error);\n background: transparent;\n color: var(--cv-error);\n padding: 0.4rem 0.9rem;\n border-radius: 6px;\n cursor: pointer;\n font: inherit;\n }\n\n /* Compact mode for narrow viewports */\n @media (max-width: 480px) {\n .toolbar { padding: 0.4rem 0.5rem; }\n .tab { padding: 0.35rem 0.6rem; font-size: 0.9rem; }\n .meta { width: 100%; margin-left: 0; padding-top: 0.25rem; font-size: 0.75rem; }\n .md, .html { padding: 1rem 1.25rem; margin: 0.5rem; }\n }\n\n /* Honour user motion preferences. */\n @media (prefers-reduced-motion: reduce) {\n .skeleton .bar { animation: none; }\n .tab { transition: none; }\n }\n `;\n\n @property({ type: String }) src = '';\n @property({ type: String }) view: Tab | 'auto' = 'auto';\n @property({ type: String, attribute: 'language' }) language = '';\n @property({ type: Boolean, attribute: 'tab-bar' }) tabBar = true;\n @property({ type: String, reflect: true }) theme: Theme = 'auto';\n\n @state() private file: CvFile | null = null;\n @state() private error: string | null = null;\n @state() private activeTab: Tab = 'pdf';\n @state() private loading = true;\n @state() private pdfPage = 1;\n @state() private pdfPageCount = 1;\n\n private pdfModulePromise: Promise<typeof import('./render-pdf.js')> | null = null;\n\n override async connectedCallback(): Promise<void> {\n super.connectedCallback();\n if (this.src) {\n await this.loadFromSrc();\n }\n }\n\n override willUpdate(changed: ChangedKeys): void {\n if (changed.has('src') && this.src) {\n void this.loadFromSrc();\n }\n if (changed.has('view') && this.view !== 'auto' && this.file) {\n this.activeTab = this.view;\n }\n }\n\n override updated(changed: ChangedKeys): void {\n if (changed.has('activeTab') || changed.has('file') || changed.has('pdfPage')) {\n if (this.activeTab === 'pdf' && this.file) {\n void this.renderPdf();\n }\n }\n }\n\n loadFromBytes(bytes: Uint8Array): Promise<void> {\n return this.processBytes(bytes);\n }\n\n /** Public API: re-attempt the last load (used by error retry button). */\n async retry(): Promise<void> {\n this.error = null;\n if (this.src) {\n await this.loadFromSrc();\n }\n }\n\n private async loadFromSrc(): Promise<void> {\n this.loading = true;\n this.error = null;\n this.file = null;\n try {\n const res = await fetch(this.src);\n if (!res.ok) {\n throw new Error(`Fetch failed: ${res.status} ${res.statusText}`);\n }\n const buf = new Uint8Array(await res.arrayBuffer());\n await this.processBytes(buf);\n } catch (err) {\n this.error = (err as Error).message;\n this.loading = false;\n }\n }\n\n private async processBytes(bytes: Uint8Array): Promise<void> {\n this.loading = true;\n try {\n const file = await extract(bytes);\n this.file = file;\n this.activeTab = this.pickInitialTab(file);\n this.loading = false;\n } catch (err) {\n this.error = (err as Error).message;\n this.loading = false;\n }\n }\n\n private pickInitialTab(file: CvFile): Tab {\n if (this.view !== 'auto') return this.view;\n void file;\n return 'pdf';\n }\n\n private async renderPdf(): Promise<void> {\n if (!this.file) return;\n const canvas = this.renderRoot.querySelector<HTMLCanvasElement>('.pdf-canvas');\n if (!canvas) return;\n if (!this.pdfModulePromise) {\n this.pdfModulePromise = import('./render-pdf.js');\n }\n try {\n const mod = await this.pdfModulePromise;\n const { numPages } = await mod.renderPdfPage(this.file.bytes, this.pdfPage, canvas);\n if (numPages !== this.pdfPageCount) {\n this.pdfPageCount = numPages;\n }\n } catch (err) {\n this.error = `PDF render failed: ${(err as Error).message}`;\n }\n }\n\n private switchTab(tab: Tab, available: Record<Tab, boolean>): void {\n if (!available[tab]) return;\n this.activeTab = tab;\n }\n\n /** Keyboard navigation per WAI-ARIA tablist authoring practices. */\n private handleTabKeydown(event: KeyboardEvent, available: Record<Tab, boolean>): void {\n const order: Tab[] = (['pdf', 'md', 'html'] as Tab[]).filter((t) => available[t]);\n const idx = order.indexOf(this.activeTab);\n let next: Tab | null = null;\n switch (event.key) {\n case 'ArrowRight':\n case 'ArrowDown':\n next = order[(idx + 1) % order.length] ?? null;\n break;\n case 'ArrowLeft':\n case 'ArrowUp':\n next = order[(idx - 1 + order.length) % order.length] ?? null;\n break;\n case 'Home':\n next = order[0] ?? null;\n break;\n case 'End':\n next = order[order.length - 1] ?? null;\n break;\n }\n if (next) {\n event.preventDefault();\n this.switchTab(next, available);\n requestAnimationFrame(() => {\n const button = this.renderRoot.querySelector<HTMLButtonElement>(`button[data-tab=\"${next}\"]`);\n button?.focus();\n });\n }\n }\n\n override render(): TemplateResult {\n if (this.error) {\n return html`<div class=\"err\" role=\"alert\">\n <strong>Could not load .cv</strong>\n <div>${this.error}</div>\n ${this.src\n ? html`<button type=\"button\" @click=${() => void this.retry()}>Retry</button>`\n : null}\n <slot name=\"error\"></slot>\n </div>`;\n }\n if (this.loading || !this.file) {\n return html`<div class=\"empty\" aria-busy=\"true\" aria-live=\"polite\">\n <div class=\"skeleton\" part=\"skeleton\">\n <div class=\"bar short\"></div>\n <div class=\"bar\"></div>\n <div class=\"bar medium\"></div>\n <div class=\"bar\"></div>\n <div class=\"bar short\"></div>\n </div>\n </div>`;\n }\n\n const file = this.file;\n const md = file.payloads.find((p) => p.mimeType === 'text/markdown');\n const htmlPayload = file.payloads.find((p) => p.mimeType === 'text/html');\n const available: Record<Tab, boolean> = {\n pdf: true,\n md: !!md,\n html: !!htmlPayload,\n };\n const panelId = 'cv-embed-panel';\n\n return html`\n ${this.tabBar\n ? html`<div\n class=\"toolbar\"\n role=\"tablist\"\n aria-label=\"Document representations\"\n part=\"toolbar\"\n @keydown=${(e: KeyboardEvent) => this.handleTabKeydown(e, available)}\n >\n ${(['pdf', 'md', 'html'] as Tab[]).map((t) => {\n const enabled = available[t];\n const selected = this.activeTab === t;\n const labels: Record<Tab, string> = { pdf: 'PDF', md: 'Markdown', html: 'HTML' };\n return html`<button\n class=\"tab\"\n data-tab=${t}\n role=\"tab\"\n part=\"tab tab-${t}${selected ? ' tab-active' : ''}\"\n aria-selected=${selected}\n aria-controls=${panelId}\n tabindex=${selected ? 0 : -1}\n ?disabled=${!enabled}\n @click=${() => this.switchTab(t, available)}\n >\n ${labels[t]}\n </button>`;\n })}\n <div class=\"meta\" part=\"meta\">\n <span><code>cv:${file.metadata.version}</code></span>\n <span>${file.metadata.primaryLanguage}</span>\n <span>${file.payloads.length} payload${file.payloads.length === 1 ? '' : 's'}</span>\n </div>\n </div>`\n : null}\n <div\n id=${panelId}\n class=\"stage\"\n role=\"tabpanel\"\n part=\"stage\"\n aria-label=\"${this.activeTab.toUpperCase()} representation\"\n >\n ${this.activeTab === 'pdf'\n ? html`<canvas class=\"pdf-canvas\" part=\"pdf-canvas\" aria-label=\"PDF preview\"></canvas>`\n : this.activeTab === 'md' && md\n ? html`<div class=\"md\" part=\"md\">${this.renderMd(md.text())}</div>`\n : this.activeTab === 'html' && htmlPayload\n ? html`<div class=\"html\" part=\"html\">\n <iframe sandbox=\"\" srcdoc=${htmlPayload.text()} title=\"HTML rendering\"></iframe>\n </div>`\n : html`<div class=\"empty\">No payload available for this view.</div>`}\n </div>\n ${this.activeTab === 'pdf' && this.pdfPageCount > 1\n ? html`<div class=\"pager\" part=\"pager\">\n <button\n type=\"button\"\n ?disabled=${this.pdfPage <= 1}\n aria-label=\"Previous page\"\n @click=${() => {\n this.pdfPage = Math.max(1, this.pdfPage - 1);\n }}\n >\n ← Prev\n </button>\n <span aria-live=\"polite\">Page ${this.pdfPage} of ${this.pdfPageCount}</span>\n <button\n type=\"button\"\n ?disabled=${this.pdfPage >= this.pdfPageCount}\n aria-label=\"Next page\"\n @click=${() => {\n this.pdfPage = Math.min(this.pdfPageCount, this.pdfPage + 1);\n }}\n >\n Next →\n </button>\n </div>`\n : null}\n `;\n }\n\n private renderMd(source: string): TemplateResult {\n const safeHtml = renderMarkdown(source);\n const tmpl = document.createElement('template');\n tmpl.innerHTML = safeHtml;\n return html`${tmpl.content.cloneNode(true)}`;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'cv-embed': CvEmbed;\n }\n}\n\nexport { renderPdfPage };\n","import { CvEmbed } from './cv-embed.js';\n\nif (typeof window !== 'undefined' && !customElements.get('cv-embed')) {\n customElements.define('cv-embed', CvEmbed);\n}\n\nexport { CvEmbed };\n"]}
1
+ {"version":3,"sources":["../src/render-markdown.ts","../src/payload-selection.ts","../src/cv-embed.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import DOMPurify from 'dompurify';\nimport { marked } from 'marked';\n\nlet hooked = false;\n\n/**\n * Force every rendered anchor to open safely. Markdown links point at\n * untrusted, author-supplied URLs, so we add `rel=\"noopener noreferrer\"` to\n * sever any `window.opener` link and suppress referrer leakage.\n */\nfunction ensureLinkHardening(): void {\n if (hooked) return;\n DOMPurify.addHook('afterSanitizeAttributes', (node) => {\n if (node instanceof HTMLAnchorElement) {\n node.setAttribute('rel', 'noopener noreferrer');\n }\n });\n hooked = true;\n}\n\nexport function renderMarkdown(source: string): string {\n ensureLinkHardening();\n const raw = marked.parse(source, { async: false }) as string;\n // DOMPurify's `html` profile is a safe positive allowlist on its own: it\n // strips scripting elements, event-handler attributes, and dangerous URI\n // schemes by default. A partial FORBID_* blocklist on top is misleading, so\n // we rely on the profile and harden links via the hook above.\n return DOMPurify.sanitize(raw, {\n USE_PROFILES: { html: true },\n });\n}\n","import type { CvFile, ExtractedPayload } from '@cvfile/sdk';\n\n/**\n * Select the payload of a given mime type, preferring `language`, then the\n * file's `primaryLanguage`, then the first match. Mirrors the SDK's\n * `pickByLanguage` semantics used internally by `extractMarkdown`/`extractHtml`.\n */\nexport function pickPayloadByLanguage(\n file: CvFile,\n mimeType: string,\n language: string,\n): ExtractedPayload | undefined {\n const matches = file.payloads.filter((p) => p.mimeType === mimeType);\n if (matches.length === 0) return undefined;\n const preferred = language || file.metadata.primaryLanguage;\n return matches.find((p) => p.language === preferred) ?? matches[0];\n}\n\n/**\n * The crawler-friendly clean text for a file: the selected markdown payload, or\n * HTML as a fallback, or an empty string when neither exists.\n */\nexport function selectCleanText(file: CvFile, language: string): string {\n return (\n pickPayloadByLanguage(file, 'text/markdown', language)?.text() ??\n pickPayloadByLanguage(file, 'text/html', language)?.text() ??\n ''\n );\n}\n","import { LitElement, css, html, type TemplateResult } from 'lit';\nimport { property, state } from 'lit/decorators.js';\nimport { extract } from '@cvfile/sdk';\nimport type { CvFile } from '@cvfile/sdk';\nimport { renderMarkdown } from './render-markdown.js';\nimport { pickPayloadByLanguage, selectCleanText } from './payload-selection.js';\n\ntype Tab = 'pdf' | 'md' | 'html';\ntype Theme = 'auto' | 'light' | 'dark';\ntype ChangedKeys = Map<PropertyKey, unknown>;\n\n/** Abort a stalled fetch after this many milliseconds. */\nconst FETCH_TIMEOUT_MS = 15_000;\n/** Reject absurdly large bodies before extraction (.cv files are PDF-sized). */\nconst MAX_FILE_BYTES = 50 * 1024 * 1024;\n\nexport class CvEmbed extends LitElement {\n static override styles = css`\n :host {\n --cv-color: #111;\n --cv-bg: #fafaf9;\n --cv-border: #e5e5e3;\n --cv-toolbar-bg: #ffffff;\n --cv-stage-bg: #f4f4f2;\n --cv-meta: #6b7280;\n --cv-tab-hover: rgba(0, 0, 0, 0.04);\n --cv-tab-active: rgba(0, 0, 0, 0.08);\n --cv-card-bg: #ffffff;\n --cv-card-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);\n --cv-focus-ring: #4f46e5;\n --cv-error: #b91c1c;\n --cv-error-bg: #fef2f2;\n\n display: block;\n font-family: ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", sans-serif;\n color: var(--cv-color);\n background: var(--cv-bg);\n border: 1px solid var(--cv-border);\n border-radius: 12px;\n overflow: hidden;\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);\n width: 100%;\n min-height: 480px;\n contain: paint;\n }\n\n :host([theme='dark']),\n :host([theme='auto']) {\n color-scheme: light dark;\n }\n :host([theme='dark']) {\n --cv-color: #f3f4f6;\n --cv-bg: #0b0b0d;\n --cv-border: #2a2a2e;\n --cv-toolbar-bg: #15151a;\n --cv-stage-bg: #0f0f12;\n --cv-meta: #9ca3af;\n --cv-tab-hover: rgba(255, 255, 255, 0.05);\n --cv-tab-active: rgba(255, 255, 255, 0.10);\n --cv-card-bg: #15151a;\n --cv-card-shadow: 0 2px 12px rgba(0, 0, 0, 0.4);\n --cv-error-bg: #2a1414;\n }\n @media (prefers-color-scheme: dark) {\n :host([theme='auto']) {\n --cv-color: #f3f4f6;\n --cv-bg: #0b0b0d;\n --cv-border: #2a2a2e;\n --cv-toolbar-bg: #15151a;\n --cv-stage-bg: #0f0f12;\n --cv-meta: #9ca3af;\n --cv-tab-hover: rgba(255, 255, 255, 0.05);\n --cv-tab-active: rgba(255, 255, 255, 0.10);\n --cv-card-bg: #15151a;\n --cv-card-shadow: 0 2px 12px rgba(0, 0, 0, 0.4);\n --cv-error-bg: #2a1414;\n }\n }\n\n .toolbar {\n display: flex;\n align-items: center;\n gap: 0.25rem;\n padding: 0.5rem 0.75rem;\n border-bottom: 1px solid var(--cv-border);\n background: var(--cv-toolbar-bg);\n flex-wrap: wrap;\n }\n .tab {\n appearance: none;\n border: 0;\n background: transparent;\n padding: 0.4rem 0.8rem;\n border-radius: 6px;\n font: inherit;\n color: inherit;\n cursor: pointer;\n opacity: 0.6;\n transition: background 120ms, opacity 120ms;\n }\n .tab:hover:not([disabled]) { background: var(--cv-tab-hover); opacity: 0.95; }\n .tab[aria-selected='true'] {\n background: var(--cv-tab-active);\n opacity: 1;\n font-weight: 600;\n }\n .tab[disabled] { opacity: 0.3; cursor: not-allowed; }\n .tab:focus-visible {\n outline: 2px solid var(--cv-focus-ring);\n outline-offset: 2px;\n }\n .meta {\n margin-left: auto;\n font-size: 0.8rem;\n color: var(--cv-meta);\n display: flex;\n gap: 0.75rem;\n flex-wrap: wrap;\n }\n .meta code { font: inherit; opacity: 0.85; }\n .stage {\n position: relative;\n min-height: 420px;\n max-height: var(--cv-max-height, 80vh);\n overflow: auto;\n padding: 0;\n background: var(--cv-stage-bg);\n }\n .pdf-canvas {\n display: block;\n margin: 1rem auto;\n box-shadow: var(--cv-card-shadow);\n background: #fff;\n max-width: 100%;\n }\n .pager {\n display: flex;\n justify-content: center;\n align-items: center;\n gap: 0.5rem;\n padding: 0.5rem;\n background: var(--cv-toolbar-bg);\n border-top: 1px solid var(--cv-border);\n font-size: 0.85rem;\n }\n .pager button {\n appearance: none;\n border: 1px solid var(--cv-border);\n background: var(--cv-card-bg);\n color: inherit;\n padding: 0.25rem 0.6rem;\n border-radius: 6px;\n cursor: pointer;\n font: inherit;\n }\n .pager button:focus-visible {\n outline: 2px solid var(--cv-focus-ring);\n outline-offset: 2px;\n }\n .pager button[disabled] { opacity: 0.4; cursor: not-allowed; }\n .md, .html {\n padding: 1.5rem 2rem;\n background: var(--cv-card-bg);\n max-width: 760px;\n margin: 1rem auto;\n box-shadow: var(--cv-card-shadow);\n border-radius: 8px;\n line-height: 1.6;\n }\n .html iframe {\n width: 100%;\n min-height: 420px;\n border: 0;\n background: #fff;\n border-radius: 4px;\n }\n .md :first-child { margin-top: 0; }\n .md h1 { font-size: 1.75rem; }\n .md h2 { font-size: 1.25rem; border-bottom: 1px solid var(--cv-border); padding-bottom: 0.25rem; margin-top: 1.5rem; }\n .md ul { padding-left: 1.25rem; }\n .md a { color: var(--cv-focus-ring); }\n\n .skeleton {\n display: flex;\n flex-direction: column;\n gap: 1rem;\n padding: 1.5rem 2rem;\n max-width: 760px;\n margin: 1rem auto;\n }\n .skeleton .bar {\n height: 1rem;\n background: linear-gradient(90deg, var(--cv-tab-hover) 0%, var(--cv-tab-active) 50%, var(--cv-tab-hover) 100%);\n background-size: 200% 100%;\n border-radius: 4px;\n animation: shimmer 1.4s infinite;\n }\n .skeleton .bar.short { width: 40%; }\n .skeleton .bar.medium { width: 70%; }\n @keyframes shimmer {\n 0% { background-position: 200% 0; }\n 100% { background-position: -200% 0; }\n }\n\n .empty {\n padding: 2rem;\n text-align: center;\n color: var(--cv-meta);\n }\n\n .err {\n margin: 1rem;\n padding: 1.5rem;\n background: var(--cv-error-bg);\n color: var(--cv-error);\n border-radius: 8px;\n border: 1px solid var(--cv-error);\n }\n .err button {\n margin-top: 0.75rem;\n appearance: none;\n border: 1px solid var(--cv-error);\n background: transparent;\n color: var(--cv-error);\n padding: 0.4rem 0.9rem;\n border-radius: 6px;\n cursor: pointer;\n font: inherit;\n }\n\n /* Compact mode for narrow viewports */\n @media (max-width: 480px) {\n .toolbar { padding: 0.4rem 0.5rem; }\n .tab { padding: 0.35rem 0.6rem; font-size: 0.9rem; }\n .meta { width: 100%; margin-left: 0; padding-top: 0.25rem; font-size: 0.75rem; }\n .md, .html { padding: 1rem 1.25rem; margin: 0.5rem; }\n }\n\n /* Honour user motion preferences. */\n @media (prefers-reduced-motion: reduce) {\n .skeleton .bar { animation: none; }\n .tab { transition: none; }\n }\n `;\n\n @property({ type: String }) src = '';\n @property({ type: String }) view: Tab | 'auto' = 'auto';\n @property({ type: String, attribute: 'language' }) language = '';\n @property({ type: Boolean, attribute: 'tab-bar' }) tabBar = true;\n @property({ type: String, reflect: true }) theme: Theme = 'auto';\n /** Optional override for the pdf.js worker URL (defaults to a bundler-portable resolution). */\n @property({ type: String, attribute: 'worker-src' }) workerSrc = '';\n\n @state() private file: CvFile | null = null;\n @state() private error: string | null = null;\n @state() private activeTab: Tab = 'pdf';\n @state() private loading = true;\n @state() private pdfPage = 1;\n @state() private pdfPageCount = 1;\n\n /**\n * The extracted crawler-friendly clean text (the selected markdown payload,\n * or HTML as a fallback). Exposed read-only so hosts and tests can read it,\n * and mirrored into the light DOM in {@link projectCleanText} so search\n * engines can index it (shadow-DOM content is not indexed).\n */\n get cleanText(): string {\n return this._cleanText;\n }\n private _cleanText = '';\n private cleanTextNode: HTMLElement | null = null;\n\n private pdfModulePromise: Promise<typeof import('./render-pdf.js')> | null = null;\n\n override async connectedCallback(): Promise<void> {\n super.connectedCallback();\n if (this.src) {\n await this.loadFromSrc();\n }\n }\n\n override willUpdate(changed: ChangedKeys): void {\n if (changed.has('src') && this.src) {\n void this.loadFromSrc();\n }\n if (changed.has('view') && this.view !== 'auto' && this.file) {\n this.activeTab = this.view;\n }\n }\n\n override updated(changed: ChangedKeys): void {\n if (changed.has('activeTab') || changed.has('file') || changed.has('pdfPage')) {\n if (this.activeTab === 'pdf' && this.file) {\n void this.renderPdf();\n }\n }\n if (changed.has('file') || changed.has('language')) {\n this.projectCleanText();\n }\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n this.cleanTextNode?.remove();\n this.cleanTextNode = null;\n }\n\n loadFromBytes(bytes: Uint8Array): Promise<void> {\n return this.processBytes(bytes);\n }\n\n /** Public API: re-attempt the last load (used by error retry button). */\n async retry(): Promise<void> {\n this.error = null;\n if (this.src) {\n await this.loadFromSrc();\n }\n }\n\n private async loadFromSrc(): Promise<void> {\n this.loading = true;\n this.error = null;\n this.file = null;\n try {\n const res = await fetch(this.src, {\n credentials: 'omit',\n redirect: 'follow',\n signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),\n });\n if (!res.ok) {\n throw new Error(`Fetch failed: ${res.status} ${res.statusText}`);\n }\n const declared = Number(res.headers.get('content-length'));\n if (Number.isFinite(declared) && declared > MAX_FILE_BYTES) {\n throw new Error(`File too large: ${declared} bytes exceeds limit of ${MAX_FILE_BYTES}`);\n }\n const buf = new Uint8Array(await res.arrayBuffer());\n if (buf.byteLength > MAX_FILE_BYTES) {\n throw new Error(`File too large: ${buf.byteLength} bytes exceeds limit of ${MAX_FILE_BYTES}`);\n }\n await this.processBytes(buf);\n } catch (err) {\n this.error = (err as Error).message;\n this.loading = false;\n }\n }\n\n private async processBytes(bytes: Uint8Array): Promise<void> {\n this.loading = true;\n try {\n const file = await extract(bytes);\n this.file = file;\n this.activeTab = this.pickInitialTab(file);\n this.loading = false;\n } catch (err) {\n this.error = (err as Error).message;\n this.loading = false;\n }\n }\n\n private pickInitialTab(file: CvFile): Tab {\n if (this.view !== 'auto') return this.view;\n void file;\n return 'pdf';\n }\n\n /**\n * Mirror the extracted clean text into the LIGHT DOM. Shadow-DOM content is\n * invisible to crawlers, which defeats the purpose of shipping clean text, so\n * we maintain a single light-DOM child holding the selected markdown (or HTML)\n * payload. It is visually hidden but remains in the accessibility tree and is\n * indexable. Hosts can also read it via the `cleanText` property.\n */\n private projectCleanText(): void {\n const text = this.file ? selectCleanText(this.file, this.language) : '';\n this._cleanText = text;\n\n if (!text) {\n this.cleanTextNode?.remove();\n this.cleanTextNode = null;\n return;\n }\n if (!this.cleanTextNode) {\n const node = document.createElement('div');\n node.setAttribute('data-cv-clean-text', '');\n node.setAttribute('aria-hidden', 'false');\n // Visually hidden but kept in the DOM, accessibility tree, and crawl index.\n Object.assign(node.style, {\n position: 'absolute',\n width: '1px',\n height: '1px',\n margin: '-1px',\n padding: '0',\n overflow: 'hidden',\n clip: 'rect(0 0 0 0)',\n clipPath: 'inset(50%)',\n whiteSpace: 'pre-wrap',\n border: '0',\n } satisfies Partial<CSSStyleDeclaration>);\n this.cleanTextNode = node;\n this.appendChild(node);\n }\n this.cleanTextNode.textContent = text;\n }\n\n private async renderPdf(): Promise<void> {\n if (!this.file) return;\n const canvas = this.renderRoot.querySelector<HTMLCanvasElement>('.pdf-canvas');\n if (!canvas) return;\n if (!this.pdfModulePromise) {\n // Dynamic import keeps heavy pdf.js out of the entry chunk.\n this.pdfModulePromise = import('./render-pdf.js');\n }\n try {\n const mod = await this.pdfModulePromise;\n // Configure the worker source on the lazily loaded module (idempotent).\n if (this.workerSrc) mod.setWorkerSrc(this.workerSrc);\n const { numPages } = await mod.renderPdfPage(this.file.bytes, this.pdfPage, canvas);\n if (numPages !== this.pdfPageCount) {\n this.pdfPageCount = numPages;\n }\n } catch (err) {\n this.error = `PDF render failed: ${(err as Error).message}`;\n }\n }\n\n private switchTab(tab: Tab, available: Record<Tab, boolean>): void {\n if (!available[tab]) return;\n this.activeTab = tab;\n }\n\n /** Keyboard navigation per WAI-ARIA tablist authoring practices. */\n private handleTabKeydown(event: KeyboardEvent, available: Record<Tab, boolean>): void {\n const order: Tab[] = (['pdf', 'md', 'html'] as Tab[]).filter((t) => available[t]);\n const idx = order.indexOf(this.activeTab);\n let next: Tab | null = null;\n switch (event.key) {\n case 'ArrowRight':\n case 'ArrowDown':\n next = order[(idx + 1) % order.length] ?? null;\n break;\n case 'ArrowLeft':\n case 'ArrowUp':\n next = order[(idx - 1 + order.length) % order.length] ?? null;\n break;\n case 'Home':\n next = order[0] ?? null;\n break;\n case 'End':\n next = order[order.length - 1] ?? null;\n break;\n }\n if (next) {\n event.preventDefault();\n this.switchTab(next, available);\n requestAnimationFrame(() => {\n const button = this.renderRoot.querySelector<HTMLButtonElement>(`button[data-tab=\"${next}\"]`);\n button?.focus();\n });\n }\n }\n\n override render(): TemplateResult {\n if (this.error) {\n return html`<div class=\"err\" role=\"alert\">\n <strong>Could not load .cv</strong>\n <div>${this.error}</div>\n ${this.src\n ? html`<button type=\"button\" @click=${() => void this.retry()}>Retry</button>`\n : null}\n <slot name=\"error\"></slot>\n </div>`;\n }\n if (this.loading || !this.file) {\n return html`<div class=\"empty\" aria-busy=\"true\" aria-live=\"polite\">\n <div class=\"skeleton\" part=\"skeleton\">\n <div class=\"bar short\"></div>\n <div class=\"bar\"></div>\n <div class=\"bar medium\"></div>\n <div class=\"bar\"></div>\n <div class=\"bar short\"></div>\n </div>\n </div>`;\n }\n\n const file = this.file;\n const md = pickPayloadByLanguage(file, 'text/markdown', this.language);\n const htmlPayload = pickPayloadByLanguage(file, 'text/html', this.language);\n const available: Record<Tab, boolean> = {\n pdf: true,\n md: !!md,\n html: !!htmlPayload,\n };\n const panelId = 'cv-embed-panel';\n\n return html`\n ${this.tabBar\n ? html`<div\n class=\"toolbar\"\n role=\"tablist\"\n aria-label=\"Document representations\"\n part=\"toolbar\"\n @keydown=${(e: KeyboardEvent) => this.handleTabKeydown(e, available)}\n >\n ${(['pdf', 'md', 'html'] as Tab[]).map((t) => {\n const enabled = available[t];\n const selected = this.activeTab === t;\n const labels: Record<Tab, string> = { pdf: 'PDF', md: 'Markdown', html: 'HTML' };\n return html`<button\n class=\"tab\"\n data-tab=${t}\n role=\"tab\"\n part=\"tab tab-${t}${selected ? ' tab-active' : ''}\"\n aria-selected=${selected}\n aria-controls=${panelId}\n tabindex=${selected ? 0 : -1}\n ?disabled=${!enabled}\n @click=${() => this.switchTab(t, available)}\n >\n ${labels[t]}\n </button>`;\n })}\n <div class=\"meta\" part=\"meta\">\n <span><code>cv:${file.metadata.version}</code></span>\n <span>${file.metadata.primaryLanguage}</span>\n <span>${file.payloads.length} payload${file.payloads.length === 1 ? '' : 's'}</span>\n </div>\n </div>`\n : null}\n <div\n id=${panelId}\n class=\"stage\"\n role=\"tabpanel\"\n part=\"stage\"\n aria-label=\"${this.activeTab.toUpperCase()} representation\"\n >\n ${this.activeTab === 'pdf'\n ? html`<canvas class=\"pdf-canvas\" part=\"pdf-canvas\" aria-label=\"PDF preview\"></canvas>`\n : this.activeTab === 'md' && md\n ? html`<div class=\"md\" part=\"md\">${this.renderMd(md.text())}</div>`\n : this.activeTab === 'html' && htmlPayload\n ? html`<div class=\"html\" part=\"html\">\n <iframe sandbox=\"\" srcdoc=${htmlPayload.text()} title=\"HTML rendering\"></iframe>\n </div>`\n : html`<div class=\"empty\">No payload available for this view.</div>`}\n </div>\n ${this.activeTab === 'pdf' && this.pdfPageCount > 1\n ? html`<div class=\"pager\" part=\"pager\">\n <button\n type=\"button\"\n ?disabled=${this.pdfPage <= 1}\n aria-label=\"Previous page\"\n @click=${() => {\n this.pdfPage = Math.max(1, this.pdfPage - 1);\n }}\n >\n ← Prev\n </button>\n <span aria-live=\"polite\">Page ${this.pdfPage} of ${this.pdfPageCount}</span>\n <button\n type=\"button\"\n ?disabled=${this.pdfPage >= this.pdfPageCount}\n aria-label=\"Next page\"\n @click=${() => {\n this.pdfPage = Math.min(this.pdfPageCount, this.pdfPage + 1);\n }}\n >\n Next →\n </button>\n </div>`\n : null}\n `;\n }\n\n private renderMd(source: string): TemplateResult {\n const safeHtml = renderMarkdown(source);\n const tmpl = document.createElement('template');\n tmpl.innerHTML = safeHtml;\n return html`${tmpl.content.cloneNode(true)}`;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'cv-embed': CvEmbed;\n }\n}\n","import { CvEmbed } from './cv-embed.js';\n\nif (typeof window !== 'undefined' && !customElements.get('cv-embed')) {\n customElements.define('cv-embed', CvEmbed);\n}\n\nexport { CvEmbed };\n"]}
@@ -0,0 +1,8 @@
1
+ /** Override the pdf.js worker URL (e.g. when serving it from a custom path/CDN). */
2
+ declare function setWorkerSrc(src: string): void;
3
+ interface RenderResult {
4
+ numPages: number;
5
+ }
6
+ declare function renderPdfPage(bytes: Uint8Array, pageNumber: number, canvas: HTMLCanvasElement): Promise<RenderResult>;
7
+
8
+ export { type RenderResult, renderPdfPage, setWorkerSrc };
@@ -0,0 +1,43 @@
1
+ import './chunk-EUXUH3YW.js';
2
+ import * as pdfjsLib from 'pdfjs-dist';
3
+
4
+ var DEFAULT_WORKER_URL = new URL("pdfjs-dist/build/pdf.worker.min.mjs", import.meta.url).href;
5
+ var workerConfigured = false;
6
+ var workerSrcOverride = null;
7
+ function setWorkerSrc(src) {
8
+ workerSrcOverride = src;
9
+ workerConfigured = false;
10
+ }
11
+ function ensureWorker() {
12
+ if (workerConfigured) return;
13
+ pdfjsLib.GlobalWorkerOptions.workerSrc = workerSrcOverride ?? DEFAULT_WORKER_URL;
14
+ workerConfigured = true;
15
+ }
16
+ async function renderPdfPage(bytes, pageNumber, canvas) {
17
+ ensureWorker();
18
+ const view = new Uint8Array(bytes.byteLength);
19
+ view.set(bytes);
20
+ const loadingTask = pdfjsLib.getDocument({ data: view });
21
+ const pdf = await loadingTask.promise;
22
+ const page = await pdf.getPage(pageNumber);
23
+ const dpr = window.devicePixelRatio || 1;
24
+ const baseViewport = page.getViewport({ scale: 1 });
25
+ const containerWidth = canvas.parentElement?.clientWidth ?? baseViewport.width;
26
+ const targetWidth = Math.min(containerWidth - 32, 900);
27
+ const scale = targetWidth / baseViewport.width * dpr;
28
+ const viewport = page.getViewport({ scale });
29
+ canvas.width = Math.floor(viewport.width);
30
+ canvas.height = Math.floor(viewport.height);
31
+ canvas.style.width = `${Math.floor(viewport.width / dpr)}px`;
32
+ canvas.style.height = `${Math.floor(viewport.height / dpr)}px`;
33
+ const ctx = canvas.getContext("2d");
34
+ if (!ctx) {
35
+ throw new Error("Could not acquire 2D canvas context");
36
+ }
37
+ await page.render({ canvasContext: ctx, viewport }).promise;
38
+ return { numPages: pdf.numPages };
39
+ }
40
+
41
+ export { renderPdfPage, setWorkerSrc };
42
+ //# sourceMappingURL=render-pdf.js.map
43
+ //# sourceMappingURL=render-pdf.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/render-pdf.ts"],"names":[],"mappings":";;;AAQA,IAAM,qBAAqB,IAAI,GAAA,CAAI,qCAAA,EAAuC,MAAA,CAAA,IAAA,CAAY,GAAG,CAAA,CAAE,IAAA;AAE3F,IAAI,gBAAA,GAAmB,KAAA;AACvB,IAAI,iBAAA,GAAmC,IAAA;AAGhC,SAAS,aAAa,GAAA,EAAmB;AAC9C,EAAA,iBAAA,GAAoB,GAAA;AACpB,EAAA,gBAAA,GAAmB,KAAA;AACrB;AAEA,SAAS,YAAA,GAAqB;AAC5B,EAAA,IAAI,gBAAA,EAAkB;AACtB,EAAS,QAAA,CAAA,mBAAA,CAAoB,YAAY,iBAAA,IAAqB,kBAAA;AAC9D,EAAA,gBAAA,GAAmB,IAAA;AACrB;AAMA,eAAsB,aAAA,CACpB,KAAA,EACA,UAAA,EACA,MAAA,EACuB;AACvB,EAAA,YAAA,EAAa;AACb,EAAA,MAAM,IAAA,GAAO,IAAI,UAAA,CAAW,KAAA,CAAM,UAAU,CAAA;AAC5C,EAAA,IAAA,CAAK,IAAI,KAAK,CAAA;AACd,EAAA,MAAM,WAAA,GAAuB,QAAA,CAAA,WAAA,CAAY,EAAE,IAAA,EAAM,MAAM,CAAA;AACvD,EAAA,MAAM,GAAA,GAAM,MAAM,WAAA,CAAY,OAAA;AAC9B,EAAA,MAAM,IAAA,GAAO,MAAM,GAAA,CAAI,OAAA,CAAQ,UAAU,CAAA;AAEzC,EAAA,MAAM,GAAA,GAAM,OAAO,gBAAA,IAAoB,CAAA;AACvC,EAAA,MAAM,eAAe,IAAA,CAAK,WAAA,CAAY,EAAE,KAAA,EAAO,GAAG,CAAA;AAClD,EAAA,MAAM,cAAA,GAAiB,MAAA,CAAO,aAAA,EAAe,WAAA,IAAe,YAAA,CAAa,KAAA;AACzE,EAAA,MAAM,WAAA,GAAc,IAAA,CAAK,GAAA,CAAI,cAAA,GAAiB,IAAI,GAAG,CAAA;AACrD,EAAA,MAAM,KAAA,GAAS,WAAA,GAAc,YAAA,CAAa,KAAA,GAAS,GAAA;AACnD,EAAA,MAAM,QAAA,GAAW,IAAA,CAAK,WAAA,CAAY,EAAE,OAAO,CAAA;AAE3C,EAAA,MAAA,CAAO,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,QAAA,CAAS,KAAK,CAAA;AACxC,EAAA,MAAA,CAAO,MAAA,GAAS,IAAA,CAAK,KAAA,CAAM,QAAA,CAAS,MAAM,CAAA;AAC1C,EAAA,MAAA,CAAO,KAAA,CAAM,QAAQ,CAAA,EAAG,IAAA,CAAK,MAAM,QAAA,CAAS,KAAA,GAAQ,GAAG,CAAC,CAAA,EAAA,CAAA;AACxD,EAAA,MAAA,CAAO,KAAA,CAAM,SAAS,CAAA,EAAG,IAAA,CAAK,MAAM,QAAA,CAAS,MAAA,GAAS,GAAG,CAAC,CAAA,EAAA,CAAA;AAE1D,EAAA,MAAM,GAAA,GAAM,MAAA,CAAO,UAAA,CAAW,IAAI,CAAA;AAClC,EAAA,IAAI,CAAC,GAAA,EAAK;AACR,IAAA,MAAM,IAAI,MAAM,qCAAqC,CAAA;AAAA,EACvD;AACA,EAAA,MAAM,KAAK,MAAA,CAAO,EAAE,eAAe,GAAA,EAAK,QAAA,EAAU,CAAA,CAAE,OAAA;AACpD,EAAA,OAAO,EAAE,QAAA,EAAU,GAAA,CAAI,QAAA,EAAS;AAClC","file":"render-pdf.js","sourcesContent":["import * as pdfjsLib from 'pdfjs-dist';\n\n/**\n * Resolve the pdf.js worker with a bundler-portable specifier. Unlike a\n * Vite-only `?url` import, `new URL(..., import.meta.url)` is understood by\n * webpack, Rollup, esbuild, and plain ESM, so the published `dist` works for\n * every consumer. A host can still override this via `setWorkerSrc`.\n */\nconst DEFAULT_WORKER_URL = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).href;\n\nlet workerConfigured = false;\nlet workerSrcOverride: string | null = null;\n\n/** Override the pdf.js worker URL (e.g. when serving it from a custom path/CDN). */\nexport function setWorkerSrc(src: string): void {\n workerSrcOverride = src;\n workerConfigured = false;\n}\n\nfunction ensureWorker(): void {\n if (workerConfigured) return;\n pdfjsLib.GlobalWorkerOptions.workerSrc = workerSrcOverride ?? DEFAULT_WORKER_URL;\n workerConfigured = true;\n}\n\nexport interface RenderResult {\n numPages: number;\n}\n\nexport async function renderPdfPage(\n bytes: Uint8Array,\n pageNumber: number,\n canvas: HTMLCanvasElement,\n): Promise<RenderResult> {\n ensureWorker();\n const view = new Uint8Array(bytes.byteLength);\n view.set(bytes);\n const loadingTask = pdfjsLib.getDocument({ data: view });\n const pdf = await loadingTask.promise;\n const page = await pdf.getPage(pageNumber);\n\n const dpr = window.devicePixelRatio || 1;\n const baseViewport = page.getViewport({ scale: 1 });\n const containerWidth = canvas.parentElement?.clientWidth ?? baseViewport.width;\n const targetWidth = Math.min(containerWidth - 32, 900);\n const scale = (targetWidth / baseViewport.width) * dpr;\n const viewport = page.getViewport({ scale });\n\n canvas.width = Math.floor(viewport.width);\n canvas.height = Math.floor(viewport.height);\n canvas.style.width = `${Math.floor(viewport.width / dpr)}px`;\n canvas.style.height = `${Math.floor(viewport.height / dpr)}px`;\n\n const ctx = canvas.getContext('2d');\n if (!ctx) {\n throw new Error('Could not acquire 2D canvas context');\n }\n await page.render({ canvasContext: ctx, viewport }).promise;\n return { numPages: pdf.numPages };\n}\n"]}
package/package.json CHANGED
@@ -1,14 +1,17 @@
1
1
  {
2
2
  "name": "@cvfile/viewer-web",
3
- "version": "0.1.0",
3
+ "version": "0.3.1",
4
4
  "description": "<cv-embed> web component and standalone viewer for the .cv open file format.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://cvfile.org",
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "https://github.com/cvfile/cv",
9
+ "url": "git+https://github.com/cvfile/cv.git",
10
10
  "directory": "packages/viewer-web"
11
11
  },
12
+ "publishConfig": {
13
+ "access": "public"
14
+ },
12
15
  "type": "module",
13
16
  "main": "./dist/index.js",
14
17
  "types": "./dist/index.d.ts",
@@ -16,6 +19,10 @@
16
19
  ".": {
17
20
  "types": "./dist/index.d.ts",
18
21
  "import": "./dist/index.js"
22
+ },
23
+ "./render-pdf": {
24
+ "types": "./dist/render-pdf.d.ts",
25
+ "import": "./dist/render-pdf.js"
19
26
  }
20
27
  },
21
28
  "files": [
@@ -28,22 +35,25 @@
28
35
  "./src/index.ts"
29
36
  ],
30
37
  "dependencies": {
38
+ "dompurify": "^3.2.0",
31
39
  "lit": "^3.2.0",
32
40
  "marked": "^14.1.0",
33
- "dompurify": "^3.2.0",
34
41
  "pdfjs-dist": "^4.7.76",
35
- "@cvfile/sdk": "0.1.0"
42
+ "@cvfile/sdk": "^0.3.1"
36
43
  },
37
44
  "devDependencies": {
38
- "@types/dompurify": "^3.0.5",
45
+ "happy-dom": "^15",
39
46
  "tsup": "^8.3.0",
40
47
  "typescript": "^5.6.0",
41
- "vite": "^5.4.0"
48
+ "vite": "^5.4.0",
49
+ "vitest": "^2.1.0"
42
50
  },
43
51
  "scripts": {
44
- "build": "tsup",
52
+ "build": "tsup && tsup --config tsup.cdn.config.ts",
45
53
  "dev": "vite",
46
54
  "preview": "vite preview",
47
- "typecheck": "tsc --noEmit"
55
+ "test": "vitest run",
56
+ "typecheck": "tsc --noEmit",
57
+ "lint": "eslint ."
48
58
  }
49
59
  }