github-code 0.1.0 → 0.2.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/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # github-code
2
+
3
+ [![npm version](https://img.shields.io/npm/v/github-code)](https://www.npmjs.com/package/github-code)
4
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/github-code)](https://bundlephobia.com/package/github-code)
5
+ [![License](https://img.shields.io/npm/l/github-code)](https://github.com/destan/github-code/blob/main/LICENSE)
6
+ [![highlight.js](https://img.shields.io/badge/highlight.js-syntax%20highlighting-blue)](https://highlightjs.org/)
7
+
8
+ A web component for displaying GitHub files with syntax highlighting.
9
+
10
+ ## Demo
11
+
12
+ See the live demo: **[github-code Demo](https://destan.github.io/github-code/)**
13
+
14
+ ## Installation
15
+
16
+ Include the script in your HTML:
17
+
18
+ ```html
19
+ <script type="module" src="https://cdn.jsdelivr.net/npm/github-code/dist/github-code.min.js"></script>
20
+ ```
21
+
22
+ Or install via npm:
23
+
24
+ ```bash
25
+ npm install github-code
26
+ ```
27
+
28
+ ## Usage
29
+
30
+ ### Single File
31
+
32
+ ```html
33
+ <github-code
34
+ file="https://github.com/owner/repo/blob/main/src/example.ts">
35
+ </github-code>
36
+ ```
37
+
38
+ ### Multiple Files (Tabbed)
39
+
40
+ Comma-separate URLs to display files as tabs:
41
+
42
+ ```html
43
+ <github-code
44
+ file="https://github.com/owner/repo/blob/main/src/app.ts,
45
+ https://github.com/owner/repo/blob/main/src/utils.ts">
46
+ </github-code>
47
+ ```
48
+
49
+ ## Attributes
50
+
51
+ | Attribute | Default | Description |
52
+ |-----------|---------|-------------|
53
+ | `file` | _(required)_ | GitHub blob URL(s). Comma-separated for multiple files. |
54
+ | `theme` | `auto` | `light`, `dark`, or `auto` (follows system preference) |
55
+
56
+ ## URL Format
57
+
58
+ URLs must be GitHub blob URLs:
59
+
60
+ ```
61
+ https://github.com/{owner}/{repo}/blob/{branch|commit}/{path}
62
+ ```
63
+
64
+ ## Features
65
+
66
+ - Syntax highlighting via highlight.js (auto-detected language)
67
+ - Lazy loading (content fetched on demand)
68
+ - Keyboard-accessible tabs (Arrow keys, Home, End)
69
+ - CSP-compliant (no inline styles)
70
+ - Shadow DOM encapsulation
71
+
72
+ ## Development
73
+
74
+ ```bash
75
+ npm install
76
+ npm run build # Build dist files
77
+ npm run dev # Watch mode
78
+ npm run test:ci # Full CI check (lint + typecheck + build + tests)
79
+ ```
80
+
81
+ ## License
82
+
83
+ MIT
@@ -75,25 +75,61 @@ async function ensureFileLoaded(file, isRetry = false) {
75
75
 
76
76
  // src/fetching/highlightjs-loader.ts
77
77
  var highlightJSLoadingPromise = null;
78
- async function loadHighlightJS() {
78
+ var loadedHighlightJSUrl = null;
79
+ var highlightJSSource = "global";
80
+ function getLoadedHighlightJSUrl() {
81
+ return loadedHighlightJSUrl || "auto";
82
+ }
83
+ function getHighlightJSSource() {
84
+ return highlightJSSource;
85
+ }
86
+ async function loadHighlightJS(customUrl) {
79
87
  if (window.hljs) {
88
+ if (!loadedHighlightJSUrl) {
89
+ highlightJSSource = "global";
90
+ loadedHighlightJSUrl = "auto";
91
+ }
80
92
  return;
81
93
  }
82
94
  if (highlightJSLoadingPromise) {
95
+ if (customUrl && loadedHighlightJSUrl && customUrl !== loadedHighlightJSUrl) {
96
+ console.warn(
97
+ `[github-code] Different highlight.js URLs detected. Already loading from "${loadedHighlightJSUrl}", but this instance requested "${customUrl}". The first URL will be used for all instances.`
98
+ );
99
+ }
83
100
  return highlightJSLoadingPromise;
84
101
  }
102
+ const DEFAULT_URL = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js";
103
+ const urlToLoad = customUrl || DEFAULT_URL;
104
+ loadedHighlightJSUrl = urlToLoad;
105
+ highlightJSSource = customUrl ? "user-provided" : "cdn-default";
85
106
  highlightJSLoadingPromise = new Promise((resolve, reject) => {
86
107
  const script = document.createElement("script");
87
- script.src = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js";
88
- script.integrity = "sha384-RH2xi4eIQ/gjtbs9fUXM68sLSi99C7ZWBRX1vDrVv6GQXRibxXLbwO2NGZB74MbU";
89
- script.crossOrigin = "anonymous";
108
+ script.src = urlToLoad;
109
+ if (!customUrl) {
110
+ script.integrity = "sha384-RH2xi4eIQ/gjtbs9fUXM68sLSi99C7ZWBRX1vDrVv6GQXRibxXLbwO2NGZB74MbU";
111
+ script.crossOrigin = "anonymous";
112
+ }
90
113
  script.onload = () => {
91
114
  highlightJSLoadingPromise = null;
115
+ if (!window.hljs) {
116
+ loadedHighlightJSUrl = null;
117
+ highlightJSSource = "global";
118
+ reject(
119
+ new Error(
120
+ `The script at "${urlToLoad}" loaded successfully but did not define window.hljs. Please ensure this URL points to a valid highlight.js library.`
121
+ )
122
+ );
123
+ return;
124
+ }
92
125
  resolve();
93
126
  };
94
127
  script.onerror = () => {
95
128
  highlightJSLoadingPromise = null;
96
- const errorMsg = "Failed to load highlight.js library. If you have a Content Security Policy (CSP), ensure it allows:\n script-src https://cdnjs.cloudflare.com\n style-src https://cdnjs.cloudflare.com";
129
+ loadedHighlightJSUrl = null;
130
+ highlightJSSource = "global";
131
+ const errorMsg = `Failed to load highlight.js library from: ${urlToLoad}
132
+ ` + (customUrl ? "Please check that the URL is correct and accessible." : "If you have a Content Security Policy (CSP), ensure it allows:\n script-src https://cdnjs.cloudflare.com\n style-src https://cdnjs.cloudflare.com");
97
133
  reject(new Error(errorMsg));
98
134
  };
99
135
  document.head.appendChild(script);
@@ -101,6 +137,79 @@ async function loadHighlightJS() {
101
137
  return highlightJSLoadingPromise;
102
138
  }
103
139
 
140
+ // package.json
141
+ var package_default = {
142
+ name: "github-code",
143
+ version: "0.1.0",
144
+ description: "Custom element for embedding GitHub source files with syntax highlighting",
145
+ type: "module",
146
+ main: "dist/github-code.min.js",
147
+ module: "dist/github-code.min.js",
148
+ exports: {
149
+ ".": {
150
+ import: "./dist/github-code.min.js",
151
+ default: "./dist/github-code.min.js"
152
+ },
153
+ "./dist/*": "./dist/*"
154
+ },
155
+ files: [
156
+ "dist"
157
+ ],
158
+ scripts: {
159
+ build: "node esbuild.config.mjs",
160
+ dev: "node esbuild.config.mjs --watch",
161
+ "type-check": "tsc --noEmit",
162
+ lint: "eslint src",
163
+ "lint:fix": "eslint src --fix",
164
+ format: 'prettier --write "src/**/*.ts"',
165
+ "format:check": 'prettier --check "src/**/*.ts"',
166
+ test: "vitest run",
167
+ "test:watch": "vitest",
168
+ "test:ui": "vitest --ui",
169
+ "test:coverage": "vitest run --coverage",
170
+ "pretest:e2e": "npm run build",
171
+ "test:e2e": "playwright test",
172
+ "test:e2e:ui": "playwright test --ui",
173
+ "test:e2e:debug": "playwright test --debug",
174
+ "test:e2e:headed": "playwright test --headed",
175
+ "test:all": "npm run test:coverage && npm run test:e2e",
176
+ "test:ci": "npm run lint && npm run type-check && npm run build && npm run test:all"
177
+ },
178
+ keywords: [
179
+ "web-component",
180
+ "github",
181
+ "code",
182
+ "syntax-highlighting"
183
+ ],
184
+ author: "",
185
+ license: "MIT",
186
+ repository: {
187
+ type: "git",
188
+ url: "git+https://github.com/destan/github-code.git"
189
+ },
190
+ devDependencies: {
191
+ "@eslint/js": "^9.39.2",
192
+ "@playwright/test": "^1.57.0",
193
+ "@semantic-release/changelog": "^6.0.3",
194
+ "@semantic-release/git": "^10.0.1",
195
+ "@types/node": "^25.0.2",
196
+ "@vitest/coverage-v8": "^4.0.15",
197
+ "@vitest/ui": "^4.0.15",
198
+ esbuild: "0.27.1",
199
+ eslint: "^9.39.2",
200
+ "eslint-config-prettier": "^10.1.8",
201
+ "happy-dom": "^20.0.11",
202
+ prettier: "^3.7.4",
203
+ "semantic-release": "^24.2.9",
204
+ typescript: "5.9.3",
205
+ "typescript-eslint": "^8.50.0",
206
+ vitest: "^4.0.15"
207
+ },
208
+ engines: {
209
+ node: ">=24.12.0"
210
+ }
211
+ };
212
+
104
213
  // src/styles/base-light.css
105
214
  var base_light_default = ":host {\n --border-color: #d0d7de;\n --header-background: #f6f8fa;\n --header-text-color: #24292f;\n --line-number-color: #57606a;\n --tab-color: #57606a;\n --tab-hover-border: #d0d7de;\n --tabs-background: #f6f8fa;\n --code-background: #ffffff;\n --skeleton-base: #e0e0e0;\n --skeleton-highlight: #f0f0f0;\n --error-text-color: #cf222e;\n --error-background: #ffebe9;\n --error-border: #ff8182;\n --button-background: #f6f8fa;\n --button-text-color: #24292f;\n --button-border: #d0d7de;\n --button-hover-background: #f3f4f6;\n --font-family-base:\n -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif, 'Apple Color Emoji',\n 'Segoe UI Emoji';\n --font-family-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;\n\n display: block;\n min-width: 0;\n font-family: var(--font-family-base);\n}\n\narticle {\n border: 1px solid var(--border-color);\n border-radius: 6px;\n overflow: hidden;\n background-color: var(--code-background);\n}\n\nheader {\n background-color: var(--header-background);\n padding: 8px 16px;\n border-bottom: 1px solid var(--border-color);\n color: var(--header-text-color);\n font-weight: 600;\n font-size: 14px;\n}\n\n.code-wrapper {\n width: 100%;\n margin: 0;\n overflow-x: auto;\n background-color: var(--code-background);\n}\n\n.code-table {\n display: flex;\n flex-direction: column;\n width: 100%;\n min-width: 0;\n}\n\n.code-row {\n display: flex;\n}\n\n.line-number {\n flex-shrink: 0;\n text-align: right;\n padding-right: 6px;\n padding-left: 8px;\n color: var(--line-number-color);\n user-select: none;\n border-right: 1px solid var(--border-color);\n font-family: var(--font-family-mono);\n font-size: 12px;\n line-height: 20px;\n min-width: 40px;\n}\n\n.code-cell {\n flex: 1;\n min-width: 0;\n padding-left: 2px;\n font-family: var(--font-family-mono);\n font-size: 12px;\n line-height: 20px;\n white-space: pre;\n}\n\n.error {\n padding: 16px;\n color: var(--error-text-color);\n background-color: var(--error-background);\n border: 1px solid var(--error-border);\n border-radius: 6px;\n font-family: var(--font-family-mono);\n font-size: 12px;\n line-height: 1.5;\n margin: 16px;\n}\n\n.retry-button {\n margin-top: 12px;\n padding: 8px 16px;\n background: var(--button-background);\n color: var(--button-text-color);\n border: 1px solid var(--button-border);\n border-radius: 6px;\n cursor: pointer;\n font-size: 14px;\n font-family: var(--font-family-base);\n font-weight: 500;\n transition: background-color 0.2s ease;\n}\n\n.retry-button:hover {\n background-color: var(--button-hover-background);\n}\n\n/* Skeleton loader styles */\n.skeleton-line {\n height: 12px;\n background: linear-gradient(90deg, var(--skeleton-base) 25%, var(--skeleton-highlight) 50%, var(--skeleton-base) 75%);\n background-size: 200% 100%;\n animation: loading 1.5s ease-in-out infinite;\n border-radius: 4px;\n margin: 4px 0;\n}\n\n@keyframes loading {\n 0% {\n background-position: 200% 0;\n }\n 100% {\n background-position: -200% 0;\n }\n}\n\n.skeleton-loading .line-number {\n opacity: 0.3;\n}\n";
106
215
 
@@ -423,7 +532,17 @@ var GitHubCode = class extends HTMLElement {
423
532
  return file;
424
533
  }
425
534
  static get observedAttributes() {
426
- return ["file"];
535
+ return ["file", "theme"];
536
+ }
537
+ /**
538
+ * Runtime information about the component and loaded libraries
539
+ */
540
+ static get info() {
541
+ return {
542
+ version: package_default.version,
543
+ highlightjsUrl: getLoadedHighlightJSUrl(),
544
+ highlightjsSource: getHighlightJSSource()
545
+ };
427
546
  }
428
547
  connectedCallback() {
429
548
  this.#setupThemeListener();
@@ -435,7 +554,10 @@ var GitHubCode = class extends HTMLElement {
435
554
  }
436
555
  }
437
556
  attributeChangedCallback(name, oldValue, newValue) {
438
- if (name === "file" && oldValue !== null && oldValue !== newValue) {
557
+ if (oldValue === null || oldValue === newValue) {
558
+ return;
559
+ }
560
+ if (name === "file") {
439
561
  const previousIndex = this.#tabState.getActiveTabIndex();
440
562
  this.#tabState.reset();
441
563
  void this.#render();
@@ -444,6 +566,9 @@ var GitHubCode = class extends HTMLElement {
444
566
  } else {
445
567
  this.#tabState.setActiveTabIndex(0);
446
568
  }
569
+ } else if (name === "theme") {
570
+ this.#resolvedTheme = null;
571
+ void this.#render();
447
572
  }
448
573
  }
449
574
  // Private methods - theming
@@ -535,7 +660,9 @@ var GitHubCode = class extends HTMLElement {
535
660
  this.#renderTabsSkeletonStructure();
536
661
  }
537
662
  try {
538
- await loadHighlightJS();
663
+ const customUrlAttr = this.getAttribute("highlightjs-url");
664
+ const customUrl = customUrlAttr && customUrlAttr !== "auto" ? customUrlAttr : void 0;
665
+ await loadHighlightJS(customUrl);
539
666
  } catch (error) {
540
667
  const errorMsg = error instanceof Error ? error.message : String(error);
541
668
  this.#showError(`Error loading highlight.js: ${errorMsg}`);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/parsers/file-parser.ts", "../src/parsers/url-parser.ts", "../src/fetching/code-fetcher.ts", "../src/fetching/highlightjs-loader.ts", "../src/styles/base-light.css", "../src/styles/base-dark.css", "../src/styles/tab.css", "../src/styles/stylesheet-manager.ts", "../src/rendering/html-generators.ts", "../src/rendering/template-generators.ts", "../src/rendering/syntax-highlighter.ts", "../src/theme/theme-resolver.ts", "../src/tabs/tab-state.ts", "../src/tabs/tab-controller.ts", "../src/github-code.ts", "../src/index.ts"],
4
- "sourcesContent": ["/**\n * Parses the comma-separated file attribute into individual URLs\n */\nexport function parseFileAttribute(fileAttr: string): string[] {\n return fileAttr\n .split(',')\n .map((url) => url.trim())\n .filter((url) => url.length > 0);\n}\n", "import type { GitHubUrlParts } from '../types';\n\n/**\n * Regex pattern to validate GitHub blob URLs\n */\n// eslint-disable-next-line no-useless-escape\nconst GITHUB_BLOB_PATTERN = /^https:\\/\\/github\\.com\\/[^\\/]+\\/[^\\/]+\\/blob\\/.+/;\n\n/**\n * Checks if a URL is a valid GitHub blob URL\n */\nexport function isValidGitHubUrl(url: string): boolean {\n return GITHUB_BLOB_PATTERN.test(url);\n}\n\n/**\n * Parses a GitHub blob URL into its components\n * @throws {Error} If the URL cannot be parsed\n */\nexport function parseGitHubUrl(url: string): GitHubUrlParts {\n // eslint-disable-next-line no-useless-escape\n const match = /^https:\\/\\/github\\.com\\/([^\\/]+)\\/([^\\/]+)\\/blob\\/([^\\/]+)\\/(.+)$/.exec(url);\n\n if (!match) {\n throw new Error('Failed to parse GitHub URL');\n }\n\n const owner = match[1];\n const repo = match[2];\n const commit = match[3];\n const path = match[4];\n\n if (!owner || !repo || !commit || !path) {\n throw new Error('Failed to parse GitHub URL');\n }\n\n const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${commit}/${path}`;\n\n const filename = path.split('/').pop() || 'unknown';\n\n return {\n rawUrl,\n filename,\n };\n}\n\n/**\n * Extracts filename from any URL (fallback for non-standard URLs)\n */\nexport function extractFilenameFromUrl(url: string): string {\n try {\n // eslint-disable-next-line no-useless-escape\n const match = /\\/([^\\/]+)$/.exec(url);\n return match?.[1] ?? 'unknown';\n } catch {\n return 'unknown';\n }\n}\n", "import type { FileMetadata } from '../types';\n\n/**\n * Fetches code content from a URL\n * @throws {Error} If the fetch fails (network error, HTTP error, CORS error)\n */\nexport async function fetchCode(url: string): Promise<string> {\n try {\n const response = await fetch(url);\n\n if (!response.ok) {\n throw new Error(`Failed to fetch code (HTTP ${response.status}). Please check if the URL is accessible.`);\n }\n\n return await response.text();\n } catch (error) {\n // Detect CORS errors specifically\n if (error instanceof TypeError && error.message.includes('fetch')) {\n throw new Error(\n `Failed to fetch code from ${url}. ` +\n `This is likely a CORS (Cross-Origin Resource Sharing) error. ` +\n `The server needs to allow requests from this origin. ` +\n `GitHub's raw.githubusercontent.com should work without CORS issues.`\n );\n }\n throw error;\n }\n}\n\n/**\n * Ensures a file's content is loaded, fetching it if necessary\n * @param file The file metadata object to load\n * @param isRetry Whether this is a retry attempt (will force re-fetch)\n */\nexport async function ensureFileLoaded(file: FileMetadata, isRetry = false): Promise<void> {\n // Already loaded (skip if not a retry)\n if (file.loaded && !isRetry) {\n return;\n }\n\n // If retrying, reset the loaded flag to allow re-fetch\n if (isRetry) {\n file.loaded = false;\n file.error = null;\n }\n\n try {\n const code = await fetchCode(file.rawUrl);\n file.code = code;\n file.error = null;\n file.loaded = true;\n } catch (error) {\n file.code = null;\n file.error = error instanceof Error ? error.message : String(error);\n file.loaded = true; // Mark as loaded so we show error (can retry later)\n }\n}\n", "/**\n * Shared promise to prevent multiple simultaneous loads of highlight.js\n */\nlet highlightJSLoadingPromise: Promise<void> | null = null;\n\n/**\n * Type definition for highlight.js on window object\n */\ndeclare global {\n interface Window {\n hljs?: {\n highlightAuto: (code: string) => { value: string };\n };\n }\n}\n\n/**\n * Loads highlight.js library from CDN if not already loaded.\n * Uses a singleton pattern to ensure only one load attempt happens at a time.\n */\nexport async function loadHighlightJS(): Promise<void> {\n // If already loaded, return immediately\n if (window.hljs) {\n return;\n }\n\n // If currently loading, return the existing promise\n if (highlightJSLoadingPromise) {\n return highlightJSLoadingPromise;\n }\n\n // Create new loading promise\n highlightJSLoadingPromise = new Promise((resolve, reject) => {\n const script = document.createElement('script');\n script.src = 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js';\n script.integrity = 'sha384-RH2xi4eIQ/gjtbs9fUXM68sLSi99C7ZWBRX1vDrVv6GQXRibxXLbwO2NGZB74MbU';\n script.crossOrigin = 'anonymous';\n script.onload = () => {\n highlightJSLoadingPromise = null; // Clear after successful load\n resolve();\n };\n script.onerror = () => {\n highlightJSLoadingPromise = null; // Clear on error to allow retry\n const errorMsg =\n 'Failed to load highlight.js library. ' +\n 'If you have a Content Security Policy (CSP), ensure it allows:\\n' +\n ' script-src https://cdnjs.cloudflare.com\\n' +\n ' style-src https://cdnjs.cloudflare.com';\n reject(new Error(errorMsg));\n };\n document.head.appendChild(script);\n });\n\n return highlightJSLoadingPromise;\n}\n", "export default \":host {\\n --border-color: #d0d7de;\\n --header-background: #f6f8fa;\\n --header-text-color: #24292f;\\n --line-number-color: #57606a;\\n --tab-color: #57606a;\\n --tab-hover-border: #d0d7de;\\n --tabs-background: #f6f8fa;\\n --code-background: #ffffff;\\n --skeleton-base: #e0e0e0;\\n --skeleton-highlight: #f0f0f0;\\n --error-text-color: #cf222e;\\n --error-background: #ffebe9;\\n --error-border: #ff8182;\\n --button-background: #f6f8fa;\\n --button-text-color: #24292f;\\n --button-border: #d0d7de;\\n --button-hover-background: #f3f4f6;\\n --font-family-base:\\n -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif, 'Apple Color Emoji',\\n 'Segoe UI Emoji';\\n --font-family-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;\\n\\n display: block;\\n min-width: 0;\\n font-family: var(--font-family-base);\\n}\\n\\narticle {\\n border: 1px solid var(--border-color);\\n border-radius: 6px;\\n overflow: hidden;\\n background-color: var(--code-background);\\n}\\n\\nheader {\\n background-color: var(--header-background);\\n padding: 8px 16px;\\n border-bottom: 1px solid var(--border-color);\\n color: var(--header-text-color);\\n font-weight: 600;\\n font-size: 14px;\\n}\\n\\n.code-wrapper {\\n width: 100%;\\n margin: 0;\\n overflow-x: auto;\\n background-color: var(--code-background);\\n}\\n\\n.code-table {\\n display: flex;\\n flex-direction: column;\\n width: 100%;\\n min-width: 0;\\n}\\n\\n.code-row {\\n display: flex;\\n}\\n\\n.line-number {\\n flex-shrink: 0;\\n text-align: right;\\n padding-right: 6px;\\n padding-left: 8px;\\n color: var(--line-number-color);\\n user-select: none;\\n border-right: 1px solid var(--border-color);\\n font-family: var(--font-family-mono);\\n font-size: 12px;\\n line-height: 20px;\\n min-width: 40px;\\n}\\n\\n.code-cell {\\n flex: 1;\\n min-width: 0;\\n padding-left: 2px;\\n font-family: var(--font-family-mono);\\n font-size: 12px;\\n line-height: 20px;\\n white-space: pre;\\n}\\n\\n.error {\\n padding: 16px;\\n color: var(--error-text-color);\\n background-color: var(--error-background);\\n border: 1px solid var(--error-border);\\n border-radius: 6px;\\n font-family: var(--font-family-mono);\\n font-size: 12px;\\n line-height: 1.5;\\n margin: 16px;\\n}\\n\\n.retry-button {\\n margin-top: 12px;\\n padding: 8px 16px;\\n background: var(--button-background);\\n color: var(--button-text-color);\\n border: 1px solid var(--button-border);\\n border-radius: 6px;\\n cursor: pointer;\\n font-size: 14px;\\n font-family: var(--font-family-base);\\n font-weight: 500;\\n transition: background-color 0.2s ease;\\n}\\n\\n.retry-button:hover {\\n background-color: var(--button-hover-background);\\n}\\n\\n/* Skeleton loader styles */\\n.skeleton-line {\\n height: 12px;\\n background: linear-gradient(90deg, var(--skeleton-base) 25%, var(--skeleton-highlight) 50%, var(--skeleton-base) 75%);\\n background-size: 200% 100%;\\n animation: loading 1.5s ease-in-out infinite;\\n border-radius: 4px;\\n margin: 4px 0;\\n}\\n\\n@keyframes loading {\\n 0% {\\n background-position: 200% 0;\\n }\\n 100% {\\n background-position: -200% 0;\\n }\\n}\\n\\n.skeleton-loading .line-number {\\n opacity: 0.3;\\n}\\n\"", "export default \":host {\\n --border-color: #30363d;\\n --header-background: #161b22;\\n --header-text-color: #c9d1d9;\\n --line-number-color: #8b949e;\\n --tab-color: #8b949e;\\n --tab-hover-border: #30363d;\\n --tabs-background: #161b22;\\n --code-background: #0d1117;\\n --skeleton-base: #21262d;\\n --skeleton-highlight: #30363d;\\n --error-text-color: #ff7b72;\\n --error-background: #490202;\\n --error-border: #f85149;\\n --button-background: #21262d;\\n --button-text-color: #c9d1d9;\\n --button-border: #30363d;\\n --button-hover-background: #30363d;\\n --font-family-base:\\n -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif, 'Apple Color Emoji',\\n 'Segoe UI Emoji';\\n --font-family-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;\\n\\n display: block;\\n min-width: 0;\\n font-family: var(--font-family-base);\\n}\\n\\narticle {\\n border: 1px solid var(--border-color);\\n border-radius: 6px;\\n overflow: hidden;\\n background-color: var(--code-background);\\n}\\n\\nheader {\\n background-color: var(--header-background);\\n padding: 8px 16px;\\n border-bottom: 1px solid var(--border-color);\\n color: var(--header-text-color);\\n font-weight: 600;\\n font-size: 14px;\\n}\\n\\n.code-wrapper {\\n width: 100%;\\n margin: 0;\\n overflow-x: auto;\\n background-color: var(--code-background);\\n}\\n\\n.code-table {\\n display: flex;\\n flex-direction: column;\\n width: 100%;\\n min-width: 0;\\n}\\n\\n.code-row {\\n display: flex;\\n}\\n\\n.line-number {\\n flex-shrink: 0;\\n text-align: right;\\n padding-right: 6px;\\n padding-left: 8px;\\n color: var(--line-number-color);\\n user-select: none;\\n border-right: 1px solid var(--border-color);\\n font-family: var(--font-family-mono);\\n font-size: 12px;\\n line-height: 20px;\\n min-width: 40px;\\n}\\n\\n.code-cell {\\n flex: 1;\\n min-width: 0;\\n padding-left: 2px;\\n font-family: var(--font-family-mono);\\n font-size: 12px;\\n line-height: 20px;\\n white-space: pre;\\n}\\n\\n.error {\\n padding: 16px;\\n color: var(--error-text-color);\\n background-color: var(--error-background);\\n border: 1px solid var(--error-border);\\n border-radius: 6px;\\n font-family: var(--font-family-mono);\\n font-size: 12px;\\n line-height: 1.5;\\n margin: 16px;\\n}\\n\\n.retry-button {\\n margin-top: 12px;\\n padding: 8px 16px;\\n background: var(--button-background);\\n color: var(--button-text-color);\\n border: 1px solid var(--button-border);\\n border-radius: 6px;\\n cursor: pointer;\\n font-size: 14px;\\n font-family: var(--font-family-base);\\n font-weight: 500;\\n transition: background-color 0.2s ease;\\n}\\n\\n.retry-button:hover {\\n background-color: var(--button-hover-background);\\n}\\n\\n/* Skeleton loader styles */\\n.skeleton-line {\\n height: 12px;\\n background: linear-gradient(90deg, var(--skeleton-base) 25%, var(--skeleton-highlight) 50%, var(--skeleton-base) 75%);\\n background-size: 200% 100%;\\n animation: loading 1.5s ease-in-out infinite;\\n border-radius: 4px;\\n margin: 4px 0;\\n}\\n\\n@keyframes loading {\\n 0% {\\n background-position: 200% 0;\\n }\\n 100% {\\n background-position: -200% 0;\\n }\\n}\\n\\n.skeleton-loading .line-number {\\n opacity: 0.3;\\n}\\n\"", "export default \"nav[role='tablist'] {\\n display: flex;\\n background-color: var(--tabs-background);\\n border-bottom: 1px solid var(--border-color);\\n overflow-x: auto;\\n}\\n\\nnav > button {\\n padding: 8px 16px;\\n background: none;\\n border: none;\\n border-bottom: 2px solid transparent;\\n color: var(--tab-color);\\n font-size: 14px;\\n font-family: var(--font-family-base);\\n cursor: pointer;\\n white-space: nowrap;\\n transition:\\n color 0.1s ease,\\n border-color 0.1s ease;\\n}\\n\\nnav > button:hover {\\n color: var(--header-text-color);\\n border-bottom-color: var(--tab-hover-border);\\n}\\n\\nnav > button[aria-selected='true'] {\\n color: var(--header-text-color);\\n border-bottom-color: #fd8c73;\\n font-weight: 600;\\n}\\n\\nsection[role='tabpanel'] {\\n display: block;\\n}\\n\\nsection[role='tabpanel'][aria-hidden='true'] {\\n display: none;\\n}\\n\"", "import baseLightCss from './base-light.css';\nimport baseDarkCss from './base-dark.css';\nimport tabCss from './tab.css';\nimport type { ResolvedTheme } from '../types';\n\n/**\n * Manages constructable stylesheets for the component.\n * Implements caching to share stylesheet instances across all component instances.\n */\nexport class StylesheetManager {\n private static baseStylesLight: CSSStyleSheet | null = null;\n private static baseStylesDark: CSSStyleSheet | null = null;\n private static tabStyles: CSSStyleSheet | null = null;\n\n /**\n * Gets the base stylesheet for the specified theme (light or dark).\n * Lazy-loads and caches the stylesheet on first access.\n */\n static getBaseStyleSheet(theme: ResolvedTheme): CSSStyleSheet {\n if (theme === 'dark') {\n if (!this.baseStylesDark) {\n this.baseStylesDark = new CSSStyleSheet();\n this.baseStylesDark.replaceSync(baseDarkCss);\n }\n return this.baseStylesDark;\n } else {\n if (!this.baseStylesLight) {\n this.baseStylesLight = new CSSStyleSheet();\n this.baseStylesLight.replaceSync(baseLightCss);\n }\n return this.baseStylesLight;\n }\n }\n\n /**\n * Gets the tab stylesheet.\n * Lazy-loads and caches the stylesheet on first access.\n */\n static getTabStyleSheet(): CSSStyleSheet {\n if (!this.tabStyles) {\n this.tabStyles = new CSSStyleSheet();\n this.tabStyles.replaceSync(tabCss);\n }\n return this.tabStyles;\n }\n\n /**\n * Gets the highlight.js theme URL for the specified theme.\n */\n static getHighlightJSThemeUrl(theme: ResolvedTheme): string {\n const themeFile = theme === 'dark' ? 'github-dark.min.css' : 'github.min.css';\n return `https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/${themeFile}`;\n }\n}\n", "/**\n * Escapes HTML special characters to prevent XSS\n */\nexport function escapeHtml(text: string): string {\n const div = document.createElement('div');\n div.textContent = text;\n return div.innerHTML;\n}\n\n/**\n * Generates HTML for error display\n */\nexport function getErrorContentHtml(errorMessage: string, showRetry = false): string {\n const retryButton = showRetry ? `<button class=\"retry-button\">Retry</button>` : '';\n return `<div class=\"error\">${escapeHtml(errorMessage)}${retryButton}</div>`;\n}\n\n/**\n * Generates HTML for skeleton loading state\n */\nexport function getSkeletonContentHtml(): string {\n // Generate skeleton lines that look like code\n const skeletonLines = Array.from({ length: 20 }, (_, i) => {\n // Random width between 60-90%\n // Using Math.random() is acceptable here for non-cryptographic purposes\n const width = 60 + Math.random() * 30; // NOSONAR\n return `\n <div class=\"code-row\">\n <div class=\"line-number\">${i + 1}</div>\n <div class=\"skeleton-line\" style=\"width: ${width}%\"></div>\n </div>\n `;\n }).join('');\n\n return `\n <div class=\"code-wrapper skeleton-loading\">\n <div class=\"code-table\">\n ${skeletonLines}\n </div>\n </div>\n `;\n}\n\n/**\n * Generates HTML for code display (without syntax highlighting)\n */\nexport function getCodeContentHtml(code: string | null): string {\n // Safety check for null/undefined code\n if (!code) {\n return getErrorContentHtml('No code content available');\n }\n\n const lines = code.split('\\n');\n\n // Remove only the last empty line if it exists (from final newline)\n if (lines.length > 0 && lines[lines.length - 1] === '') {\n lines.pop();\n }\n\n return `\n <div class=\"code-wrapper hljs\">\n <div class=\"code-table\">\n ${lines\n .map(\n (line, index) => `\n <div class=\"code-row\">\n <div class=\"line-number\">${index + 1}</div>\n <div class=\"code-cell\">${escapeHtml(line) || ' '}</div>\n </div>\n `\n )\n .join('')}\n </div>\n </div>\n `;\n}\n", "import { escapeHtml } from './html-generators';\n\n/**\n * Generates the outer HTML structure for a single file display.\n *\n * @param filename - The filename to show in the header\n * @param content - The inner content HTML (skeleton, code, or error)\n * @param themeStylesheetUrl - URL for the highlight.js theme CSS\n * @returns Complete HTML string for single file layout\n */\nexport function generateSingleFileTemplate(filename: string, content: string, themeStylesheetUrl: string): string {\n return `<link rel=\"stylesheet\" href=\"${themeStylesheetUrl}\">\n<article>\n <header>${escapeHtml(filename)}</header>\n ${content}\n</article>`;\n}\n\n/**\n * Generates the article content for a single file (header + content).\n * Used when updating only the article innards, not the full template.\n *\n * @param filename - The filename to show in the header\n * @param content - The inner content HTML\n * @returns HTML string for article content\n */\nexport function generateArticleContent(filename: string, content: string): string {\n return `<header>${escapeHtml(filename)}</header>\n ${content}`;\n}\n\n/**\n * Generates the outer HTML structure for tabbed file display.\n *\n * @param tabsHtml - Pre-generated HTML for tab buttons\n * @param activeIndex - Index of the currently active tab\n * @param panelContent - Content HTML for the active panel\n * @param themeStylesheetUrl - URL for the highlight.js theme CSS\n * @returns Complete HTML string for tabbed layout\n */\nexport function generateTabbedTemplate(\n tabsHtml: string,\n activeIndex: number,\n panelContent: string,\n themeStylesheetUrl: string\n): string {\n return `<link rel=\"stylesheet\" href=\"${themeStylesheetUrl}\">\n<article>\n <nav role=\"tablist\" aria-label=\"Code files\">${tabsHtml}</nav>\n <section role=\"tabpanel\"\n id=\"panel-${activeIndex}\"\n aria-labelledby=\"tab-${activeIndex}\"\n data-index=\"${activeIndex}\">\n ${panelContent}\n </section>\n</article>`;\n}\n", "/**\n * Gets syntax-highlighted lines from code using highlight.js\n */\nexport function getHighlightedLines(code: string | null): string[] {\n // Safety check for null/undefined code\n if (!code) {\n return [''];\n }\n\n const lines = code.split('\\n');\n\n // Remove only the last empty line if it exists (from final newline)\n if (lines.length > 0 && lines[lines.length - 1] === '') {\n lines.pop();\n }\n\n const fullCode = lines.join('\\n');\n const result = window.hljs!.highlightAuto(fullCode);\n const highlightedLines = result.value.split('\\n');\n\n // Ensure highlightedLines matches lines length\n while (highlightedLines.length > lines.length) {\n highlightedLines.pop();\n }\n\n return highlightedLines;\n}\n\n/**\n * Applies syntax highlighting to code cells within a container\n */\nexport function applySyntaxHighlighting(container: Element | ShadowRoot, code: string): void {\n const codeCells = container.querySelectorAll('.code-cell');\n\n const applyHighlighting = (): void => {\n const highlightedLines = getHighlightedLines(code);\n codeCells.forEach((cell, index) => {\n (cell as HTMLElement).innerHTML = highlightedLines[index] || ' ';\n });\n };\n\n // Use requestIdleCallback to avoid blocking the main thread\n if ('requestIdleCallback' in window) {\n requestIdleCallback(applyHighlighting);\n } else {\n applyHighlighting();\n }\n}\n", "import type { Theme, ResolvedTheme } from '../types';\n\n/**\n * Resolves a theme attribute to an actual theme value.\n * Handles 'auto' theme by detecting system preference.\n *\n * @param themeAttr - The theme attribute value ('light', 'dark', or 'auto')\n * @returns The resolved theme ('light' or 'dark')\n */\nexport function resolveTheme(themeAttr: Theme): ResolvedTheme {\n if (themeAttr === 'dark') {\n return 'dark';\n }\n\n if (themeAttr === 'light') {\n return 'light';\n }\n\n // auto - detect system preference\n const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;\n return prefersDark ? 'dark' : 'light';\n}\n\n/**\n * Gets the theme attribute from an element, defaulting to 'auto'.\n *\n * @param element - The HTML element to get the theme from\n * @returns The theme value\n */\nexport function getThemeAttribute(element: HTMLElement): Theme {\n const attr = element.getAttribute('theme');\n if (attr === 'dark' || attr === 'light') {\n return attr;\n }\n return 'auto';\n}\n\n/**\n * Creates a theme change handler that clears cached theme and triggers re-render.\n *\n * @param onThemeChange - Callback to invoke when theme changes\n * @returns Object with setup and cleanup functions for theme media query listener\n */\nexport function createThemeListener(onThemeChange: () => void): {\n setup: () => MediaQueryList | null;\n cleanup: (mediaQuery: MediaQueryList | null) => void;\n handler: () => void;\n} {\n const handler = onThemeChange;\n\n return {\n setup: () => {\n if (window.matchMedia) {\n const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');\n mediaQuery.addEventListener('change', handler);\n return mediaQuery;\n }\n return null;\n },\n cleanup: (mediaQuery: MediaQueryList | null) => {\n if (mediaQuery) {\n mediaQuery.removeEventListener('change', handler);\n }\n },\n handler,\n };\n}\n", "/**\n * Manages tab state for the component\n */\nexport class TabState {\n private activeTabIndex = 0;\n private renderedTabs = new Set<number>();\n private tabsFullyRendered = false;\n\n /**\n * Gets the currently active tab index\n */\n getActiveTabIndex(): number {\n return this.activeTabIndex;\n }\n\n /**\n * Sets the active tab index\n */\n setActiveTabIndex(index: number): void {\n this.activeTabIndex = index;\n }\n\n /**\n * Checks if tabs structure is fully rendered with event listeners\n */\n areTabsFullyRendered(): boolean {\n return this.tabsFullyRendered;\n }\n\n /**\n * Marks tabs as fully rendered\n */\n setTabsFullyRendered(value: boolean): void {\n this.tabsFullyRendered = value;\n }\n\n /**\n * Checks if a tab has been rendered\n */\n isTabRendered(index: number): boolean {\n return this.renderedTabs.has(index);\n }\n\n /**\n * Marks a tab as rendered\n */\n markTabAsRendered(index: number): void {\n this.renderedTabs.add(index);\n }\n\n /**\n * Clears all rendered tabs\n */\n clearRenderedTabs(): void {\n this.renderedTabs.clear();\n }\n\n /**\n * Gets the total number of tabs\n */\n getTabCount(files: unknown[]): number {\n return files.length;\n }\n\n /**\n * Resets tab state (useful when re-parsing files)\n */\n reset(): void {\n this.renderedTabs.clear();\n this.tabsFullyRendered = false;\n // Note: activeTabIndex is NOT reset here - that's handled by the caller\n }\n}\n", "import type { FileMetadata } from '../types';\nimport { escapeHtml } from '../rendering/html-generators';\n\n/**\n * Generates HTML for tab buttons\n */\nexport function generateTabsHtml(files: FileMetadata[], activeTabIndex: number): string {\n return files\n .map(\n (file, index) => `\n <button role=\"tab\"\n id=\"tab-${index}\"\n aria-selected=\"${index === activeTabIndex ? 'true' : 'false'}\"\n aria-controls=\"panel-${index}\"\n tabindex=\"${index === activeTabIndex ? '0' : '-1'}\"\n data-index=\"${index}\">\n ${escapeHtml(file.filename)}\n </button>\n `\n )\n .join('');\n}\n\n/**\n * Updates tab button states (aria attributes) when switching tabs\n */\nexport function updateTabButtonStates(shadowRoot: ShadowRoot, newActiveIndex: number): void {\n const tabs = shadowRoot.querySelectorAll('nav > button');\n tabs.forEach((tab) => {\n const tabIndex = parseInt((tab as HTMLElement).dataset.index || '0');\n const isSelected = tabIndex === newActiveIndex;\n tab.setAttribute('aria-selected', isSelected ? 'true' : 'false');\n tab.setAttribute('tabindex', isSelected ? '0' : '-1');\n });\n}\n\n/**\n * Updates tab panel visibility states when switching tabs\n */\nexport function updateTabPanelStates(shadowRoot: ShadowRoot, activeIndex: number): void {\n const allPanels = shadowRoot.querySelectorAll('section[role=\"tabpanel\"]');\n allPanels.forEach((panel) => {\n const panelIndex = parseInt((panel as HTMLElement).dataset.index || '0');\n panel.setAttribute('aria-hidden', panelIndex !== activeIndex ? 'true' : 'false');\n });\n}\n\n/**\n * Creates a new tab panel element for lazy loading\n */\nexport function createTabPanel(index: number, skeletonHtml: string): HTMLElement {\n const contentElement = document.createElement('section');\n contentElement.setAttribute('role', 'tabpanel');\n contentElement.setAttribute('id', `panel-${index}`);\n contentElement.setAttribute('aria-labelledby', `tab-${index}`);\n contentElement.dataset.index = String(index);\n contentElement.innerHTML = skeletonHtml;\n return contentElement;\n}\n\n/**\n * Handles keyboard navigation for tabs (Arrow keys, Home, End)\n * Returns the new index if navigation should occur, null otherwise\n */\nexport function handleTabKeyNavigation(key: string, currentIndex: number, maxIndex: number): number | null {\n switch (key) {\n case 'ArrowLeft':\n return currentIndex > 0 ? currentIndex - 1 : maxIndex;\n case 'ArrowRight':\n return currentIndex < maxIndex ? currentIndex + 1 : 0;\n case 'Home':\n return 0;\n case 'End':\n return maxIndex;\n default:\n return null;\n }\n}\n", "import type { FileMetadata, ResolvedTheme } from './types';\nimport { parseFileAttribute } from './parsers/file-parser';\nimport { isValidGitHubUrl, parseGitHubUrl, extractFilenameFromUrl } from './parsers/url-parser';\nimport { ensureFileLoaded } from './fetching/code-fetcher';\nimport { loadHighlightJS } from './fetching/highlightjs-loader';\nimport { StylesheetManager } from './styles/stylesheet-manager';\nimport {\n escapeHtml,\n getErrorContentHtml,\n getSkeletonContentHtml,\n getCodeContentHtml,\n} from './rendering/html-generators';\nimport {\n generateSingleFileTemplate,\n generateArticleContent,\n generateTabbedTemplate,\n} from './rendering/template-generators';\nimport { applySyntaxHighlighting } from './rendering/syntax-highlighter';\nimport { resolveTheme, getThemeAttribute } from './theme/theme-resolver';\nimport { TabState } from './tabs/tab-state';\nimport {\n generateTabsHtml,\n updateTabButtonStates,\n updateTabPanelStates,\n createTabPanel,\n handleTabKeyNavigation,\n} from './tabs/tab-controller';\n\n/**\n * GitHub Code Web Component\n * Displays GitHub file URLs with syntax highlighting\n */\nexport class GitHubCode extends HTMLElement {\n // Private fields\n #files: FileMetadata[] = [];\n #tabState = new TabState();\n #resolvedTheme: ResolvedTheme | null = null;\n #themeMediaQuery: MediaQueryList | null = null;\n #themeChangeHandler: (() => void) | null = null;\n\n constructor() {\n super();\n this.attachShadow({ mode: 'open' });\n }\n\n /**\n * Safely gets a file by index, throwing if out of bounds.\n * This provides type-safe array access with noUncheckedIndexedAccess.\n */\n #getFile(index: number): FileMetadata {\n const file = this.#files[index];\n if (!file) {\n throw new Error(`File at index ${index} not found`);\n }\n return file;\n }\n\n static get observedAttributes(): string[] {\n return ['file'];\n }\n\n connectedCallback(): void {\n // Set up theme change listener for reactive theme updates\n this.#setupThemeListener();\n\n // Initial render (will parse files and display)\n void this.#render();\n }\n\n disconnectedCallback(): void {\n // Clean up theme change listener to prevent memory leaks\n if (this.#themeMediaQuery && this.#themeChangeHandler) {\n this.#themeMediaQuery.removeEventListener('change', this.#themeChangeHandler);\n }\n }\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n // Skip initial attribute set (oldValue is null) - connectedCallback handles initial render\n if (name === 'file' && oldValue !== null && oldValue !== newValue) {\n // Store current active tab index before re-parsing files\n const previousIndex = this.#tabState.getActiveTabIndex();\n\n // Re-parse files (clear renderedTabs since structure will be rebuilt)\n this.#tabState.reset();\n\n // Re-render with new file URLs\n void this.#render();\n\n // Try to preserve tab index if it still exists\n if (previousIndex < this.#files.length) {\n this.#tabState.setActiveTabIndex(previousIndex);\n } else {\n // Reset to first tab if previous index no longer exists\n this.#tabState.setActiveTabIndex(0);\n }\n }\n }\n\n // Private methods - theming\n #setupThemeListener(): void {\n if (window.matchMedia) {\n this.#themeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');\n this.#themeChangeHandler = () => {\n this.#resolvedTheme = null; // Clear cached theme\n void this.#render(); // Re-render with new theme\n };\n this.#themeMediaQuery.addEventListener('change', this.#themeChangeHandler);\n }\n }\n\n #getResolvedTheme(): ResolvedTheme {\n if (this.#resolvedTheme) {\n return this.#resolvedTheme;\n }\n\n this.#resolvedTheme = resolveTheme(getThemeAttribute(this));\n return this.#resolvedTheme;\n }\n\n // Apply constructable stylesheets to shadow root (CSP-compliant, no 'unsafe-inline' needed)\n #applyStyleSheets(includeTabStyles = false): void {\n const theme = this.#getResolvedTheme();\n const baseSheet = StylesheetManager.getBaseStyleSheet(theme);\n\n if (includeTabStyles) {\n const tabSheet = StylesheetManager.getTabStyleSheet();\n this.shadowRoot!.adoptedStyleSheets = [baseSheet, tabSheet];\n } else {\n this.shadowRoot!.adoptedStyleSheets = [baseSheet];\n }\n }\n\n // Private methods - rendering\n async #render(): Promise<void> {\n const fileAttr = this.getAttribute('file');\n\n if (!fileAttr) {\n this.#showError('Error: \"file\" attribute is required. Please provide a GitHub file URL.');\n return;\n }\n\n const fileUrls = parseFileAttribute(fileAttr);\n\n if (fileUrls.length === 0) {\n this.#showError('Error: \"file\" attribute is required. Please provide a GitHub file URL.');\n return;\n }\n\n // Validate all URLs\n const invalidUrl = fileUrls.find((url) => !isValidGitHubUrl(url));\n if (invalidUrl) {\n // Sanitize URL before using in error message to prevent XSS\n const sanitizedUrl = escapeHtml(invalidUrl);\n this.#showError(\n `Error: Invalid GitHub URL format: ${sanitizedUrl}. Expected format: https://github.com/{owner}/{repo}/blob/{commit}/{path}`\n );\n return;\n }\n\n // Parse URLs to get file metadata first (don't fetch content yet - lazy load on demand)\n this.#files = fileUrls.map((url) => {\n try {\n const { rawUrl, filename } = parseGitHubUrl(url);\n return {\n filename,\n rawUrl,\n url,\n code: null,\n error: null,\n loaded: false,\n };\n } catch (error) {\n const filename = extractFilenameFromUrl(url);\n return {\n filename,\n rawUrl: url,\n url,\n code: null,\n error: error instanceof Error ? error.message : String(error),\n loaded: true, // Parse error - no point retrying\n };\n }\n });\n\n // Render skeleton UI immediately (tabs or single file structure)\n if (fileUrls.length === 1) {\n // Single file: show header + skeleton\n const file = this.#getFile(0);\n if (file.error) {\n this.#showError(`Error loading code: ${file.error}`);\n return;\n }\n // Show skeleton immediately\n this.shadowRoot!.innerHTML = generateSingleFileTemplate(\n file.filename,\n getSkeletonContentHtml(),\n StylesheetManager.getHighlightJSThemeUrl(this.#getResolvedTheme())\n );\n this.#applyStyleSheets(false);\n } else {\n // Multiple files: show tabs + skeleton immediately\n this.#renderTabsSkeletonStructure();\n }\n\n // Load highlight.js in background (UI already visible)\n try {\n await loadHighlightJS();\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : String(error);\n this.#showError(`Error loading highlight.js: ${errorMsg}`);\n return;\n }\n\n // Now load actual content\n if (fileUrls.length === 1) {\n await this.#displayCode(0);\n } else {\n await this.#displayWithTabs();\n }\n }\n\n async #displayCode(index: number): Promise<void> {\n const file = this.#getFile(index);\n const themeUrl = StylesheetManager.getHighlightJSThemeUrl(this.#getResolvedTheme());\n\n // Render skeleton immediately\n this.shadowRoot!.innerHTML = generateSingleFileTemplate(file.filename, getSkeletonContentHtml(), themeUrl);\n this.#applyStyleSheets(false);\n\n // Load content in background and update when ready\n await ensureFileLoaded(file, false);\n\n const contentArea = this.shadowRoot!.querySelector('article');\n if (file.error) {\n contentArea!.innerHTML = generateArticleContent(file.filename, getErrorContentHtml(file.error, true));\n // Add retry button event listener\n const retryButton = contentArea!.querySelector('.retry-button');\n if (retryButton) {\n retryButton.addEventListener('click', () => {\n void (async () => {\n // Show skeleton during retry\n contentArea!.innerHTML = generateArticleContent(file.filename, getSkeletonContentHtml());\n await ensureFileLoaded(file, true);\n await this.#displayCode(index);\n })();\n });\n }\n } else {\n contentArea!.innerHTML = generateArticleContent(file.filename, getCodeContentHtml(file.code));\n if (window.hljs) {\n applySyntaxHighlighting(this.shadowRoot!, file.code!);\n }\n }\n }\n\n async #displayWithTabs(): Promise<void> {\n if (!this.#tabState.areTabsFullyRendered()) {\n // First full render - build entire structure with event listeners\n await this.#renderTabsStructure();\n this.#tabState.setTabsFullyRendered(true);\n } else {\n // Tab switching - update only what's needed\n this.#switchToTab(this.#tabState.getActiveTabIndex());\n }\n }\n\n #renderTabsSkeletonStructure(): void {\n const activeIndex = this.#tabState.getActiveTabIndex();\n const tabsHtml = generateTabsHtml(this.#files, activeIndex);\n const themeUrl = StylesheetManager.getHighlightJSThemeUrl(this.#getResolvedTheme());\n\n this.shadowRoot!.innerHTML = generateTabbedTemplate(tabsHtml, activeIndex, getSkeletonContentHtml(), themeUrl);\n this.#applyStyleSheets(true);\n }\n\n async #renderTabsStructure(): Promise<void> {\n const activeIndex = this.#tabState.getActiveTabIndex();\n const tabsHtml = generateTabsHtml(this.#files, activeIndex);\n const themeUrl = StylesheetManager.getHighlightJSThemeUrl(this.#getResolvedTheme());\n\n // Render tabs and skeleton immediately\n this.shadowRoot!.innerHTML = generateTabbedTemplate(tabsHtml, activeIndex, getSkeletonContentHtml(), themeUrl);\n this.#applyStyleSheets(true);\n\n // Use event delegation on parent nav element to prevent race conditions\n const nav = this.shadowRoot!.querySelector('nav[role=\"tablist\"]');\n nav!.addEventListener('click', (e) => {\n const tab = (e.target as Element).closest('button[role=\"tab\"]');\n if (tab) {\n const newIndex = parseInt((tab as HTMLElement).dataset.index || '0');\n if (newIndex !== this.#tabState.getActiveTabIndex()) {\n this.#tabState.setActiveTabIndex(newIndex);\n void this.#displayWithTabs();\n }\n }\n });\n\n // Add keyboard navigation for accessibility\n nav!.addEventListener('keydown', (e) => {\n const keyboardEvent = e as KeyboardEvent;\n const tab = (keyboardEvent.target as Element).closest('button[role=\"tab\"]');\n if (!tab) {\n return;\n }\n\n const currentIndex = this.#tabState.getActiveTabIndex();\n const maxIndex = this.#files.length - 1;\n\n const newIndex = handleTabKeyNavigation(keyboardEvent.key, currentIndex, maxIndex);\n\n if (newIndex !== null) {\n e.preventDefault();\n this.#tabState.setActiveTabIndex(newIndex);\n void this.#displayWithTabs().then(() => {\n // Focus the newly selected tab\n const newTab = this.shadowRoot!.querySelector(`button[data-index=\"${newIndex}\"]`);\n if (newTab) {\n (newTab as HTMLElement).focus();\n }\n });\n }\n });\n\n // Mark this tab as rendered\n this.#tabState.markTabAsRendered(activeIndex);\n\n // Load active file content in background and update when ready\n const contentElement = this.shadowRoot!.querySelector('section[role=\"tabpanel\"]');\n await this.#loadAndRenderTabContent(activeIndex, contentElement as HTMLElement).catch((error: unknown) => {\n console.error('Failed to load tab content:', error);\n (contentElement as HTMLElement).innerHTML = getErrorContentHtml(\n `Failed to load content: ${error instanceof Error ? error.message : String(error)}`\n );\n });\n }\n\n #switchToTab(newIndex: number): void {\n // Update tab buttons - use aria-selected and tabindex for accessibility\n updateTabButtonStates(this.shadowRoot!, newIndex);\n\n // Check if content for this tab already exists\n let contentElement = this.shadowRoot!.querySelector(`section[role=\"tabpanel\"][data-index=\"${newIndex}\"]`);\n\n if (!contentElement) {\n // Create skeleton immediately (lazy load pattern)\n contentElement = createTabPanel(newIndex, getSkeletonContentHtml());\n\n // Append skeleton to DOM immediately (user sees it right away)\n this.shadowRoot!.querySelector('article')!.appendChild(contentElement);\n this.#tabState.markTabAsRendered(newIndex);\n\n // Load content asynchronously and update when ready\n void this.#loadAndRenderTabContent(newIndex, contentElement as HTMLElement).catch((error: unknown) => {\n console.error('Failed to load tab content:', error);\n (contentElement as HTMLElement).innerHTML = getErrorContentHtml(\n `Failed to load content: ${error instanceof Error ? error.message : String(error)}`\n );\n });\n }\n\n // Update panels - use aria-hidden instead of .hidden class\n updateTabPanelStates(this.shadowRoot!, newIndex);\n }\n\n async #loadAndRenderTabContent(index: number, contentElement: HTMLElement): Promise<void> {\n // Load file content\n const file = this.#getFile(index);\n await ensureFileLoaded(file, false);\n\n // Replace skeleton with actual content\n if (file.error || !file.code) {\n const errorMsg = file.error || 'Failed to load content: No content available';\n contentElement.innerHTML = getErrorContentHtml(errorMsg, true);\n\n // Add retry button event listener\n const retryButton = contentElement.querySelector('.retry-button');\n if (retryButton) {\n retryButton.addEventListener('click', () => {\n void (async () => {\n // Show skeleton during retry\n contentElement.innerHTML = getSkeletonContentHtml();\n await ensureFileLoaded(file, true);\n await this.#loadAndRenderTabContent(index, contentElement);\n })();\n });\n }\n } else {\n contentElement.innerHTML = getCodeContentHtml(file.code);\n // Apply syntax highlighting\n if (window.hljs) {\n applySyntaxHighlighting(contentElement, file.code);\n }\n }\n }\n\n #showError(message: string): void {\n this.shadowRoot!.innerHTML = `\n <div class=\"error\">${escapeHtml(message)}</div>\n `;\n this.#applyStyleSheets(false);\n }\n}\n", "import { GitHubCode } from './github-code';\n\n// Register the custom element\ncustomElements.define('github-code', GitHubCode);\n\n// Export for potential programmatic usage\nexport { GitHubCode };\n"],
5
- "mappings": "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",
3
+ "sources": ["../src/parsers/file-parser.ts", "../src/parsers/url-parser.ts", "../src/fetching/code-fetcher.ts", "../src/fetching/highlightjs-loader.ts", "../package.json", "../src/styles/base-light.css", "../src/styles/base-dark.css", "../src/styles/tab.css", "../src/styles/stylesheet-manager.ts", "../src/rendering/html-generators.ts", "../src/rendering/template-generators.ts", "../src/rendering/syntax-highlighter.ts", "../src/theme/theme-resolver.ts", "../src/tabs/tab-state.ts", "../src/tabs/tab-controller.ts", "../src/github-code.ts", "../src/index.ts"],
4
+ "sourcesContent": ["/**\n * Parses the comma-separated file attribute into individual URLs\n */\nexport function parseFileAttribute(fileAttr: string): string[] {\n return fileAttr\n .split(',')\n .map((url) => url.trim())\n .filter((url) => url.length > 0);\n}\n", "import type { GitHubUrlParts } from '../types';\n\n/**\n * Regex pattern to validate GitHub blob URLs\n */\n// eslint-disable-next-line no-useless-escape\nconst GITHUB_BLOB_PATTERN = /^https:\\/\\/github\\.com\\/[^\\/]+\\/[^\\/]+\\/blob\\/.+/;\n\n/**\n * Checks if a URL is a valid GitHub blob URL\n */\nexport function isValidGitHubUrl(url: string): boolean {\n return GITHUB_BLOB_PATTERN.test(url);\n}\n\n/**\n * Parses a GitHub blob URL into its components\n * @throws {Error} If the URL cannot be parsed\n */\nexport function parseGitHubUrl(url: string): GitHubUrlParts {\n // eslint-disable-next-line no-useless-escape\n const match = /^https:\\/\\/github\\.com\\/([^\\/]+)\\/([^\\/]+)\\/blob\\/([^\\/]+)\\/(.+)$/.exec(url);\n\n if (!match) {\n throw new Error('Failed to parse GitHub URL');\n }\n\n const owner = match[1];\n const repo = match[2];\n const commit = match[3];\n const path = match[4];\n\n if (!owner || !repo || !commit || !path) {\n throw new Error('Failed to parse GitHub URL');\n }\n\n const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${commit}/${path}`;\n\n const filename = path.split('/').pop() || 'unknown';\n\n return {\n rawUrl,\n filename,\n };\n}\n\n/**\n * Extracts filename from any URL (fallback for non-standard URLs)\n */\nexport function extractFilenameFromUrl(url: string): string {\n try {\n // eslint-disable-next-line no-useless-escape\n const match = /\\/([^\\/]+)$/.exec(url);\n return match?.[1] ?? 'unknown';\n } catch {\n return 'unknown';\n }\n}\n", "import type { FileMetadata } from '../types';\n\n/**\n * Fetches code content from a URL\n * @throws {Error} If the fetch fails (network error, HTTP error, CORS error)\n */\nexport async function fetchCode(url: string): Promise<string> {\n try {\n const response = await fetch(url);\n\n if (!response.ok) {\n throw new Error(`Failed to fetch code (HTTP ${response.status}). Please check if the URL is accessible.`);\n }\n\n return await response.text();\n } catch (error) {\n // Detect CORS errors specifically\n if (error instanceof TypeError && error.message.includes('fetch')) {\n throw new Error(\n `Failed to fetch code from ${url}. ` +\n `This is likely a CORS (Cross-Origin Resource Sharing) error. ` +\n `The server needs to allow requests from this origin. ` +\n `GitHub's raw.githubusercontent.com should work without CORS issues.`\n );\n }\n throw error;\n }\n}\n\n/**\n * Ensures a file's content is loaded, fetching it if necessary\n * @param file The file metadata object to load\n * @param isRetry Whether this is a retry attempt (will force re-fetch)\n */\nexport async function ensureFileLoaded(file: FileMetadata, isRetry = false): Promise<void> {\n // Already loaded (skip if not a retry)\n if (file.loaded && !isRetry) {\n return;\n }\n\n // If retrying, reset the loaded flag to allow re-fetch\n if (isRetry) {\n file.loaded = false;\n file.error = null;\n }\n\n try {\n const code = await fetchCode(file.rawUrl);\n file.code = code;\n file.error = null;\n file.loaded = true;\n } catch (error) {\n file.code = null;\n file.error = error instanceof Error ? error.message : String(error);\n file.loaded = true; // Mark as loaded so we show error (can retry later)\n }\n}\n", "/**\n * Shared promise to prevent multiple simultaneous loads of highlight.js\n */\nlet highlightJSLoadingPromise: Promise<void> | null = null;\n\n/**\n * Track which URL was loaded for introspection\n */\nlet loadedHighlightJSUrl: string | null = null;\n\n/**\n * Track the source of the loaded highlight.js library\n */\nlet highlightJSSource: 'user-provided' | 'cdn-default' | 'global' = 'global';\n\n/**\n * Type definition for highlight.js on window object\n */\ndeclare global {\n interface Window {\n hljs?: {\n highlightAuto: (code: string) => { value: string };\n };\n }\n}\n\n/**\n * Get the URL that was loaded (or will be loaded)\n * Returns \"auto\" if using default behavior or global hljs\n */\nexport function getLoadedHighlightJSUrl(): string {\n return loadedHighlightJSUrl || 'auto';\n}\n\n/**\n * Get the source of the highlight.js library\n */\nexport function getHighlightJSSource(): 'user-provided' | 'cdn-default' | 'global' {\n return highlightJSSource;\n}\n\n/**\n * Loads highlight.js library from CDN if not already loaded.\n * Uses a singleton pattern to ensure only one load attempt happens at a time.\n * @param customUrl - Optional custom URL to load highlight.js from\n */\nexport async function loadHighlightJS(customUrl?: string): Promise<void> {\n // If already loaded globally, track and return\n if (window.hljs) {\n if (!loadedHighlightJSUrl) {\n highlightJSSource = 'global';\n loadedHighlightJSUrl = 'auto';\n }\n return;\n }\n\n // If currently loading, check for URL conflicts and return the existing promise\n if (highlightJSLoadingPromise) {\n if (customUrl && loadedHighlightJSUrl && customUrl !== loadedHighlightJSUrl) {\n console.warn(\n `[github-code] Different highlight.js URLs detected. ` +\n `Already loading from \"${loadedHighlightJSUrl}\", ` +\n `but this instance requested \"${customUrl}\". ` +\n `The first URL will be used for all instances.`\n );\n }\n return highlightJSLoadingPromise;\n }\n\n // Determine which URL to use\n const DEFAULT_URL = 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js';\n const urlToLoad = customUrl || DEFAULT_URL;\n\n // Track state\n loadedHighlightJSUrl = urlToLoad;\n highlightJSSource = customUrl ? 'user-provided' : 'cdn-default';\n\n // Create new loading promise\n highlightJSLoadingPromise = new Promise((resolve, reject) => {\n const script = document.createElement('script');\n script.src = urlToLoad;\n\n // Only add integrity for default CDN URL\n if (!customUrl) {\n script.integrity = 'sha384-RH2xi4eIQ/gjtbs9fUXM68sLSi99C7ZWBRX1vDrVv6GQXRibxXLbwO2NGZB74MbU';\n script.crossOrigin = 'anonymous';\n }\n\n script.onload = () => {\n highlightJSLoadingPromise = null;\n\n // Validate that window.hljs was defined\n if (!window.hljs) {\n loadedHighlightJSUrl = null;\n highlightJSSource = 'global';\n reject(\n new Error(\n `The script at \"${urlToLoad}\" loaded successfully but did not define window.hljs. ` +\n `Please ensure this URL points to a valid highlight.js library.`\n )\n );\n return;\n }\n\n resolve();\n };\n\n script.onerror = () => {\n highlightJSLoadingPromise = null;\n loadedHighlightJSUrl = null;\n highlightJSSource = 'global';\n\n const errorMsg =\n `Failed to load highlight.js library from: ${urlToLoad}\\n` +\n (customUrl\n ? 'Please check that the URL is correct and accessible.'\n : 'If you have a Content Security Policy (CSP), ensure it allows:\\n' +\n ' script-src https://cdnjs.cloudflare.com\\n' +\n ' style-src https://cdnjs.cloudflare.com');\n reject(new Error(errorMsg));\n };\n\n document.head.appendChild(script);\n });\n\n return highlightJSLoadingPromise;\n}\n", "{\n \"name\": \"github-code\",\n \"version\": \"0.1.0\",\n \"description\": \"Custom element for embedding GitHub source files with syntax highlighting\",\n \"type\": \"module\",\n \"main\": \"dist/github-code.min.js\",\n \"module\": \"dist/github-code.min.js\",\n \"exports\": {\n \".\": {\n \"import\": \"./dist/github-code.min.js\",\n \"default\": \"./dist/github-code.min.js\"\n },\n \"./dist/*\": \"./dist/*\"\n },\n \"files\": [\n \"dist\"\n ],\n \"scripts\": {\n \"build\": \"node esbuild.config.mjs\",\n \"dev\": \"node esbuild.config.mjs --watch\",\n \"type-check\": \"tsc --noEmit\",\n \"lint\": \"eslint src\",\n \"lint:fix\": \"eslint src --fix\",\n \"format\": \"prettier --write \\\"src/**/*.ts\\\"\",\n \"format:check\": \"prettier --check \\\"src/**/*.ts\\\"\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"test:ui\": \"vitest --ui\",\n \"test:coverage\": \"vitest run --coverage\",\n \"pretest:e2e\": \"npm run build\",\n \"test:e2e\": \"playwright test\",\n \"test:e2e:ui\": \"playwright test --ui\",\n \"test:e2e:debug\": \"playwright test --debug\",\n \"test:e2e:headed\": \"playwright test --headed\",\n \"test:all\": \"npm run test:coverage && npm run test:e2e\",\n \"test:ci\": \"npm run lint && npm run type-check && npm run build && npm run test:all\"\n },\n \"keywords\": [\n \"web-component\",\n \"github\",\n \"code\",\n \"syntax-highlighting\"\n ],\n \"author\": \"\",\n \"license\": \"MIT\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/destan/github-code.git\"\n },\n \"devDependencies\": {\n \"@eslint/js\": \"^9.39.2\",\n \"@playwright/test\": \"^1.57.0\",\n \"@semantic-release/changelog\": \"^6.0.3\",\n \"@semantic-release/git\": \"^10.0.1\",\n \"@types/node\": \"^25.0.2\",\n \"@vitest/coverage-v8\": \"^4.0.15\",\n \"@vitest/ui\": \"^4.0.15\",\n \"esbuild\": \"0.27.1\",\n \"eslint\": \"^9.39.2\",\n \"eslint-config-prettier\": \"^10.1.8\",\n \"happy-dom\": \"^20.0.11\",\n \"prettier\": \"^3.7.4\",\n \"semantic-release\": \"^24.2.9\",\n \"typescript\": \"5.9.3\",\n \"typescript-eslint\": \"^8.50.0\",\n \"vitest\": \"^4.0.15\"\n },\n \"engines\": {\n \"node\": \">=24.12.0\"\n }\n}\n", "export default \":host {\\n --border-color: #d0d7de;\\n --header-background: #f6f8fa;\\n --header-text-color: #24292f;\\n --line-number-color: #57606a;\\n --tab-color: #57606a;\\n --tab-hover-border: #d0d7de;\\n --tabs-background: #f6f8fa;\\n --code-background: #ffffff;\\n --skeleton-base: #e0e0e0;\\n --skeleton-highlight: #f0f0f0;\\n --error-text-color: #cf222e;\\n --error-background: #ffebe9;\\n --error-border: #ff8182;\\n --button-background: #f6f8fa;\\n --button-text-color: #24292f;\\n --button-border: #d0d7de;\\n --button-hover-background: #f3f4f6;\\n --font-family-base:\\n -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif, 'Apple Color Emoji',\\n 'Segoe UI Emoji';\\n --font-family-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;\\n\\n display: block;\\n min-width: 0;\\n font-family: var(--font-family-base);\\n}\\n\\narticle {\\n border: 1px solid var(--border-color);\\n border-radius: 6px;\\n overflow: hidden;\\n background-color: var(--code-background);\\n}\\n\\nheader {\\n background-color: var(--header-background);\\n padding: 8px 16px;\\n border-bottom: 1px solid var(--border-color);\\n color: var(--header-text-color);\\n font-weight: 600;\\n font-size: 14px;\\n}\\n\\n.code-wrapper {\\n width: 100%;\\n margin: 0;\\n overflow-x: auto;\\n background-color: var(--code-background);\\n}\\n\\n.code-table {\\n display: flex;\\n flex-direction: column;\\n width: 100%;\\n min-width: 0;\\n}\\n\\n.code-row {\\n display: flex;\\n}\\n\\n.line-number {\\n flex-shrink: 0;\\n text-align: right;\\n padding-right: 6px;\\n padding-left: 8px;\\n color: var(--line-number-color);\\n user-select: none;\\n border-right: 1px solid var(--border-color);\\n font-family: var(--font-family-mono);\\n font-size: 12px;\\n line-height: 20px;\\n min-width: 40px;\\n}\\n\\n.code-cell {\\n flex: 1;\\n min-width: 0;\\n padding-left: 2px;\\n font-family: var(--font-family-mono);\\n font-size: 12px;\\n line-height: 20px;\\n white-space: pre;\\n}\\n\\n.error {\\n padding: 16px;\\n color: var(--error-text-color);\\n background-color: var(--error-background);\\n border: 1px solid var(--error-border);\\n border-radius: 6px;\\n font-family: var(--font-family-mono);\\n font-size: 12px;\\n line-height: 1.5;\\n margin: 16px;\\n}\\n\\n.retry-button {\\n margin-top: 12px;\\n padding: 8px 16px;\\n background: var(--button-background);\\n color: var(--button-text-color);\\n border: 1px solid var(--button-border);\\n border-radius: 6px;\\n cursor: pointer;\\n font-size: 14px;\\n font-family: var(--font-family-base);\\n font-weight: 500;\\n transition: background-color 0.2s ease;\\n}\\n\\n.retry-button:hover {\\n background-color: var(--button-hover-background);\\n}\\n\\n/* Skeleton loader styles */\\n.skeleton-line {\\n height: 12px;\\n background: linear-gradient(90deg, var(--skeleton-base) 25%, var(--skeleton-highlight) 50%, var(--skeleton-base) 75%);\\n background-size: 200% 100%;\\n animation: loading 1.5s ease-in-out infinite;\\n border-radius: 4px;\\n margin: 4px 0;\\n}\\n\\n@keyframes loading {\\n 0% {\\n background-position: 200% 0;\\n }\\n 100% {\\n background-position: -200% 0;\\n }\\n}\\n\\n.skeleton-loading .line-number {\\n opacity: 0.3;\\n}\\n\"", "export default \":host {\\n --border-color: #30363d;\\n --header-background: #161b22;\\n --header-text-color: #c9d1d9;\\n --line-number-color: #8b949e;\\n --tab-color: #8b949e;\\n --tab-hover-border: #30363d;\\n --tabs-background: #161b22;\\n --code-background: #0d1117;\\n --skeleton-base: #21262d;\\n --skeleton-highlight: #30363d;\\n --error-text-color: #ff7b72;\\n --error-background: #490202;\\n --error-border: #f85149;\\n --button-background: #21262d;\\n --button-text-color: #c9d1d9;\\n --button-border: #30363d;\\n --button-hover-background: #30363d;\\n --font-family-base:\\n -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif, 'Apple Color Emoji',\\n 'Segoe UI Emoji';\\n --font-family-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;\\n\\n display: block;\\n min-width: 0;\\n font-family: var(--font-family-base);\\n}\\n\\narticle {\\n border: 1px solid var(--border-color);\\n border-radius: 6px;\\n overflow: hidden;\\n background-color: var(--code-background);\\n}\\n\\nheader {\\n background-color: var(--header-background);\\n padding: 8px 16px;\\n border-bottom: 1px solid var(--border-color);\\n color: var(--header-text-color);\\n font-weight: 600;\\n font-size: 14px;\\n}\\n\\n.code-wrapper {\\n width: 100%;\\n margin: 0;\\n overflow-x: auto;\\n background-color: var(--code-background);\\n}\\n\\n.code-table {\\n display: flex;\\n flex-direction: column;\\n width: 100%;\\n min-width: 0;\\n}\\n\\n.code-row {\\n display: flex;\\n}\\n\\n.line-number {\\n flex-shrink: 0;\\n text-align: right;\\n padding-right: 6px;\\n padding-left: 8px;\\n color: var(--line-number-color);\\n user-select: none;\\n border-right: 1px solid var(--border-color);\\n font-family: var(--font-family-mono);\\n font-size: 12px;\\n line-height: 20px;\\n min-width: 40px;\\n}\\n\\n.code-cell {\\n flex: 1;\\n min-width: 0;\\n padding-left: 2px;\\n font-family: var(--font-family-mono);\\n font-size: 12px;\\n line-height: 20px;\\n white-space: pre;\\n}\\n\\n.error {\\n padding: 16px;\\n color: var(--error-text-color);\\n background-color: var(--error-background);\\n border: 1px solid var(--error-border);\\n border-radius: 6px;\\n font-family: var(--font-family-mono);\\n font-size: 12px;\\n line-height: 1.5;\\n margin: 16px;\\n}\\n\\n.retry-button {\\n margin-top: 12px;\\n padding: 8px 16px;\\n background: var(--button-background);\\n color: var(--button-text-color);\\n border: 1px solid var(--button-border);\\n border-radius: 6px;\\n cursor: pointer;\\n font-size: 14px;\\n font-family: var(--font-family-base);\\n font-weight: 500;\\n transition: background-color 0.2s ease;\\n}\\n\\n.retry-button:hover {\\n background-color: var(--button-hover-background);\\n}\\n\\n/* Skeleton loader styles */\\n.skeleton-line {\\n height: 12px;\\n background: linear-gradient(90deg, var(--skeleton-base) 25%, var(--skeleton-highlight) 50%, var(--skeleton-base) 75%);\\n background-size: 200% 100%;\\n animation: loading 1.5s ease-in-out infinite;\\n border-radius: 4px;\\n margin: 4px 0;\\n}\\n\\n@keyframes loading {\\n 0% {\\n background-position: 200% 0;\\n }\\n 100% {\\n background-position: -200% 0;\\n }\\n}\\n\\n.skeleton-loading .line-number {\\n opacity: 0.3;\\n}\\n\"", "export default \"nav[role='tablist'] {\\n display: flex;\\n background-color: var(--tabs-background);\\n border-bottom: 1px solid var(--border-color);\\n overflow-x: auto;\\n}\\n\\nnav > button {\\n padding: 8px 16px;\\n background: none;\\n border: none;\\n border-bottom: 2px solid transparent;\\n color: var(--tab-color);\\n font-size: 14px;\\n font-family: var(--font-family-base);\\n cursor: pointer;\\n white-space: nowrap;\\n transition:\\n color 0.1s ease,\\n border-color 0.1s ease;\\n}\\n\\nnav > button:hover {\\n color: var(--header-text-color);\\n border-bottom-color: var(--tab-hover-border);\\n}\\n\\nnav > button[aria-selected='true'] {\\n color: var(--header-text-color);\\n border-bottom-color: #fd8c73;\\n font-weight: 600;\\n}\\n\\nsection[role='tabpanel'] {\\n display: block;\\n}\\n\\nsection[role='tabpanel'][aria-hidden='true'] {\\n display: none;\\n}\\n\"", "import baseLightCss from './base-light.css';\nimport baseDarkCss from './base-dark.css';\nimport tabCss from './tab.css';\nimport type { ResolvedTheme } from '../types';\n\n/**\n * Manages constructable stylesheets for the component.\n * Implements caching to share stylesheet instances across all component instances.\n */\nexport class StylesheetManager {\n private static baseStylesLight: CSSStyleSheet | null = null;\n private static baseStylesDark: CSSStyleSheet | null = null;\n private static tabStyles: CSSStyleSheet | null = null;\n\n /**\n * Gets the base stylesheet for the specified theme (light or dark).\n * Lazy-loads and caches the stylesheet on first access.\n */\n static getBaseStyleSheet(theme: ResolvedTheme): CSSStyleSheet {\n if (theme === 'dark') {\n if (!this.baseStylesDark) {\n this.baseStylesDark = new CSSStyleSheet();\n this.baseStylesDark.replaceSync(baseDarkCss);\n }\n return this.baseStylesDark;\n } else {\n if (!this.baseStylesLight) {\n this.baseStylesLight = new CSSStyleSheet();\n this.baseStylesLight.replaceSync(baseLightCss);\n }\n return this.baseStylesLight;\n }\n }\n\n /**\n * Gets the tab stylesheet.\n * Lazy-loads and caches the stylesheet on first access.\n */\n static getTabStyleSheet(): CSSStyleSheet {\n if (!this.tabStyles) {\n this.tabStyles = new CSSStyleSheet();\n this.tabStyles.replaceSync(tabCss);\n }\n return this.tabStyles;\n }\n\n /**\n * Gets the highlight.js theme URL for the specified theme.\n */\n static getHighlightJSThemeUrl(theme: ResolvedTheme): string {\n const themeFile = theme === 'dark' ? 'github-dark.min.css' : 'github.min.css';\n return `https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/${themeFile}`;\n }\n}\n", "/**\n * Escapes HTML special characters to prevent XSS\n */\nexport function escapeHtml(text: string): string {\n const div = document.createElement('div');\n div.textContent = text;\n return div.innerHTML;\n}\n\n/**\n * Generates HTML for error display\n */\nexport function getErrorContentHtml(errorMessage: string, showRetry = false): string {\n const retryButton = showRetry ? `<button class=\"retry-button\">Retry</button>` : '';\n return `<div class=\"error\">${escapeHtml(errorMessage)}${retryButton}</div>`;\n}\n\n/**\n * Generates HTML for skeleton loading state\n */\nexport function getSkeletonContentHtml(): string {\n // Generate skeleton lines that look like code\n const skeletonLines = Array.from({ length: 20 }, (_, i) => {\n // Random width between 60-90%\n // Using Math.random() is acceptable here for non-cryptographic purposes\n const width = 60 + Math.random() * 30; // NOSONAR\n return `\n <div class=\"code-row\">\n <div class=\"line-number\">${i + 1}</div>\n <div class=\"skeleton-line\" style=\"width: ${width}%\"></div>\n </div>\n `;\n }).join('');\n\n return `\n <div class=\"code-wrapper skeleton-loading\">\n <div class=\"code-table\">\n ${skeletonLines}\n </div>\n </div>\n `;\n}\n\n/**\n * Generates HTML for code display (without syntax highlighting)\n */\nexport function getCodeContentHtml(code: string | null): string {\n // Safety check for null/undefined code\n if (!code) {\n return getErrorContentHtml('No code content available');\n }\n\n const lines = code.split('\\n');\n\n // Remove only the last empty line if it exists (from final newline)\n if (lines.length > 0 && lines[lines.length - 1] === '') {\n lines.pop();\n }\n\n return `\n <div class=\"code-wrapper hljs\">\n <div class=\"code-table\">\n ${lines\n .map(\n (line, index) => `\n <div class=\"code-row\">\n <div class=\"line-number\">${index + 1}</div>\n <div class=\"code-cell\">${escapeHtml(line) || ' '}</div>\n </div>\n `\n )\n .join('')}\n </div>\n </div>\n `;\n}\n", "import { escapeHtml } from './html-generators';\n\n/**\n * Generates the outer HTML structure for a single file display.\n *\n * @param filename - The filename to show in the header\n * @param content - The inner content HTML (skeleton, code, or error)\n * @param themeStylesheetUrl - URL for the highlight.js theme CSS\n * @returns Complete HTML string for single file layout\n */\nexport function generateSingleFileTemplate(filename: string, content: string, themeStylesheetUrl: string): string {\n return `<link rel=\"stylesheet\" href=\"${themeStylesheetUrl}\">\n<article>\n <header>${escapeHtml(filename)}</header>\n ${content}\n</article>`;\n}\n\n/**\n * Generates the article content for a single file (header + content).\n * Used when updating only the article innards, not the full template.\n *\n * @param filename - The filename to show in the header\n * @param content - The inner content HTML\n * @returns HTML string for article content\n */\nexport function generateArticleContent(filename: string, content: string): string {\n return `<header>${escapeHtml(filename)}</header>\n ${content}`;\n}\n\n/**\n * Generates the outer HTML structure for tabbed file display.\n *\n * @param tabsHtml - Pre-generated HTML for tab buttons\n * @param activeIndex - Index of the currently active tab\n * @param panelContent - Content HTML for the active panel\n * @param themeStylesheetUrl - URL for the highlight.js theme CSS\n * @returns Complete HTML string for tabbed layout\n */\nexport function generateTabbedTemplate(\n tabsHtml: string,\n activeIndex: number,\n panelContent: string,\n themeStylesheetUrl: string\n): string {\n return `<link rel=\"stylesheet\" href=\"${themeStylesheetUrl}\">\n<article>\n <nav role=\"tablist\" aria-label=\"Code files\">${tabsHtml}</nav>\n <section role=\"tabpanel\"\n id=\"panel-${activeIndex}\"\n aria-labelledby=\"tab-${activeIndex}\"\n data-index=\"${activeIndex}\">\n ${panelContent}\n </section>\n</article>`;\n}\n", "/**\n * Gets syntax-highlighted lines from code using highlight.js\n */\nexport function getHighlightedLines(code: string | null): string[] {\n // Safety check for null/undefined code\n if (!code) {\n return [''];\n }\n\n const lines = code.split('\\n');\n\n // Remove only the last empty line if it exists (from final newline)\n if (lines.length > 0 && lines[lines.length - 1] === '') {\n lines.pop();\n }\n\n const fullCode = lines.join('\\n');\n const result = window.hljs!.highlightAuto(fullCode);\n const highlightedLines = result.value.split('\\n');\n\n // Ensure highlightedLines matches lines length\n while (highlightedLines.length > lines.length) {\n highlightedLines.pop();\n }\n\n return highlightedLines;\n}\n\n/**\n * Applies syntax highlighting to code cells within a container\n */\nexport function applySyntaxHighlighting(container: Element | ShadowRoot, code: string): void {\n const codeCells = container.querySelectorAll('.code-cell');\n\n const applyHighlighting = (): void => {\n const highlightedLines = getHighlightedLines(code);\n codeCells.forEach((cell, index) => {\n (cell as HTMLElement).innerHTML = highlightedLines[index] || ' ';\n });\n };\n\n // Use requestIdleCallback to avoid blocking the main thread\n if ('requestIdleCallback' in window) {\n requestIdleCallback(applyHighlighting);\n } else {\n applyHighlighting();\n }\n}\n", "import type { Theme, ResolvedTheme } from '../types';\n\n/**\n * Resolves a theme attribute to an actual theme value.\n * Handles 'auto' theme by detecting system preference.\n *\n * @param themeAttr - The theme attribute value ('light', 'dark', or 'auto')\n * @returns The resolved theme ('light' or 'dark')\n */\nexport function resolveTheme(themeAttr: Theme): ResolvedTheme {\n if (themeAttr === 'dark') {\n return 'dark';\n }\n\n if (themeAttr === 'light') {\n return 'light';\n }\n\n // auto - detect system preference\n const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;\n return prefersDark ? 'dark' : 'light';\n}\n\n/**\n * Gets the theme attribute from an element, defaulting to 'auto'.\n *\n * @param element - The HTML element to get the theme from\n * @returns The theme value\n */\nexport function getThemeAttribute(element: HTMLElement): Theme {\n const attr = element.getAttribute('theme');\n if (attr === 'dark' || attr === 'light') {\n return attr;\n }\n return 'auto';\n}\n\n/**\n * Creates a theme change handler that clears cached theme and triggers re-render.\n *\n * @param onThemeChange - Callback to invoke when theme changes\n * @returns Object with setup and cleanup functions for theme media query listener\n */\nexport function createThemeListener(onThemeChange: () => void): {\n setup: () => MediaQueryList | null;\n cleanup: (mediaQuery: MediaQueryList | null) => void;\n handler: () => void;\n} {\n const handler = onThemeChange;\n\n return {\n setup: () => {\n if (window.matchMedia) {\n const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');\n mediaQuery.addEventListener('change', handler);\n return mediaQuery;\n }\n return null;\n },\n cleanup: (mediaQuery: MediaQueryList | null) => {\n if (mediaQuery) {\n mediaQuery.removeEventListener('change', handler);\n }\n },\n handler,\n };\n}\n", "/**\n * Manages tab state for the component\n */\nexport class TabState {\n private activeTabIndex = 0;\n private renderedTabs = new Set<number>();\n private tabsFullyRendered = false;\n\n /**\n * Gets the currently active tab index\n */\n getActiveTabIndex(): number {\n return this.activeTabIndex;\n }\n\n /**\n * Sets the active tab index\n */\n setActiveTabIndex(index: number): void {\n this.activeTabIndex = index;\n }\n\n /**\n * Checks if tabs structure is fully rendered with event listeners\n */\n areTabsFullyRendered(): boolean {\n return this.tabsFullyRendered;\n }\n\n /**\n * Marks tabs as fully rendered\n */\n setTabsFullyRendered(value: boolean): void {\n this.tabsFullyRendered = value;\n }\n\n /**\n * Checks if a tab has been rendered\n */\n isTabRendered(index: number): boolean {\n return this.renderedTabs.has(index);\n }\n\n /**\n * Marks a tab as rendered\n */\n markTabAsRendered(index: number): void {\n this.renderedTabs.add(index);\n }\n\n /**\n * Clears all rendered tabs\n */\n clearRenderedTabs(): void {\n this.renderedTabs.clear();\n }\n\n /**\n * Gets the total number of tabs\n */\n getTabCount(files: unknown[]): number {\n return files.length;\n }\n\n /**\n * Resets tab state (useful when re-parsing files)\n */\n reset(): void {\n this.renderedTabs.clear();\n this.tabsFullyRendered = false;\n // Note: activeTabIndex is NOT reset here - that's handled by the caller\n }\n}\n", "import type { FileMetadata } from '../types';\nimport { escapeHtml } from '../rendering/html-generators';\n\n/**\n * Generates HTML for tab buttons\n */\nexport function generateTabsHtml(files: FileMetadata[], activeTabIndex: number): string {\n return files\n .map(\n (file, index) => `\n <button role=\"tab\"\n id=\"tab-${index}\"\n aria-selected=\"${index === activeTabIndex ? 'true' : 'false'}\"\n aria-controls=\"panel-${index}\"\n tabindex=\"${index === activeTabIndex ? '0' : '-1'}\"\n data-index=\"${index}\">\n ${escapeHtml(file.filename)}\n </button>\n `\n )\n .join('');\n}\n\n/**\n * Updates tab button states (aria attributes) when switching tabs\n */\nexport function updateTabButtonStates(shadowRoot: ShadowRoot, newActiveIndex: number): void {\n const tabs = shadowRoot.querySelectorAll('nav > button');\n tabs.forEach((tab) => {\n const tabIndex = parseInt((tab as HTMLElement).dataset.index || '0');\n const isSelected = tabIndex === newActiveIndex;\n tab.setAttribute('aria-selected', isSelected ? 'true' : 'false');\n tab.setAttribute('tabindex', isSelected ? '0' : '-1');\n });\n}\n\n/**\n * Updates tab panel visibility states when switching tabs\n */\nexport function updateTabPanelStates(shadowRoot: ShadowRoot, activeIndex: number): void {\n const allPanels = shadowRoot.querySelectorAll('section[role=\"tabpanel\"]');\n allPanels.forEach((panel) => {\n const panelIndex = parseInt((panel as HTMLElement).dataset.index || '0');\n panel.setAttribute('aria-hidden', panelIndex !== activeIndex ? 'true' : 'false');\n });\n}\n\n/**\n * Creates a new tab panel element for lazy loading\n */\nexport function createTabPanel(index: number, skeletonHtml: string): HTMLElement {\n const contentElement = document.createElement('section');\n contentElement.setAttribute('role', 'tabpanel');\n contentElement.setAttribute('id', `panel-${index}`);\n contentElement.setAttribute('aria-labelledby', `tab-${index}`);\n contentElement.dataset.index = String(index);\n contentElement.innerHTML = skeletonHtml;\n return contentElement;\n}\n\n/**\n * Handles keyboard navigation for tabs (Arrow keys, Home, End)\n * Returns the new index if navigation should occur, null otherwise\n */\nexport function handleTabKeyNavigation(key: string, currentIndex: number, maxIndex: number): number | null {\n switch (key) {\n case 'ArrowLeft':\n return currentIndex > 0 ? currentIndex - 1 : maxIndex;\n case 'ArrowRight':\n return currentIndex < maxIndex ? currentIndex + 1 : 0;\n case 'Home':\n return 0;\n case 'End':\n return maxIndex;\n default:\n return null;\n }\n}\n", "import type { FileMetadata, ResolvedTheme, GitHubCodeInfo } from './types';\nimport { parseFileAttribute } from './parsers/file-parser';\nimport { isValidGitHubUrl, parseGitHubUrl, extractFilenameFromUrl } from './parsers/url-parser';\nimport { ensureFileLoaded } from './fetching/code-fetcher';\nimport { loadHighlightJS, getLoadedHighlightJSUrl, getHighlightJSSource } from './fetching/highlightjs-loader';\nimport packageJson from '../package.json';\nimport { StylesheetManager } from './styles/stylesheet-manager';\nimport {\n escapeHtml,\n getErrorContentHtml,\n getSkeletonContentHtml,\n getCodeContentHtml,\n} from './rendering/html-generators';\nimport {\n generateSingleFileTemplate,\n generateArticleContent,\n generateTabbedTemplate,\n} from './rendering/template-generators';\nimport { applySyntaxHighlighting } from './rendering/syntax-highlighter';\nimport { resolveTheme, getThemeAttribute } from './theme/theme-resolver';\nimport { TabState } from './tabs/tab-state';\nimport {\n generateTabsHtml,\n updateTabButtonStates,\n updateTabPanelStates,\n createTabPanel,\n handleTabKeyNavigation,\n} from './tabs/tab-controller';\n\n/**\n * GitHub Code Web Component\n * Displays GitHub file URLs with syntax highlighting\n */\nexport class GitHubCode extends HTMLElement {\n // Private fields\n #files: FileMetadata[] = [];\n #tabState = new TabState();\n #resolvedTheme: ResolvedTheme | null = null;\n #themeMediaQuery: MediaQueryList | null = null;\n #themeChangeHandler: (() => void) | null = null;\n\n constructor() {\n super();\n this.attachShadow({ mode: 'open' });\n }\n\n /**\n * Safely gets a file by index, throwing if out of bounds.\n * This provides type-safe array access with noUncheckedIndexedAccess.\n */\n #getFile(index: number): FileMetadata {\n const file = this.#files[index];\n if (!file) {\n throw new Error(`File at index ${index} not found`);\n }\n return file;\n }\n\n static get observedAttributes(): string[] {\n return ['file', 'theme'];\n }\n\n /**\n * Runtime information about the component and loaded libraries\n */\n static get info(): GitHubCodeInfo {\n return {\n version: packageJson.version,\n highlightjsUrl: getLoadedHighlightJSUrl(),\n highlightjsSource: getHighlightJSSource(),\n };\n }\n\n connectedCallback(): void {\n // Set up theme change listener for reactive theme updates\n this.#setupThemeListener();\n\n // Initial render (will parse files and display)\n void this.#render();\n }\n\n disconnectedCallback(): void {\n // Clean up theme change listener to prevent memory leaks\n if (this.#themeMediaQuery && this.#themeChangeHandler) {\n this.#themeMediaQuery.removeEventListener('change', this.#themeChangeHandler);\n }\n }\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n // Skip initial attribute set (oldValue is null) - connectedCallback handles initial render\n if (oldValue === null || oldValue === newValue) {\n return;\n }\n\n if (name === 'file') {\n // Store current active tab index before re-parsing files\n const previousIndex = this.#tabState.getActiveTabIndex();\n\n // Re-parse files (clear renderedTabs since structure will be rebuilt)\n this.#tabState.reset();\n\n // Re-render with new file URLs\n void this.#render();\n\n // Try to preserve tab index if it still exists\n if (previousIndex < this.#files.length) {\n this.#tabState.setActiveTabIndex(previousIndex);\n } else {\n // Reset to first tab if previous index no longer exists\n this.#tabState.setActiveTabIndex(0);\n }\n } else if (name === 'theme') {\n this.#resolvedTheme = null; // Clear cached theme\n void this.#render();\n }\n }\n\n // Private methods - theming\n #setupThemeListener(): void {\n if (window.matchMedia) {\n this.#themeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');\n this.#themeChangeHandler = () => {\n this.#resolvedTheme = null; // Clear cached theme\n void this.#render(); // Re-render with new theme\n };\n this.#themeMediaQuery.addEventListener('change', this.#themeChangeHandler);\n }\n }\n\n #getResolvedTheme(): ResolvedTheme {\n if (this.#resolvedTheme) {\n return this.#resolvedTheme;\n }\n\n this.#resolvedTheme = resolveTheme(getThemeAttribute(this));\n return this.#resolvedTheme;\n }\n\n // Apply constructable stylesheets to shadow root (CSP-compliant, no 'unsafe-inline' needed)\n #applyStyleSheets(includeTabStyles = false): void {\n const theme = this.#getResolvedTheme();\n const baseSheet = StylesheetManager.getBaseStyleSheet(theme);\n\n if (includeTabStyles) {\n const tabSheet = StylesheetManager.getTabStyleSheet();\n this.shadowRoot!.adoptedStyleSheets = [baseSheet, tabSheet];\n } else {\n this.shadowRoot!.adoptedStyleSheets = [baseSheet];\n }\n }\n\n // Private methods - rendering\n async #render(): Promise<void> {\n const fileAttr = this.getAttribute('file');\n\n if (!fileAttr) {\n this.#showError('Error: \"file\" attribute is required. Please provide a GitHub file URL.');\n return;\n }\n\n const fileUrls = parseFileAttribute(fileAttr);\n\n if (fileUrls.length === 0) {\n this.#showError('Error: \"file\" attribute is required. Please provide a GitHub file URL.');\n return;\n }\n\n // Validate all URLs\n const invalidUrl = fileUrls.find((url) => !isValidGitHubUrl(url));\n if (invalidUrl) {\n // Sanitize URL before using in error message to prevent XSS\n const sanitizedUrl = escapeHtml(invalidUrl);\n this.#showError(\n `Error: Invalid GitHub URL format: ${sanitizedUrl}. Expected format: https://github.com/{owner}/{repo}/blob/{commit}/{path}`\n );\n return;\n }\n\n // Parse URLs to get file metadata first (don't fetch content yet - lazy load on demand)\n this.#files = fileUrls.map((url) => {\n try {\n const { rawUrl, filename } = parseGitHubUrl(url);\n return {\n filename,\n rawUrl,\n url,\n code: null,\n error: null,\n loaded: false,\n };\n } catch (error) {\n const filename = extractFilenameFromUrl(url);\n return {\n filename,\n rawUrl: url,\n url,\n code: null,\n error: error instanceof Error ? error.message : String(error),\n loaded: true, // Parse error - no point retrying\n };\n }\n });\n\n // Render skeleton UI immediately (tabs or single file structure)\n if (fileUrls.length === 1) {\n // Single file: show header + skeleton\n const file = this.#getFile(0);\n if (file.error) {\n this.#showError(`Error loading code: ${file.error}`);\n return;\n }\n // Show skeleton immediately\n this.shadowRoot!.innerHTML = generateSingleFileTemplate(\n file.filename,\n getSkeletonContentHtml(),\n StylesheetManager.getHighlightJSThemeUrl(this.#getResolvedTheme())\n );\n this.#applyStyleSheets(false);\n } else {\n // Multiple files: show tabs + skeleton immediately\n this.#renderTabsSkeletonStructure();\n }\n\n // Load highlight.js in background (UI already visible)\n try {\n // Read custom URL attribute (treat \"auto\" as default)\n const customUrlAttr = this.getAttribute('highlightjs-url');\n const customUrl = customUrlAttr && customUrlAttr !== 'auto' ? customUrlAttr : undefined;\n\n await loadHighlightJS(customUrl);\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : String(error);\n this.#showError(`Error loading highlight.js: ${errorMsg}`);\n return;\n }\n\n // Now load actual content\n if (fileUrls.length === 1) {\n await this.#displayCode(0);\n } else {\n await this.#displayWithTabs();\n }\n }\n\n async #displayCode(index: number): Promise<void> {\n const file = this.#getFile(index);\n const themeUrl = StylesheetManager.getHighlightJSThemeUrl(this.#getResolvedTheme());\n\n // Render skeleton immediately\n this.shadowRoot!.innerHTML = generateSingleFileTemplate(file.filename, getSkeletonContentHtml(), themeUrl);\n this.#applyStyleSheets(false);\n\n // Load content in background and update when ready\n await ensureFileLoaded(file, false);\n\n const contentArea = this.shadowRoot!.querySelector('article');\n if (file.error) {\n contentArea!.innerHTML = generateArticleContent(file.filename, getErrorContentHtml(file.error, true));\n // Add retry button event listener\n const retryButton = contentArea!.querySelector('.retry-button');\n if (retryButton) {\n retryButton.addEventListener('click', () => {\n void (async () => {\n // Show skeleton during retry\n contentArea!.innerHTML = generateArticleContent(file.filename, getSkeletonContentHtml());\n await ensureFileLoaded(file, true);\n await this.#displayCode(index);\n })();\n });\n }\n } else {\n contentArea!.innerHTML = generateArticleContent(file.filename, getCodeContentHtml(file.code));\n if (window.hljs) {\n applySyntaxHighlighting(this.shadowRoot!, file.code!);\n }\n }\n }\n\n async #displayWithTabs(): Promise<void> {\n if (!this.#tabState.areTabsFullyRendered()) {\n // First full render - build entire structure with event listeners\n await this.#renderTabsStructure();\n this.#tabState.setTabsFullyRendered(true);\n } else {\n // Tab switching - update only what's needed\n this.#switchToTab(this.#tabState.getActiveTabIndex());\n }\n }\n\n #renderTabsSkeletonStructure(): void {\n const activeIndex = this.#tabState.getActiveTabIndex();\n const tabsHtml = generateTabsHtml(this.#files, activeIndex);\n const themeUrl = StylesheetManager.getHighlightJSThemeUrl(this.#getResolvedTheme());\n\n this.shadowRoot!.innerHTML = generateTabbedTemplate(tabsHtml, activeIndex, getSkeletonContentHtml(), themeUrl);\n this.#applyStyleSheets(true);\n }\n\n async #renderTabsStructure(): Promise<void> {\n const activeIndex = this.#tabState.getActiveTabIndex();\n const tabsHtml = generateTabsHtml(this.#files, activeIndex);\n const themeUrl = StylesheetManager.getHighlightJSThemeUrl(this.#getResolvedTheme());\n\n // Render tabs and skeleton immediately\n this.shadowRoot!.innerHTML = generateTabbedTemplate(tabsHtml, activeIndex, getSkeletonContentHtml(), themeUrl);\n this.#applyStyleSheets(true);\n\n // Use event delegation on parent nav element to prevent race conditions\n const nav = this.shadowRoot!.querySelector('nav[role=\"tablist\"]');\n nav!.addEventListener('click', (e) => {\n const tab = (e.target as Element).closest('button[role=\"tab\"]');\n if (tab) {\n const newIndex = parseInt((tab as HTMLElement).dataset.index || '0');\n if (newIndex !== this.#tabState.getActiveTabIndex()) {\n this.#tabState.setActiveTabIndex(newIndex);\n void this.#displayWithTabs();\n }\n }\n });\n\n // Add keyboard navigation for accessibility\n nav!.addEventListener('keydown', (e) => {\n const keyboardEvent = e as KeyboardEvent;\n const tab = (keyboardEvent.target as Element).closest('button[role=\"tab\"]');\n if (!tab) {\n return;\n }\n\n const currentIndex = this.#tabState.getActiveTabIndex();\n const maxIndex = this.#files.length - 1;\n\n const newIndex = handleTabKeyNavigation(keyboardEvent.key, currentIndex, maxIndex);\n\n if (newIndex !== null) {\n e.preventDefault();\n this.#tabState.setActiveTabIndex(newIndex);\n void this.#displayWithTabs().then(() => {\n // Focus the newly selected tab\n const newTab = this.shadowRoot!.querySelector(`button[data-index=\"${newIndex}\"]`);\n if (newTab) {\n (newTab as HTMLElement).focus();\n }\n });\n }\n });\n\n // Mark this tab as rendered\n this.#tabState.markTabAsRendered(activeIndex);\n\n // Load active file content in background and update when ready\n const contentElement = this.shadowRoot!.querySelector('section[role=\"tabpanel\"]');\n await this.#loadAndRenderTabContent(activeIndex, contentElement as HTMLElement).catch((error: unknown) => {\n console.error('Failed to load tab content:', error);\n (contentElement as HTMLElement).innerHTML = getErrorContentHtml(\n `Failed to load content: ${error instanceof Error ? error.message : String(error)}`\n );\n });\n }\n\n #switchToTab(newIndex: number): void {\n // Update tab buttons - use aria-selected and tabindex for accessibility\n updateTabButtonStates(this.shadowRoot!, newIndex);\n\n // Check if content for this tab already exists\n let contentElement = this.shadowRoot!.querySelector(`section[role=\"tabpanel\"][data-index=\"${newIndex}\"]`);\n\n if (!contentElement) {\n // Create skeleton immediately (lazy load pattern)\n contentElement = createTabPanel(newIndex, getSkeletonContentHtml());\n\n // Append skeleton to DOM immediately (user sees it right away)\n this.shadowRoot!.querySelector('article')!.appendChild(contentElement);\n this.#tabState.markTabAsRendered(newIndex);\n\n // Load content asynchronously and update when ready\n void this.#loadAndRenderTabContent(newIndex, contentElement as HTMLElement).catch((error: unknown) => {\n console.error('Failed to load tab content:', error);\n (contentElement as HTMLElement).innerHTML = getErrorContentHtml(\n `Failed to load content: ${error instanceof Error ? error.message : String(error)}`\n );\n });\n }\n\n // Update panels - use aria-hidden instead of .hidden class\n updateTabPanelStates(this.shadowRoot!, newIndex);\n }\n\n async #loadAndRenderTabContent(index: number, contentElement: HTMLElement): Promise<void> {\n // Load file content\n const file = this.#getFile(index);\n await ensureFileLoaded(file, false);\n\n // Replace skeleton with actual content\n if (file.error || !file.code) {\n const errorMsg = file.error || 'Failed to load content: No content available';\n contentElement.innerHTML = getErrorContentHtml(errorMsg, true);\n\n // Add retry button event listener\n const retryButton = contentElement.querySelector('.retry-button');\n if (retryButton) {\n retryButton.addEventListener('click', () => {\n void (async () => {\n // Show skeleton during retry\n contentElement.innerHTML = getSkeletonContentHtml();\n await ensureFileLoaded(file, true);\n await this.#loadAndRenderTabContent(index, contentElement);\n })();\n });\n }\n } else {\n contentElement.innerHTML = getCodeContentHtml(file.code);\n // Apply syntax highlighting\n if (window.hljs) {\n applySyntaxHighlighting(contentElement, file.code);\n }\n }\n }\n\n #showError(message: string): void {\n this.shadowRoot!.innerHTML = `\n <div class=\"error\">${escapeHtml(message)}</div>\n `;\n this.#applyStyleSheets(false);\n }\n}\n", "import { GitHubCode } from './github-code';\n\n// Register the custom element\ncustomElements.define('github-code', GitHubCode);\n\n// Export for potential programmatic usage\nexport { GitHubCode };\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,6 +1,7 @@
1
- function H(n){return n.split(",").map(e=>e.trim()).filter(e=>e.length>0)}var B=/^https:\/\/github\.com\/[^\/]+\/[^\/]+\/blob\/.+/;function L(n){return B.test(n)}function M(n){let e=/^https:\/\/github\.com\/([^\/]+)\/([^\/]+)\/blob\/([^\/]+)\/(.+)$/.exec(n);if(!e)throw new Error("Failed to parse GitHub URL");let t=e[1],r=e[2],o=e[3],i=e[4];if(!t||!r||!o||!i)throw new Error("Failed to parse GitHub URL");let a=`https://raw.githubusercontent.com/${t}/${r}/${o}/${i}`,c=i.split("/").pop()||"unknown";return{rawUrl:a,filename:c}}function E(n){try{return/\/([^\/]+)$/.exec(n)?.[1]??"unknown"}catch{return"unknown"}}async function D(n){try{let e=await fetch(n);if(!e.ok)throw new Error(`Failed to fetch code (HTTP ${e.status}). Please check if the URL is accessible.`);return await e.text()}catch(e){throw e instanceof TypeError&&e.message.includes("fetch")?new Error(`Failed to fetch code from ${n}. This is likely a CORS (Cross-Origin Resource Sharing) error. The server needs to allow requests from this origin. GitHub's raw.githubusercontent.com should work without CORS issues.`):e}}async function b(n,e=!1){if(!(n.loaded&&!e)){e&&(n.loaded=!1,n.error=null);try{let t=await D(n.rawUrl);n.code=t,n.error=null,n.loaded=!0}catch(t){n.code=null,n.error=t instanceof Error?t.message:String(t),n.loaded=!0}}}var h=null;async function R(){if(!window.hljs)return h||(h=new Promise((n,e)=>{let t=document.createElement("script");t.src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js",t.integrity="sha384-RH2xi4eIQ/gjtbs9fUXM68sLSi99C7ZWBRX1vDrVv6GQXRibxXLbwO2NGZB74MbU",t.crossOrigin="anonymous",t.onload=()=>{h=null,n()},t.onerror=()=>{h=null;let r=`Failed to load highlight.js library. If you have a Content Security Policy (CSP), ensure it allows:
1
+ function M(n){return n.split(",").map(e=>e.trim()).filter(e=>e.length>0)}var O=/^https:\/\/github\.com\/[^\/]+\/[^\/]+\/blob\/.+/;function R(n){return O.test(n)}function E(n){let e=/^https:\/\/github\.com\/([^\/]+)\/([^\/]+)\/blob\/([^\/]+)\/(.+)$/.exec(n);if(!e)throw new Error("Failed to parse GitHub URL");let t=e[1],r=e[2],o=e[3],i=e[4];if(!t||!r||!o||!i)throw new Error("Failed to parse GitHub URL");let a=`https://raw.githubusercontent.com/${t}/${r}/${o}/${i}`,h=i.split("/").pop()||"unknown";return{rawUrl:a,filename:h}}function C(n){try{return/\/([^\/]+)$/.exec(n)?.[1]??"unknown"}catch{return"unknown"}}async function _(n){try{let e=await fetch(n);if(!e.ok)throw new Error(`Failed to fetch code (HTTP ${e.status}). Please check if the URL is accessible.`);return await e.text()}catch(e){throw e instanceof TypeError&&e.message.includes("fetch")?new Error(`Failed to fetch code from ${n}. This is likely a CORS (Cross-Origin Resource Sharing) error. The server needs to allow requests from this origin. GitHub's raw.githubusercontent.com should work without CORS issues.`):e}}async function g(n,e=!1){if(!(n.loaded&&!e)){e&&(n.loaded=!1,n.error=null);try{let t=await _(n.rawUrl);n.code=t,n.error=null,n.loaded=!0}catch(t){n.code=null,n.error=t instanceof Error?t.message:String(t),n.loaded=!0}}}var u=null,l=null,p="global";function A(){return l||"auto"}function $(){return p}async function F(n){if(window.hljs){l||(p="global",l="auto");return}if(u)return n&&l&&n!==l&&console.warn(`[github-code] Different highlight.js URLs detected. Already loading from "${l}", but this instance requested "${n}". The first URL will be used for all instances.`),u;let t=n||"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js";return l=t,p=n?"user-provided":"cdn-default",u=new Promise((r,o)=>{let i=document.createElement("script");i.src=t,n||(i.integrity="sha384-RH2xi4eIQ/gjtbs9fUXM68sLSi99C7ZWBRX1vDrVv6GQXRibxXLbwO2NGZB74MbU",i.crossOrigin="anonymous"),i.onload=()=>{if(u=null,!window.hljs){l=null,p="global",o(new Error(`The script at "${t}" loaded successfully but did not define window.hljs. Please ensure this URL points to a valid highlight.js library.`));return}r()},i.onerror=()=>{u=null,l=null,p="global";let a=`Failed to load highlight.js library from: ${t}
2
+ `+(n?"Please check that the URL is correct and accessible.":`If you have a Content Security Policy (CSP), ensure it allows:
2
3
  script-src https://cdnjs.cloudflare.com
3
- style-src https://cdnjs.cloudflare.com`;e(new Error(r))},document.head.appendChild(t)}),h)}var C=`:host {
4
+ style-src https://cdnjs.cloudflare.com`);o(new Error(a))},document.head.appendChild(i)}),u}var U={name:"github-code",version:"0.1.0",description:"Custom element for embedding GitHub source files with syntax highlighting",type:"module",main:"dist/github-code.min.js",module:"dist/github-code.min.js",exports:{".":{import:"./dist/github-code.min.js",default:"./dist/github-code.min.js"},"./dist/*":"./dist/*"},files:["dist"],scripts:{build:"node esbuild.config.mjs",dev:"node esbuild.config.mjs --watch","type-check":"tsc --noEmit",lint:"eslint src","lint:fix":"eslint src --fix",format:'prettier --write "src/**/*.ts"',"format:check":'prettier --check "src/**/*.ts"',test:"vitest run","test:watch":"vitest","test:ui":"vitest --ui","test:coverage":"vitest run --coverage","pretest:e2e":"npm run build","test:e2e":"playwright test","test:e2e:ui":"playwright test --ui","test:e2e:debug":"playwright test --debug","test:e2e:headed":"playwright test --headed","test:all":"npm run test:coverage && npm run test:e2e","test:ci":"npm run lint && npm run type-check && npm run build && npm run test:all"},keywords:["web-component","github","code","syntax-highlighting"],author:"",license:"MIT",repository:{type:"git",url:"git+https://github.com/destan/github-code.git"},devDependencies:{"@eslint/js":"^9.39.2","@playwright/test":"^1.57.0","@semantic-release/changelog":"^6.0.3","@semantic-release/git":"^10.0.1","@types/node":"^25.0.2","@vitest/coverage-v8":"^4.0.15","@vitest/ui":"^4.0.15",esbuild:"0.27.1",eslint:"^9.39.2","eslint-config-prettier":"^10.1.8","happy-dom":"^20.0.11",prettier:"^3.7.4","semantic-release":"^24.2.9",typescript:"5.9.3","typescript-eslint":"^8.50.0",vitest:"^4.0.15"},engines:{node:">=24.12.0"}};var j=`:host {
4
5
  --border-color: #d0d7de;
5
6
  --header-background: #f6f8fa;
6
7
  --header-text-color: #24292f;
@@ -138,7 +139,7 @@ header {
138
139
  .skeleton-loading .line-number {
139
140
  opacity: 0.3;
140
141
  }
141
- `;var A=`:host {
142
+ `;var I=`:host {
142
143
  --border-color: #30363d;
143
144
  --header-background: #161b22;
144
145
  --header-text-color: #c9d1d9;
@@ -276,7 +277,7 @@ header {
276
277
  .skeleton-loading .line-number {
277
278
  opacity: 0.3;
278
279
  }
279
- `;var F=`nav[role='tablist'] {
280
+ `;var P=`nav[role='tablist'] {
280
281
  display: flex;
281
282
  background-color: var(--tabs-background);
282
283
  border-bottom: 1px solid var(--border-color);
@@ -316,7 +317,7 @@ section[role='tabpanel'] {
316
317
  section[role='tabpanel'][aria-hidden='true'] {
317
318
  display: none;
318
319
  }
319
- `;var l=class{static baseStylesLight=null;static baseStylesDark=null;static tabStyles=null;static getBaseStyleSheet(e){return e==="dark"?(this.baseStylesDark||(this.baseStylesDark=new CSSStyleSheet,this.baseStylesDark.replaceSync(A)),this.baseStylesDark):(this.baseStylesLight||(this.baseStylesLight=new CSSStyleSheet,this.baseStylesLight.replaceSync(C)),this.baseStylesLight)}static getTabStyleSheet(){return this.tabStyles||(this.tabStyles=new CSSStyleSheet,this.tabStyles.replaceSync(F)),this.tabStyles}static getHighlightJSThemeUrl(e){return`https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/${e==="dark"?"github-dark.min.css":"github.min.css"}`}};function s(n){let e=document.createElement("div");return e.textContent=n,e.innerHTML}function u(n,e=!1){let t=e?'<button class="retry-button">Retry</button>':"";return`<div class="error">${s(n)}${t}</div>`}function d(){return`
320
+ `;var d=class{static baseStylesLight=null;static baseStylesDark=null;static tabStyles=null;static getBaseStyleSheet(e){return e==="dark"?(this.baseStylesDark||(this.baseStylesDark=new CSSStyleSheet,this.baseStylesDark.replaceSync(I)),this.baseStylesDark):(this.baseStylesLight||(this.baseStylesLight=new CSSStyleSheet,this.baseStylesLight.replaceSync(j)),this.baseStylesLight)}static getTabStyleSheet(){return this.tabStyles||(this.tabStyles=new CSSStyleSheet,this.tabStyles.replaceSync(P)),this.tabStyles}static getHighlightJSThemeUrl(e){return`https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/${e==="dark"?"github-dark.min.css":"github.min.css"}`}};function s(n){let e=document.createElement("div");return e.textContent=n,e.innerHTML}function b(n,e=!1){let t=e?'<button class="retry-button">Retry</button>':"";return`<div class="error">${s(n)}${t}</div>`}function c(){return`
320
321
  <div class="code-wrapper skeleton-loading">
321
322
  <div class="code-table">
322
323
  ${Array.from({length:20},(e,t)=>{let r=60+Math.random()*30;return`
@@ -327,7 +328,7 @@ section[role='tabpanel'][aria-hidden='true'] {
327
328
  `}).join("")}
328
329
  </div>
329
330
  </div>
330
- `}function y(n){if(!n)return u("No code content available");let e=n.split(`
331
+ `}function w(n){if(!n)return b("No code content available");let e=n.split(`
331
332
  `);return e.length>0&&e[e.length-1]===""&&e.pop(),`
332
333
  <div class="code-wrapper hljs">
333
334
  <div class="code-table">
@@ -339,12 +340,12 @@ section[role='tabpanel'][aria-hidden='true'] {
339
340
  `).join("")}
340
341
  </div>
341
342
  </div>
342
- `}function x(n,e,t){return`<link rel="stylesheet" href="${t}">
343
+ `}function S(n,e,t){return`<link rel="stylesheet" href="${t}">
343
344
  <article>
344
345
  <header>${s(n)}</header>
345
346
  ${e}
346
- </article>`}function g(n,e){return`<header>${s(n)}</header>
347
- ${e}`}function w(n,e,t,r){return`<link rel="stylesheet" href="${r}">
347
+ </article>`}function f(n,e){return`<header>${s(n)}</header>
348
+ ${e}`}function k(n,e,t,r){return`<link rel="stylesheet" href="${r}">
348
349
  <article>
349
350
  <nav role="tablist" aria-label="Code files">${n}</nav>
350
351
  <section role="tabpanel"
@@ -353,10 +354,10 @@ section[role='tabpanel'][aria-hidden='true'] {
353
354
  data-index="${e}">
354
355
  ${t}
355
356
  </section>
356
- </article>`}function J(n){if(!n)return[""];let e=n.split(`
357
+ </article>`}function K(n){if(!n)return[""];let e=n.split(`
357
358
  `);e.length>0&&e[e.length-1]===""&&e.pop();let t=e.join(`
358
359
  `),o=window.hljs.highlightAuto(t).value.split(`
359
- `);for(;o.length>e.length;)o.pop();return o}function S(n,e){let t=n.querySelectorAll(".code-cell"),r=()=>{let o=J(e);t.forEach((i,a)=>{i.innerHTML=o[a]||" "})};"requestIdleCallback"in window?requestIdleCallback(r):r()}function $(n){return n==="dark"?"dark":n==="light"?"light":window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"}function U(n){let e=n.getAttribute("theme");return e==="dark"||e==="light"?e:"auto"}var m=class{activeTabIndex=0;renderedTabs=new Set;tabsFullyRendered=!1;getActiveTabIndex(){return this.activeTabIndex}setActiveTabIndex(e){this.activeTabIndex=e}areTabsFullyRendered(){return this.tabsFullyRendered}setTabsFullyRendered(e){this.tabsFullyRendered=e}isTabRendered(e){return this.renderedTabs.has(e)}markTabAsRendered(e){this.renderedTabs.add(e)}clearRenderedTabs(){this.renderedTabs.clear()}getTabCount(e){return e.length}reset(){this.renderedTabs.clear(),this.tabsFullyRendered=!1}};function k(n,e){return n.map((t,r)=>`
360
+ `);for(;o.length>e.length;)o.pop();return o}function T(n,e){let t=n.querySelectorAll(".code-cell"),r=()=>{let o=K(e);t.forEach((i,a)=>{i.innerHTML=o[a]||" "})};"requestIdleCallback"in window?requestIdleCallback(r):r()}function G(n){return n==="dark"?"dark":n==="light"?"light":window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"}function q(n){let e=n.getAttribute("theme");return e==="dark"||e==="light"?e:"auto"}var m=class{activeTabIndex=0;renderedTabs=new Set;tabsFullyRendered=!1;getActiveTabIndex(){return this.activeTabIndex}setActiveTabIndex(e){this.activeTabIndex=e}areTabsFullyRendered(){return this.tabsFullyRendered}setTabsFullyRendered(e){this.tabsFullyRendered=e}isTabRendered(e){return this.renderedTabs.has(e)}markTabAsRendered(e){this.renderedTabs.add(e)}clearRenderedTabs(){this.renderedTabs.clear()}getTabCount(e){return e.length}reset(){this.renderedTabs.clear(),this.tabsFullyRendered=!1}};function H(n,e){return n.map((t,r)=>`
360
361
  <button role="tab"
361
362
  id="tab-${r}"
362
363
  aria-selected="${r===e?"true":"false"}"
@@ -365,7 +366,7 @@ section[role='tabpanel'][aria-hidden='true'] {
365
366
  data-index="${r}">
366
367
  ${s(t.filename)}
367
368
  </button>
368
- `).join("")}function j(n,e){n.querySelectorAll("nav > button").forEach(r=>{let i=parseInt(r.dataset.index||"0")===e;r.setAttribute("aria-selected",i?"true":"false"),r.setAttribute("tabindex",i?"0":"-1")})}function P(n,e){n.querySelectorAll('section[role="tabpanel"]').forEach(r=>{let o=parseInt(r.dataset.index||"0");r.setAttribute("aria-hidden",o!==e?"true":"false")})}function I(n,e){let t=document.createElement("section");return t.setAttribute("role","tabpanel"),t.setAttribute("id",`panel-${n}`),t.setAttribute("aria-labelledby",`tab-${n}`),t.dataset.index=String(n),t.innerHTML=e,t}function q(n,e,t){switch(n){case"ArrowLeft":return e>0?e-1:t;case"ArrowRight":return e<t?e+1:0;case"Home":return 0;case"End":return t;default:return null}}var f=class extends HTMLElement{#t=[];#e=new m;#n=null;#a=null;#s=null;constructor(){super(),this.attachShadow({mode:"open"})}#l(e){let t=this.#t[e];if(!t)throw new Error(`File at index ${e} not found`);return t}static get observedAttributes(){return["file"]}connectedCallback(){this.#b(),this.#d()}disconnectedCallback(){this.#a&&this.#s&&this.#a.removeEventListener("change",this.#s)}attributeChangedCallback(e,t,r){if(e==="file"&&t!==null&&t!==r){let o=this.#e.getActiveTabIndex();this.#e.reset(),this.#d(),o<this.#t.length?this.#e.setActiveTabIndex(o):this.#e.setActiveTabIndex(0)}}#b(){window.matchMedia&&(this.#a=window.matchMedia("(prefers-color-scheme: dark)"),this.#s=()=>{this.#n=null,this.#d()},this.#a.addEventListener("change",this.#s))}#r(){return this.#n?this.#n:(this.#n=$(U(this)),this.#n)}#o(e=!1){let t=this.#r(),r=l.getBaseStyleSheet(t);if(e){let o=l.getTabStyleSheet();this.shadowRoot.adoptedStyleSheets=[r,o]}else this.shadowRoot.adoptedStyleSheets=[r]}async#d(){let e=this.getAttribute("file");if(!e){this.#i('Error: "file" attribute is required. Please provide a GitHub file URL.');return}let t=H(e);if(t.length===0){this.#i('Error: "file" attribute is required. Please provide a GitHub file URL.');return}let r=t.find(o=>!L(o));if(r){let o=s(r);this.#i(`Error: Invalid GitHub URL format: ${o}. Expected format: https://github.com/{owner}/{repo}/blob/{commit}/{path}`);return}if(this.#t=t.map(o=>{try{let{rawUrl:i,filename:a}=M(o);return{filename:a,rawUrl:i,url:o,code:null,error:null,loaded:!1}}catch(i){return{filename:E(o),rawUrl:o,url:o,code:null,error:i instanceof Error?i.message:String(i),loaded:!0}}}),t.length===1){let o=this.#l(0);if(o.error){this.#i(`Error loading code: ${o.error}`);return}this.shadowRoot.innerHTML=x(o.filename,d(),l.getHighlightJSThemeUrl(this.#r())),this.#o(!1)}else this.#g();try{await R()}catch(o){let i=o instanceof Error?o.message:String(o);this.#i(`Error loading highlight.js: ${i}`);return}t.length===1?await this.#u(0):await this.#c()}async#u(e){let t=this.#l(e),r=l.getHighlightJSThemeUrl(this.#r());this.shadowRoot.innerHTML=x(t.filename,d(),r),this.#o(!1),await b(t,!1);let o=this.shadowRoot.querySelector("article");if(t.error){o.innerHTML=g(t.filename,u(t.error,!0));let i=o.querySelector(".retry-button");i&&i.addEventListener("click",()=>{(async()=>(o.innerHTML=g(t.filename,d()),await b(t,!0),await this.#u(e)))()})}else o.innerHTML=g(t.filename,y(t.code)),window.hljs&&S(this.shadowRoot,t.code)}async#c(){this.#e.areTabsFullyRendered()?this.#f(this.#e.getActiveTabIndex()):(await this.#m(),this.#e.setTabsFullyRendered(!0))}#g(){let e=this.#e.getActiveTabIndex(),t=k(this.#t,e),r=l.getHighlightJSThemeUrl(this.#r());this.shadowRoot.innerHTML=w(t,e,d(),r),this.#o(!0)}async#m(){let e=this.#e.getActiveTabIndex(),t=k(this.#t,e),r=l.getHighlightJSThemeUrl(this.#r());this.shadowRoot.innerHTML=w(t,e,d(),r),this.#o(!0);let o=this.shadowRoot.querySelector('nav[role="tablist"]');o.addEventListener("click",a=>{let c=a.target.closest('button[role="tab"]');if(c){let p=parseInt(c.dataset.index||"0");p!==this.#e.getActiveTabIndex()&&(this.#e.setActiveTabIndex(p),this.#c())}}),o.addEventListener("keydown",a=>{let c=a;if(!c.target.closest('button[role="tab"]'))return;let G=this.#e.getActiveTabIndex(),z=this.#t.length-1,v=q(c.key,G,z);v!==null&&(a.preventDefault(),this.#e.setActiveTabIndex(v),this.#c().then(()=>{let T=this.shadowRoot.querySelector(`button[data-index="${v}"]`);T&&T.focus()}))}),this.#e.markTabAsRendered(e);let i=this.shadowRoot.querySelector('section[role="tabpanel"]');await this.#h(e,i).catch(a=>{console.error("Failed to load tab content:",a),i.innerHTML=u(`Failed to load content: ${a instanceof Error?a.message:String(a)}`)})}#f(e){j(this.shadowRoot,e);let t=this.shadowRoot.querySelector(`section[role="tabpanel"][data-index="${e}"]`);t||(t=I(e,d()),this.shadowRoot.querySelector("article").appendChild(t),this.#e.markTabAsRendered(e),this.#h(e,t).catch(r=>{console.error("Failed to load tab content:",r),t.innerHTML=u(`Failed to load content: ${r instanceof Error?r.message:String(r)}`)})),P(this.shadowRoot,e)}async#h(e,t){let r=this.#l(e);if(await b(r,!1),r.error||!r.code){let o=r.error||"Failed to load content: No content available";t.innerHTML=u(o,!0);let i=t.querySelector(".retry-button");i&&i.addEventListener("click",()=>{(async()=>(t.innerHTML=d(),await b(r,!0),await this.#h(e,t)))()})}else t.innerHTML=y(r.code),window.hljs&&S(t,r.code)}#i(e){this.shadowRoot.innerHTML=`
369
+ `).join("")}function J(n,e){n.querySelectorAll("nav > button").forEach(r=>{let i=parseInt(r.dataset.index||"0")===e;r.setAttribute("aria-selected",i?"true":"false"),r.setAttribute("tabindex",i?"0":"-1")})}function z(n,e){n.querySelectorAll('section[role="tabpanel"]').forEach(r=>{let o=parseInt(r.dataset.index||"0");r.setAttribute("aria-hidden",o!==e?"true":"false")})}function B(n,e){let t=document.createElement("section");return t.setAttribute("role","tabpanel"),t.setAttribute("id",`panel-${n}`),t.setAttribute("aria-labelledby",`tab-${n}`),t.dataset.index=String(n),t.innerHTML=e,t}function D(n,e,t){switch(n){case"ArrowLeft":return e>0?e-1:t;case"ArrowRight":return e<t?e+1:0;case"Home":return 0;case"End":return t;default:return null}}var v=class extends HTMLElement{#t=[];#e=new m;#n=null;#a=null;#s=null;constructor(){super(),this.attachShadow({mode:"open"})}#d(e){let t=this.#t[e];if(!t)throw new Error(`File at index ${e} not found`);return t}static get observedAttributes(){return["file","theme"]}static get info(){return{version:U.version,highlightjsUrl:A(),highlightjsSource:$()}}connectedCallback(){this.#b(),this.#l()}disconnectedCallback(){this.#a&&this.#s&&this.#a.removeEventListener("change",this.#s)}attributeChangedCallback(e,t,r){if(!(t===null||t===r))if(e==="file"){let o=this.#e.getActiveTabIndex();this.#e.reset(),this.#l(),o<this.#t.length?this.#e.setActiveTabIndex(o):this.#e.setActiveTabIndex(0)}else e==="theme"&&(this.#n=null,this.#l())}#b(){window.matchMedia&&(this.#a=window.matchMedia("(prefers-color-scheme: dark)"),this.#s=()=>{this.#n=null,this.#l()},this.#a.addEventListener("change",this.#s))}#r(){return this.#n?this.#n:(this.#n=G(q(this)),this.#n)}#o(e=!1){let t=this.#r(),r=d.getBaseStyleSheet(t);if(e){let o=d.getTabStyleSheet();this.shadowRoot.adoptedStyleSheets=[r,o]}else this.shadowRoot.adoptedStyleSheets=[r]}async#l(){let e=this.getAttribute("file");if(!e){this.#i('Error: "file" attribute is required. Please provide a GitHub file URL.');return}let t=M(e);if(t.length===0){this.#i('Error: "file" attribute is required. Please provide a GitHub file URL.');return}let r=t.find(o=>!R(o));if(r){let o=s(r);this.#i(`Error: Invalid GitHub URL format: ${o}. Expected format: https://github.com/{owner}/{repo}/blob/{commit}/{path}`);return}if(this.#t=t.map(o=>{try{let{rawUrl:i,filename:a}=E(o);return{filename:a,rawUrl:i,url:o,code:null,error:null,loaded:!1}}catch(i){return{filename:C(o),rawUrl:o,url:o,code:null,error:i instanceof Error?i.message:String(i),loaded:!0}}}),t.length===1){let o=this.#d(0);if(o.error){this.#i(`Error loading code: ${o.error}`);return}this.shadowRoot.innerHTML=S(o.filename,c(),d.getHighlightJSThemeUrl(this.#r())),this.#o(!1)}else this.#g();try{let o=this.getAttribute("highlightjs-url");await F(o&&o!=="auto"?o:void 0)}catch(o){let i=o instanceof Error?o.message:String(o);this.#i(`Error loading highlight.js: ${i}`);return}t.length===1?await this.#u(0):await this.#c()}async#u(e){let t=this.#d(e),r=d.getHighlightJSThemeUrl(this.#r());this.shadowRoot.innerHTML=S(t.filename,c(),r),this.#o(!1),await g(t,!1);let o=this.shadowRoot.querySelector("article");if(t.error){o.innerHTML=f(t.filename,b(t.error,!0));let i=o.querySelector(".retry-button");i&&i.addEventListener("click",()=>{(async()=>(o.innerHTML=f(t.filename,c()),await g(t,!0),await this.#u(e)))()})}else o.innerHTML=f(t.filename,w(t.code)),window.hljs&&T(this.shadowRoot,t.code)}async#c(){this.#e.areTabsFullyRendered()?this.#f(this.#e.getActiveTabIndex()):(await this.#p(),this.#e.setTabsFullyRendered(!0))}#g(){let e=this.#e.getActiveTabIndex(),t=H(this.#t,e),r=d.getHighlightJSThemeUrl(this.#r());this.shadowRoot.innerHTML=k(t,e,c(),r),this.#o(!0)}async#p(){let e=this.#e.getActiveTabIndex(),t=H(this.#t,e),r=d.getHighlightJSThemeUrl(this.#r());this.shadowRoot.innerHTML=k(t,e,c(),r),this.#o(!0);let o=this.shadowRoot.querySelector('nav[role="tablist"]');o.addEventListener("click",a=>{let h=a.target.closest('button[role="tab"]');if(h){let y=parseInt(h.dataset.index||"0");y!==this.#e.getActiveTabIndex()&&(this.#e.setActiveTabIndex(y),this.#c())}}),o.addEventListener("keydown",a=>{let h=a;if(!h.target.closest('button[role="tab"]'))return;let N=this.#e.getActiveTabIndex(),Q=this.#t.length-1,x=D(h.key,N,Q);x!==null&&(a.preventDefault(),this.#e.setActiveTabIndex(x),this.#c().then(()=>{let L=this.shadowRoot.querySelector(`button[data-index="${x}"]`);L&&L.focus()}))}),this.#e.markTabAsRendered(e);let i=this.shadowRoot.querySelector('section[role="tabpanel"]');await this.#h(e,i).catch(a=>{console.error("Failed to load tab content:",a),i.innerHTML=b(`Failed to load content: ${a instanceof Error?a.message:String(a)}`)})}#f(e){J(this.shadowRoot,e);let t=this.shadowRoot.querySelector(`section[role="tabpanel"][data-index="${e}"]`);t||(t=B(e,c()),this.shadowRoot.querySelector("article").appendChild(t),this.#e.markTabAsRendered(e),this.#h(e,t).catch(r=>{console.error("Failed to load tab content:",r),t.innerHTML=b(`Failed to load content: ${r instanceof Error?r.message:String(r)}`)})),z(this.shadowRoot,e)}async#h(e,t){let r=this.#d(e);if(await g(r,!1),r.error||!r.code){let o=r.error||"Failed to load content: No content available";t.innerHTML=b(o,!0);let i=t.querySelector(".retry-button");i&&i.addEventListener("click",()=>{(async()=>(t.innerHTML=c(),await g(r,!0),await this.#h(e,t)))()})}else t.innerHTML=w(r.code),window.hljs&&T(t,r.code)}#i(e){this.shadowRoot.innerHTML=`
369
370
  <div class="error">${s(e)}</div>
370
- `,this.#o(!1)}};customElements.define("github-code",f);export{f as GitHubCode};
371
+ `,this.#o(!1)}};customElements.define("github-code",v);export{v as GitHubCode};
371
372
  //# sourceMappingURL=github-code.min.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/parsers/file-parser.ts", "../src/parsers/url-parser.ts", "../src/fetching/code-fetcher.ts", "../src/fetching/highlightjs-loader.ts", "../src/styles/base-light.css", "../src/styles/base-dark.css", "../src/styles/tab.css", "../src/styles/stylesheet-manager.ts", "../src/rendering/html-generators.ts", "../src/rendering/template-generators.ts", "../src/rendering/syntax-highlighter.ts", "../src/theme/theme-resolver.ts", "../src/tabs/tab-state.ts", "../src/tabs/tab-controller.ts", "../src/github-code.ts", "../src/index.ts"],
4
- "sourcesContent": ["/**\n * Parses the comma-separated file attribute into individual URLs\n */\nexport function parseFileAttribute(fileAttr: string): string[] {\n return fileAttr\n .split(',')\n .map((url) => url.trim())\n .filter((url) => url.length > 0);\n}\n", "import type { GitHubUrlParts } from '../types';\n\n/**\n * Regex pattern to validate GitHub blob URLs\n */\n// eslint-disable-next-line no-useless-escape\nconst GITHUB_BLOB_PATTERN = /^https:\\/\\/github\\.com\\/[^\\/]+\\/[^\\/]+\\/blob\\/.+/;\n\n/**\n * Checks if a URL is a valid GitHub blob URL\n */\nexport function isValidGitHubUrl(url: string): boolean {\n return GITHUB_BLOB_PATTERN.test(url);\n}\n\n/**\n * Parses a GitHub blob URL into its components\n * @throws {Error} If the URL cannot be parsed\n */\nexport function parseGitHubUrl(url: string): GitHubUrlParts {\n // eslint-disable-next-line no-useless-escape\n const match = /^https:\\/\\/github\\.com\\/([^\\/]+)\\/([^\\/]+)\\/blob\\/([^\\/]+)\\/(.+)$/.exec(url);\n\n if (!match) {\n throw new Error('Failed to parse GitHub URL');\n }\n\n const owner = match[1];\n const repo = match[2];\n const commit = match[3];\n const path = match[4];\n\n if (!owner || !repo || !commit || !path) {\n throw new Error('Failed to parse GitHub URL');\n }\n\n const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${commit}/${path}`;\n\n const filename = path.split('/').pop() || 'unknown';\n\n return {\n rawUrl,\n filename,\n };\n}\n\n/**\n * Extracts filename from any URL (fallback for non-standard URLs)\n */\nexport function extractFilenameFromUrl(url: string): string {\n try {\n // eslint-disable-next-line no-useless-escape\n const match = /\\/([^\\/]+)$/.exec(url);\n return match?.[1] ?? 'unknown';\n } catch {\n return 'unknown';\n }\n}\n", "import type { FileMetadata } from '../types';\n\n/**\n * Fetches code content from a URL\n * @throws {Error} If the fetch fails (network error, HTTP error, CORS error)\n */\nexport async function fetchCode(url: string): Promise<string> {\n try {\n const response = await fetch(url);\n\n if (!response.ok) {\n throw new Error(`Failed to fetch code (HTTP ${response.status}). Please check if the URL is accessible.`);\n }\n\n return await response.text();\n } catch (error) {\n // Detect CORS errors specifically\n if (error instanceof TypeError && error.message.includes('fetch')) {\n throw new Error(\n `Failed to fetch code from ${url}. ` +\n `This is likely a CORS (Cross-Origin Resource Sharing) error. ` +\n `The server needs to allow requests from this origin. ` +\n `GitHub's raw.githubusercontent.com should work without CORS issues.`\n );\n }\n throw error;\n }\n}\n\n/**\n * Ensures a file's content is loaded, fetching it if necessary\n * @param file The file metadata object to load\n * @param isRetry Whether this is a retry attempt (will force re-fetch)\n */\nexport async function ensureFileLoaded(file: FileMetadata, isRetry = false): Promise<void> {\n // Already loaded (skip if not a retry)\n if (file.loaded && !isRetry) {\n return;\n }\n\n // If retrying, reset the loaded flag to allow re-fetch\n if (isRetry) {\n file.loaded = false;\n file.error = null;\n }\n\n try {\n const code = await fetchCode(file.rawUrl);\n file.code = code;\n file.error = null;\n file.loaded = true;\n } catch (error) {\n file.code = null;\n file.error = error instanceof Error ? error.message : String(error);\n file.loaded = true; // Mark as loaded so we show error (can retry later)\n }\n}\n", "/**\n * Shared promise to prevent multiple simultaneous loads of highlight.js\n */\nlet highlightJSLoadingPromise: Promise<void> | null = null;\n\n/**\n * Type definition for highlight.js on window object\n */\ndeclare global {\n interface Window {\n hljs?: {\n highlightAuto: (code: string) => { value: string };\n };\n }\n}\n\n/**\n * Loads highlight.js library from CDN if not already loaded.\n * Uses a singleton pattern to ensure only one load attempt happens at a time.\n */\nexport async function loadHighlightJS(): Promise<void> {\n // If already loaded, return immediately\n if (window.hljs) {\n return;\n }\n\n // If currently loading, return the existing promise\n if (highlightJSLoadingPromise) {\n return highlightJSLoadingPromise;\n }\n\n // Create new loading promise\n highlightJSLoadingPromise = new Promise((resolve, reject) => {\n const script = document.createElement('script');\n script.src = 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js';\n script.integrity = 'sha384-RH2xi4eIQ/gjtbs9fUXM68sLSi99C7ZWBRX1vDrVv6GQXRibxXLbwO2NGZB74MbU';\n script.crossOrigin = 'anonymous';\n script.onload = () => {\n highlightJSLoadingPromise = null; // Clear after successful load\n resolve();\n };\n script.onerror = () => {\n highlightJSLoadingPromise = null; // Clear on error to allow retry\n const errorMsg =\n 'Failed to load highlight.js library. ' +\n 'If you have a Content Security Policy (CSP), ensure it allows:\\n' +\n ' script-src https://cdnjs.cloudflare.com\\n' +\n ' style-src https://cdnjs.cloudflare.com';\n reject(new Error(errorMsg));\n };\n document.head.appendChild(script);\n });\n\n return highlightJSLoadingPromise;\n}\n", "export default \":host {\\n --border-color: #d0d7de;\\n --header-background: #f6f8fa;\\n --header-text-color: #24292f;\\n --line-number-color: #57606a;\\n --tab-color: #57606a;\\n --tab-hover-border: #d0d7de;\\n --tabs-background: #f6f8fa;\\n --code-background: #ffffff;\\n --skeleton-base: #e0e0e0;\\n --skeleton-highlight: #f0f0f0;\\n --error-text-color: #cf222e;\\n --error-background: #ffebe9;\\n --error-border: #ff8182;\\n --button-background: #f6f8fa;\\n --button-text-color: #24292f;\\n --button-border: #d0d7de;\\n --button-hover-background: #f3f4f6;\\n --font-family-base:\\n -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif, 'Apple Color Emoji',\\n 'Segoe UI Emoji';\\n --font-family-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;\\n\\n display: block;\\n min-width: 0;\\n font-family: var(--font-family-base);\\n}\\n\\narticle {\\n border: 1px solid var(--border-color);\\n border-radius: 6px;\\n overflow: hidden;\\n background-color: var(--code-background);\\n}\\n\\nheader {\\n background-color: var(--header-background);\\n padding: 8px 16px;\\n border-bottom: 1px solid var(--border-color);\\n color: var(--header-text-color);\\n font-weight: 600;\\n font-size: 14px;\\n}\\n\\n.code-wrapper {\\n width: 100%;\\n margin: 0;\\n overflow-x: auto;\\n background-color: var(--code-background);\\n}\\n\\n.code-table {\\n display: flex;\\n flex-direction: column;\\n width: 100%;\\n min-width: 0;\\n}\\n\\n.code-row {\\n display: flex;\\n}\\n\\n.line-number {\\n flex-shrink: 0;\\n text-align: right;\\n padding-right: 6px;\\n padding-left: 8px;\\n color: var(--line-number-color);\\n user-select: none;\\n border-right: 1px solid var(--border-color);\\n font-family: var(--font-family-mono);\\n font-size: 12px;\\n line-height: 20px;\\n min-width: 40px;\\n}\\n\\n.code-cell {\\n flex: 1;\\n min-width: 0;\\n padding-left: 2px;\\n font-family: var(--font-family-mono);\\n font-size: 12px;\\n line-height: 20px;\\n white-space: pre;\\n}\\n\\n.error {\\n padding: 16px;\\n color: var(--error-text-color);\\n background-color: var(--error-background);\\n border: 1px solid var(--error-border);\\n border-radius: 6px;\\n font-family: var(--font-family-mono);\\n font-size: 12px;\\n line-height: 1.5;\\n margin: 16px;\\n}\\n\\n.retry-button {\\n margin-top: 12px;\\n padding: 8px 16px;\\n background: var(--button-background);\\n color: var(--button-text-color);\\n border: 1px solid var(--button-border);\\n border-radius: 6px;\\n cursor: pointer;\\n font-size: 14px;\\n font-family: var(--font-family-base);\\n font-weight: 500;\\n transition: background-color 0.2s ease;\\n}\\n\\n.retry-button:hover {\\n background-color: var(--button-hover-background);\\n}\\n\\n/* Skeleton loader styles */\\n.skeleton-line {\\n height: 12px;\\n background: linear-gradient(90deg, var(--skeleton-base) 25%, var(--skeleton-highlight) 50%, var(--skeleton-base) 75%);\\n background-size: 200% 100%;\\n animation: loading 1.5s ease-in-out infinite;\\n border-radius: 4px;\\n margin: 4px 0;\\n}\\n\\n@keyframes loading {\\n 0% {\\n background-position: 200% 0;\\n }\\n 100% {\\n background-position: -200% 0;\\n }\\n}\\n\\n.skeleton-loading .line-number {\\n opacity: 0.3;\\n}\\n\"", "export default \":host {\\n --border-color: #30363d;\\n --header-background: #161b22;\\n --header-text-color: #c9d1d9;\\n --line-number-color: #8b949e;\\n --tab-color: #8b949e;\\n --tab-hover-border: #30363d;\\n --tabs-background: #161b22;\\n --code-background: #0d1117;\\n --skeleton-base: #21262d;\\n --skeleton-highlight: #30363d;\\n --error-text-color: #ff7b72;\\n --error-background: #490202;\\n --error-border: #f85149;\\n --button-background: #21262d;\\n --button-text-color: #c9d1d9;\\n --button-border: #30363d;\\n --button-hover-background: #30363d;\\n --font-family-base:\\n -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif, 'Apple Color Emoji',\\n 'Segoe UI Emoji';\\n --font-family-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;\\n\\n display: block;\\n min-width: 0;\\n font-family: var(--font-family-base);\\n}\\n\\narticle {\\n border: 1px solid var(--border-color);\\n border-radius: 6px;\\n overflow: hidden;\\n background-color: var(--code-background);\\n}\\n\\nheader {\\n background-color: var(--header-background);\\n padding: 8px 16px;\\n border-bottom: 1px solid var(--border-color);\\n color: var(--header-text-color);\\n font-weight: 600;\\n font-size: 14px;\\n}\\n\\n.code-wrapper {\\n width: 100%;\\n margin: 0;\\n overflow-x: auto;\\n background-color: var(--code-background);\\n}\\n\\n.code-table {\\n display: flex;\\n flex-direction: column;\\n width: 100%;\\n min-width: 0;\\n}\\n\\n.code-row {\\n display: flex;\\n}\\n\\n.line-number {\\n flex-shrink: 0;\\n text-align: right;\\n padding-right: 6px;\\n padding-left: 8px;\\n color: var(--line-number-color);\\n user-select: none;\\n border-right: 1px solid var(--border-color);\\n font-family: var(--font-family-mono);\\n font-size: 12px;\\n line-height: 20px;\\n min-width: 40px;\\n}\\n\\n.code-cell {\\n flex: 1;\\n min-width: 0;\\n padding-left: 2px;\\n font-family: var(--font-family-mono);\\n font-size: 12px;\\n line-height: 20px;\\n white-space: pre;\\n}\\n\\n.error {\\n padding: 16px;\\n color: var(--error-text-color);\\n background-color: var(--error-background);\\n border: 1px solid var(--error-border);\\n border-radius: 6px;\\n font-family: var(--font-family-mono);\\n font-size: 12px;\\n line-height: 1.5;\\n margin: 16px;\\n}\\n\\n.retry-button {\\n margin-top: 12px;\\n padding: 8px 16px;\\n background: var(--button-background);\\n color: var(--button-text-color);\\n border: 1px solid var(--button-border);\\n border-radius: 6px;\\n cursor: pointer;\\n font-size: 14px;\\n font-family: var(--font-family-base);\\n font-weight: 500;\\n transition: background-color 0.2s ease;\\n}\\n\\n.retry-button:hover {\\n background-color: var(--button-hover-background);\\n}\\n\\n/* Skeleton loader styles */\\n.skeleton-line {\\n height: 12px;\\n background: linear-gradient(90deg, var(--skeleton-base) 25%, var(--skeleton-highlight) 50%, var(--skeleton-base) 75%);\\n background-size: 200% 100%;\\n animation: loading 1.5s ease-in-out infinite;\\n border-radius: 4px;\\n margin: 4px 0;\\n}\\n\\n@keyframes loading {\\n 0% {\\n background-position: 200% 0;\\n }\\n 100% {\\n background-position: -200% 0;\\n }\\n}\\n\\n.skeleton-loading .line-number {\\n opacity: 0.3;\\n}\\n\"", "export default \"nav[role='tablist'] {\\n display: flex;\\n background-color: var(--tabs-background);\\n border-bottom: 1px solid var(--border-color);\\n overflow-x: auto;\\n}\\n\\nnav > button {\\n padding: 8px 16px;\\n background: none;\\n border: none;\\n border-bottom: 2px solid transparent;\\n color: var(--tab-color);\\n font-size: 14px;\\n font-family: var(--font-family-base);\\n cursor: pointer;\\n white-space: nowrap;\\n transition:\\n color 0.1s ease,\\n border-color 0.1s ease;\\n}\\n\\nnav > button:hover {\\n color: var(--header-text-color);\\n border-bottom-color: var(--tab-hover-border);\\n}\\n\\nnav > button[aria-selected='true'] {\\n color: var(--header-text-color);\\n border-bottom-color: #fd8c73;\\n font-weight: 600;\\n}\\n\\nsection[role='tabpanel'] {\\n display: block;\\n}\\n\\nsection[role='tabpanel'][aria-hidden='true'] {\\n display: none;\\n}\\n\"", "import baseLightCss from './base-light.css';\nimport baseDarkCss from './base-dark.css';\nimport tabCss from './tab.css';\nimport type { ResolvedTheme } from '../types';\n\n/**\n * Manages constructable stylesheets for the component.\n * Implements caching to share stylesheet instances across all component instances.\n */\nexport class StylesheetManager {\n private static baseStylesLight: CSSStyleSheet | null = null;\n private static baseStylesDark: CSSStyleSheet | null = null;\n private static tabStyles: CSSStyleSheet | null = null;\n\n /**\n * Gets the base stylesheet for the specified theme (light or dark).\n * Lazy-loads and caches the stylesheet on first access.\n */\n static getBaseStyleSheet(theme: ResolvedTheme): CSSStyleSheet {\n if (theme === 'dark') {\n if (!this.baseStylesDark) {\n this.baseStylesDark = new CSSStyleSheet();\n this.baseStylesDark.replaceSync(baseDarkCss);\n }\n return this.baseStylesDark;\n } else {\n if (!this.baseStylesLight) {\n this.baseStylesLight = new CSSStyleSheet();\n this.baseStylesLight.replaceSync(baseLightCss);\n }\n return this.baseStylesLight;\n }\n }\n\n /**\n * Gets the tab stylesheet.\n * Lazy-loads and caches the stylesheet on first access.\n */\n static getTabStyleSheet(): CSSStyleSheet {\n if (!this.tabStyles) {\n this.tabStyles = new CSSStyleSheet();\n this.tabStyles.replaceSync(tabCss);\n }\n return this.tabStyles;\n }\n\n /**\n * Gets the highlight.js theme URL for the specified theme.\n */\n static getHighlightJSThemeUrl(theme: ResolvedTheme): string {\n const themeFile = theme === 'dark' ? 'github-dark.min.css' : 'github.min.css';\n return `https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/${themeFile}`;\n }\n}\n", "/**\n * Escapes HTML special characters to prevent XSS\n */\nexport function escapeHtml(text: string): string {\n const div = document.createElement('div');\n div.textContent = text;\n return div.innerHTML;\n}\n\n/**\n * Generates HTML for error display\n */\nexport function getErrorContentHtml(errorMessage: string, showRetry = false): string {\n const retryButton = showRetry ? `<button class=\"retry-button\">Retry</button>` : '';\n return `<div class=\"error\">${escapeHtml(errorMessage)}${retryButton}</div>`;\n}\n\n/**\n * Generates HTML for skeleton loading state\n */\nexport function getSkeletonContentHtml(): string {\n // Generate skeleton lines that look like code\n const skeletonLines = Array.from({ length: 20 }, (_, i) => {\n // Random width between 60-90%\n // Using Math.random() is acceptable here for non-cryptographic purposes\n const width = 60 + Math.random() * 30; // NOSONAR\n return `\n <div class=\"code-row\">\n <div class=\"line-number\">${i + 1}</div>\n <div class=\"skeleton-line\" style=\"width: ${width}%\"></div>\n </div>\n `;\n }).join('');\n\n return `\n <div class=\"code-wrapper skeleton-loading\">\n <div class=\"code-table\">\n ${skeletonLines}\n </div>\n </div>\n `;\n}\n\n/**\n * Generates HTML for code display (without syntax highlighting)\n */\nexport function getCodeContentHtml(code: string | null): string {\n // Safety check for null/undefined code\n if (!code) {\n return getErrorContentHtml('No code content available');\n }\n\n const lines = code.split('\\n');\n\n // Remove only the last empty line if it exists (from final newline)\n if (lines.length > 0 && lines[lines.length - 1] === '') {\n lines.pop();\n }\n\n return `\n <div class=\"code-wrapper hljs\">\n <div class=\"code-table\">\n ${lines\n .map(\n (line, index) => `\n <div class=\"code-row\">\n <div class=\"line-number\">${index + 1}</div>\n <div class=\"code-cell\">${escapeHtml(line) || ' '}</div>\n </div>\n `\n )\n .join('')}\n </div>\n </div>\n `;\n}\n", "import { escapeHtml } from './html-generators';\n\n/**\n * Generates the outer HTML structure for a single file display.\n *\n * @param filename - The filename to show in the header\n * @param content - The inner content HTML (skeleton, code, or error)\n * @param themeStylesheetUrl - URL for the highlight.js theme CSS\n * @returns Complete HTML string for single file layout\n */\nexport function generateSingleFileTemplate(filename: string, content: string, themeStylesheetUrl: string): string {\n return `<link rel=\"stylesheet\" href=\"${themeStylesheetUrl}\">\n<article>\n <header>${escapeHtml(filename)}</header>\n ${content}\n</article>`;\n}\n\n/**\n * Generates the article content for a single file (header + content).\n * Used when updating only the article innards, not the full template.\n *\n * @param filename - The filename to show in the header\n * @param content - The inner content HTML\n * @returns HTML string for article content\n */\nexport function generateArticleContent(filename: string, content: string): string {\n return `<header>${escapeHtml(filename)}</header>\n ${content}`;\n}\n\n/**\n * Generates the outer HTML structure for tabbed file display.\n *\n * @param tabsHtml - Pre-generated HTML for tab buttons\n * @param activeIndex - Index of the currently active tab\n * @param panelContent - Content HTML for the active panel\n * @param themeStylesheetUrl - URL for the highlight.js theme CSS\n * @returns Complete HTML string for tabbed layout\n */\nexport function generateTabbedTemplate(\n tabsHtml: string,\n activeIndex: number,\n panelContent: string,\n themeStylesheetUrl: string\n): string {\n return `<link rel=\"stylesheet\" href=\"${themeStylesheetUrl}\">\n<article>\n <nav role=\"tablist\" aria-label=\"Code files\">${tabsHtml}</nav>\n <section role=\"tabpanel\"\n id=\"panel-${activeIndex}\"\n aria-labelledby=\"tab-${activeIndex}\"\n data-index=\"${activeIndex}\">\n ${panelContent}\n </section>\n</article>`;\n}\n", "/**\n * Gets syntax-highlighted lines from code using highlight.js\n */\nexport function getHighlightedLines(code: string | null): string[] {\n // Safety check for null/undefined code\n if (!code) {\n return [''];\n }\n\n const lines = code.split('\\n');\n\n // Remove only the last empty line if it exists (from final newline)\n if (lines.length > 0 && lines[lines.length - 1] === '') {\n lines.pop();\n }\n\n const fullCode = lines.join('\\n');\n const result = window.hljs!.highlightAuto(fullCode);\n const highlightedLines = result.value.split('\\n');\n\n // Ensure highlightedLines matches lines length\n while (highlightedLines.length > lines.length) {\n highlightedLines.pop();\n }\n\n return highlightedLines;\n}\n\n/**\n * Applies syntax highlighting to code cells within a container\n */\nexport function applySyntaxHighlighting(container: Element | ShadowRoot, code: string): void {\n const codeCells = container.querySelectorAll('.code-cell');\n\n const applyHighlighting = (): void => {\n const highlightedLines = getHighlightedLines(code);\n codeCells.forEach((cell, index) => {\n (cell as HTMLElement).innerHTML = highlightedLines[index] || ' ';\n });\n };\n\n // Use requestIdleCallback to avoid blocking the main thread\n if ('requestIdleCallback' in window) {\n requestIdleCallback(applyHighlighting);\n } else {\n applyHighlighting();\n }\n}\n", "import type { Theme, ResolvedTheme } from '../types';\n\n/**\n * Resolves a theme attribute to an actual theme value.\n * Handles 'auto' theme by detecting system preference.\n *\n * @param themeAttr - The theme attribute value ('light', 'dark', or 'auto')\n * @returns The resolved theme ('light' or 'dark')\n */\nexport function resolveTheme(themeAttr: Theme): ResolvedTheme {\n if (themeAttr === 'dark') {\n return 'dark';\n }\n\n if (themeAttr === 'light') {\n return 'light';\n }\n\n // auto - detect system preference\n const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;\n return prefersDark ? 'dark' : 'light';\n}\n\n/**\n * Gets the theme attribute from an element, defaulting to 'auto'.\n *\n * @param element - The HTML element to get the theme from\n * @returns The theme value\n */\nexport function getThemeAttribute(element: HTMLElement): Theme {\n const attr = element.getAttribute('theme');\n if (attr === 'dark' || attr === 'light') {\n return attr;\n }\n return 'auto';\n}\n\n/**\n * Creates a theme change handler that clears cached theme and triggers re-render.\n *\n * @param onThemeChange - Callback to invoke when theme changes\n * @returns Object with setup and cleanup functions for theme media query listener\n */\nexport function createThemeListener(onThemeChange: () => void): {\n setup: () => MediaQueryList | null;\n cleanup: (mediaQuery: MediaQueryList | null) => void;\n handler: () => void;\n} {\n const handler = onThemeChange;\n\n return {\n setup: () => {\n if (window.matchMedia) {\n const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');\n mediaQuery.addEventListener('change', handler);\n return mediaQuery;\n }\n return null;\n },\n cleanup: (mediaQuery: MediaQueryList | null) => {\n if (mediaQuery) {\n mediaQuery.removeEventListener('change', handler);\n }\n },\n handler,\n };\n}\n", "/**\n * Manages tab state for the component\n */\nexport class TabState {\n private activeTabIndex = 0;\n private renderedTabs = new Set<number>();\n private tabsFullyRendered = false;\n\n /**\n * Gets the currently active tab index\n */\n getActiveTabIndex(): number {\n return this.activeTabIndex;\n }\n\n /**\n * Sets the active tab index\n */\n setActiveTabIndex(index: number): void {\n this.activeTabIndex = index;\n }\n\n /**\n * Checks if tabs structure is fully rendered with event listeners\n */\n areTabsFullyRendered(): boolean {\n return this.tabsFullyRendered;\n }\n\n /**\n * Marks tabs as fully rendered\n */\n setTabsFullyRendered(value: boolean): void {\n this.tabsFullyRendered = value;\n }\n\n /**\n * Checks if a tab has been rendered\n */\n isTabRendered(index: number): boolean {\n return this.renderedTabs.has(index);\n }\n\n /**\n * Marks a tab as rendered\n */\n markTabAsRendered(index: number): void {\n this.renderedTabs.add(index);\n }\n\n /**\n * Clears all rendered tabs\n */\n clearRenderedTabs(): void {\n this.renderedTabs.clear();\n }\n\n /**\n * Gets the total number of tabs\n */\n getTabCount(files: unknown[]): number {\n return files.length;\n }\n\n /**\n * Resets tab state (useful when re-parsing files)\n */\n reset(): void {\n this.renderedTabs.clear();\n this.tabsFullyRendered = false;\n // Note: activeTabIndex is NOT reset here - that's handled by the caller\n }\n}\n", "import type { FileMetadata } from '../types';\nimport { escapeHtml } from '../rendering/html-generators';\n\n/**\n * Generates HTML for tab buttons\n */\nexport function generateTabsHtml(files: FileMetadata[], activeTabIndex: number): string {\n return files\n .map(\n (file, index) => `\n <button role=\"tab\"\n id=\"tab-${index}\"\n aria-selected=\"${index === activeTabIndex ? 'true' : 'false'}\"\n aria-controls=\"panel-${index}\"\n tabindex=\"${index === activeTabIndex ? '0' : '-1'}\"\n data-index=\"${index}\">\n ${escapeHtml(file.filename)}\n </button>\n `\n )\n .join('');\n}\n\n/**\n * Updates tab button states (aria attributes) when switching tabs\n */\nexport function updateTabButtonStates(shadowRoot: ShadowRoot, newActiveIndex: number): void {\n const tabs = shadowRoot.querySelectorAll('nav > button');\n tabs.forEach((tab) => {\n const tabIndex = parseInt((tab as HTMLElement).dataset.index || '0');\n const isSelected = tabIndex === newActiveIndex;\n tab.setAttribute('aria-selected', isSelected ? 'true' : 'false');\n tab.setAttribute('tabindex', isSelected ? '0' : '-1');\n });\n}\n\n/**\n * Updates tab panel visibility states when switching tabs\n */\nexport function updateTabPanelStates(shadowRoot: ShadowRoot, activeIndex: number): void {\n const allPanels = shadowRoot.querySelectorAll('section[role=\"tabpanel\"]');\n allPanels.forEach((panel) => {\n const panelIndex = parseInt((panel as HTMLElement).dataset.index || '0');\n panel.setAttribute('aria-hidden', panelIndex !== activeIndex ? 'true' : 'false');\n });\n}\n\n/**\n * Creates a new tab panel element for lazy loading\n */\nexport function createTabPanel(index: number, skeletonHtml: string): HTMLElement {\n const contentElement = document.createElement('section');\n contentElement.setAttribute('role', 'tabpanel');\n contentElement.setAttribute('id', `panel-${index}`);\n contentElement.setAttribute('aria-labelledby', `tab-${index}`);\n contentElement.dataset.index = String(index);\n contentElement.innerHTML = skeletonHtml;\n return contentElement;\n}\n\n/**\n * Handles keyboard navigation for tabs (Arrow keys, Home, End)\n * Returns the new index if navigation should occur, null otherwise\n */\nexport function handleTabKeyNavigation(key: string, currentIndex: number, maxIndex: number): number | null {\n switch (key) {\n case 'ArrowLeft':\n return currentIndex > 0 ? currentIndex - 1 : maxIndex;\n case 'ArrowRight':\n return currentIndex < maxIndex ? currentIndex + 1 : 0;\n case 'Home':\n return 0;\n case 'End':\n return maxIndex;\n default:\n return null;\n }\n}\n", "import type { FileMetadata, ResolvedTheme } from './types';\nimport { parseFileAttribute } from './parsers/file-parser';\nimport { isValidGitHubUrl, parseGitHubUrl, extractFilenameFromUrl } from './parsers/url-parser';\nimport { ensureFileLoaded } from './fetching/code-fetcher';\nimport { loadHighlightJS } from './fetching/highlightjs-loader';\nimport { StylesheetManager } from './styles/stylesheet-manager';\nimport {\n escapeHtml,\n getErrorContentHtml,\n getSkeletonContentHtml,\n getCodeContentHtml,\n} from './rendering/html-generators';\nimport {\n generateSingleFileTemplate,\n generateArticleContent,\n generateTabbedTemplate,\n} from './rendering/template-generators';\nimport { applySyntaxHighlighting } from './rendering/syntax-highlighter';\nimport { resolveTheme, getThemeAttribute } from './theme/theme-resolver';\nimport { TabState } from './tabs/tab-state';\nimport {\n generateTabsHtml,\n updateTabButtonStates,\n updateTabPanelStates,\n createTabPanel,\n handleTabKeyNavigation,\n} from './tabs/tab-controller';\n\n/**\n * GitHub Code Web Component\n * Displays GitHub file URLs with syntax highlighting\n */\nexport class GitHubCode extends HTMLElement {\n // Private fields\n #files: FileMetadata[] = [];\n #tabState = new TabState();\n #resolvedTheme: ResolvedTheme | null = null;\n #themeMediaQuery: MediaQueryList | null = null;\n #themeChangeHandler: (() => void) | null = null;\n\n constructor() {\n super();\n this.attachShadow({ mode: 'open' });\n }\n\n /**\n * Safely gets a file by index, throwing if out of bounds.\n * This provides type-safe array access with noUncheckedIndexedAccess.\n */\n #getFile(index: number): FileMetadata {\n const file = this.#files[index];\n if (!file) {\n throw new Error(`File at index ${index} not found`);\n }\n return file;\n }\n\n static get observedAttributes(): string[] {\n return ['file'];\n }\n\n connectedCallback(): void {\n // Set up theme change listener for reactive theme updates\n this.#setupThemeListener();\n\n // Initial render (will parse files and display)\n void this.#render();\n }\n\n disconnectedCallback(): void {\n // Clean up theme change listener to prevent memory leaks\n if (this.#themeMediaQuery && this.#themeChangeHandler) {\n this.#themeMediaQuery.removeEventListener('change', this.#themeChangeHandler);\n }\n }\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n // Skip initial attribute set (oldValue is null) - connectedCallback handles initial render\n if (name === 'file' && oldValue !== null && oldValue !== newValue) {\n // Store current active tab index before re-parsing files\n const previousIndex = this.#tabState.getActiveTabIndex();\n\n // Re-parse files (clear renderedTabs since structure will be rebuilt)\n this.#tabState.reset();\n\n // Re-render with new file URLs\n void this.#render();\n\n // Try to preserve tab index if it still exists\n if (previousIndex < this.#files.length) {\n this.#tabState.setActiveTabIndex(previousIndex);\n } else {\n // Reset to first tab if previous index no longer exists\n this.#tabState.setActiveTabIndex(0);\n }\n }\n }\n\n // Private methods - theming\n #setupThemeListener(): void {\n if (window.matchMedia) {\n this.#themeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');\n this.#themeChangeHandler = () => {\n this.#resolvedTheme = null; // Clear cached theme\n void this.#render(); // Re-render with new theme\n };\n this.#themeMediaQuery.addEventListener('change', this.#themeChangeHandler);\n }\n }\n\n #getResolvedTheme(): ResolvedTheme {\n if (this.#resolvedTheme) {\n return this.#resolvedTheme;\n }\n\n this.#resolvedTheme = resolveTheme(getThemeAttribute(this));\n return this.#resolvedTheme;\n }\n\n // Apply constructable stylesheets to shadow root (CSP-compliant, no 'unsafe-inline' needed)\n #applyStyleSheets(includeTabStyles = false): void {\n const theme = this.#getResolvedTheme();\n const baseSheet = StylesheetManager.getBaseStyleSheet(theme);\n\n if (includeTabStyles) {\n const tabSheet = StylesheetManager.getTabStyleSheet();\n this.shadowRoot!.adoptedStyleSheets = [baseSheet, tabSheet];\n } else {\n this.shadowRoot!.adoptedStyleSheets = [baseSheet];\n }\n }\n\n // Private methods - rendering\n async #render(): Promise<void> {\n const fileAttr = this.getAttribute('file');\n\n if (!fileAttr) {\n this.#showError('Error: \"file\" attribute is required. Please provide a GitHub file URL.');\n return;\n }\n\n const fileUrls = parseFileAttribute(fileAttr);\n\n if (fileUrls.length === 0) {\n this.#showError('Error: \"file\" attribute is required. Please provide a GitHub file URL.');\n return;\n }\n\n // Validate all URLs\n const invalidUrl = fileUrls.find((url) => !isValidGitHubUrl(url));\n if (invalidUrl) {\n // Sanitize URL before using in error message to prevent XSS\n const sanitizedUrl = escapeHtml(invalidUrl);\n this.#showError(\n `Error: Invalid GitHub URL format: ${sanitizedUrl}. Expected format: https://github.com/{owner}/{repo}/blob/{commit}/{path}`\n );\n return;\n }\n\n // Parse URLs to get file metadata first (don't fetch content yet - lazy load on demand)\n this.#files = fileUrls.map((url) => {\n try {\n const { rawUrl, filename } = parseGitHubUrl(url);\n return {\n filename,\n rawUrl,\n url,\n code: null,\n error: null,\n loaded: false,\n };\n } catch (error) {\n const filename = extractFilenameFromUrl(url);\n return {\n filename,\n rawUrl: url,\n url,\n code: null,\n error: error instanceof Error ? error.message : String(error),\n loaded: true, // Parse error - no point retrying\n };\n }\n });\n\n // Render skeleton UI immediately (tabs or single file structure)\n if (fileUrls.length === 1) {\n // Single file: show header + skeleton\n const file = this.#getFile(0);\n if (file.error) {\n this.#showError(`Error loading code: ${file.error}`);\n return;\n }\n // Show skeleton immediately\n this.shadowRoot!.innerHTML = generateSingleFileTemplate(\n file.filename,\n getSkeletonContentHtml(),\n StylesheetManager.getHighlightJSThemeUrl(this.#getResolvedTheme())\n );\n this.#applyStyleSheets(false);\n } else {\n // Multiple files: show tabs + skeleton immediately\n this.#renderTabsSkeletonStructure();\n }\n\n // Load highlight.js in background (UI already visible)\n try {\n await loadHighlightJS();\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : String(error);\n this.#showError(`Error loading highlight.js: ${errorMsg}`);\n return;\n }\n\n // Now load actual content\n if (fileUrls.length === 1) {\n await this.#displayCode(0);\n } else {\n await this.#displayWithTabs();\n }\n }\n\n async #displayCode(index: number): Promise<void> {\n const file = this.#getFile(index);\n const themeUrl = StylesheetManager.getHighlightJSThemeUrl(this.#getResolvedTheme());\n\n // Render skeleton immediately\n this.shadowRoot!.innerHTML = generateSingleFileTemplate(file.filename, getSkeletonContentHtml(), themeUrl);\n this.#applyStyleSheets(false);\n\n // Load content in background and update when ready\n await ensureFileLoaded(file, false);\n\n const contentArea = this.shadowRoot!.querySelector('article');\n if (file.error) {\n contentArea!.innerHTML = generateArticleContent(file.filename, getErrorContentHtml(file.error, true));\n // Add retry button event listener\n const retryButton = contentArea!.querySelector('.retry-button');\n if (retryButton) {\n retryButton.addEventListener('click', () => {\n void (async () => {\n // Show skeleton during retry\n contentArea!.innerHTML = generateArticleContent(file.filename, getSkeletonContentHtml());\n await ensureFileLoaded(file, true);\n await this.#displayCode(index);\n })();\n });\n }\n } else {\n contentArea!.innerHTML = generateArticleContent(file.filename, getCodeContentHtml(file.code));\n if (window.hljs) {\n applySyntaxHighlighting(this.shadowRoot!, file.code!);\n }\n }\n }\n\n async #displayWithTabs(): Promise<void> {\n if (!this.#tabState.areTabsFullyRendered()) {\n // First full render - build entire structure with event listeners\n await this.#renderTabsStructure();\n this.#tabState.setTabsFullyRendered(true);\n } else {\n // Tab switching - update only what's needed\n this.#switchToTab(this.#tabState.getActiveTabIndex());\n }\n }\n\n #renderTabsSkeletonStructure(): void {\n const activeIndex = this.#tabState.getActiveTabIndex();\n const tabsHtml = generateTabsHtml(this.#files, activeIndex);\n const themeUrl = StylesheetManager.getHighlightJSThemeUrl(this.#getResolvedTheme());\n\n this.shadowRoot!.innerHTML = generateTabbedTemplate(tabsHtml, activeIndex, getSkeletonContentHtml(), themeUrl);\n this.#applyStyleSheets(true);\n }\n\n async #renderTabsStructure(): Promise<void> {\n const activeIndex = this.#tabState.getActiveTabIndex();\n const tabsHtml = generateTabsHtml(this.#files, activeIndex);\n const themeUrl = StylesheetManager.getHighlightJSThemeUrl(this.#getResolvedTheme());\n\n // Render tabs and skeleton immediately\n this.shadowRoot!.innerHTML = generateTabbedTemplate(tabsHtml, activeIndex, getSkeletonContentHtml(), themeUrl);\n this.#applyStyleSheets(true);\n\n // Use event delegation on parent nav element to prevent race conditions\n const nav = this.shadowRoot!.querySelector('nav[role=\"tablist\"]');\n nav!.addEventListener('click', (e) => {\n const tab = (e.target as Element).closest('button[role=\"tab\"]');\n if (tab) {\n const newIndex = parseInt((tab as HTMLElement).dataset.index || '0');\n if (newIndex !== this.#tabState.getActiveTabIndex()) {\n this.#tabState.setActiveTabIndex(newIndex);\n void this.#displayWithTabs();\n }\n }\n });\n\n // Add keyboard navigation for accessibility\n nav!.addEventListener('keydown', (e) => {\n const keyboardEvent = e as KeyboardEvent;\n const tab = (keyboardEvent.target as Element).closest('button[role=\"tab\"]');\n if (!tab) {\n return;\n }\n\n const currentIndex = this.#tabState.getActiveTabIndex();\n const maxIndex = this.#files.length - 1;\n\n const newIndex = handleTabKeyNavigation(keyboardEvent.key, currentIndex, maxIndex);\n\n if (newIndex !== null) {\n e.preventDefault();\n this.#tabState.setActiveTabIndex(newIndex);\n void this.#displayWithTabs().then(() => {\n // Focus the newly selected tab\n const newTab = this.shadowRoot!.querySelector(`button[data-index=\"${newIndex}\"]`);\n if (newTab) {\n (newTab as HTMLElement).focus();\n }\n });\n }\n });\n\n // Mark this tab as rendered\n this.#tabState.markTabAsRendered(activeIndex);\n\n // Load active file content in background and update when ready\n const contentElement = this.shadowRoot!.querySelector('section[role=\"tabpanel\"]');\n await this.#loadAndRenderTabContent(activeIndex, contentElement as HTMLElement).catch((error: unknown) => {\n console.error('Failed to load tab content:', error);\n (contentElement as HTMLElement).innerHTML = getErrorContentHtml(\n `Failed to load content: ${error instanceof Error ? error.message : String(error)}`\n );\n });\n }\n\n #switchToTab(newIndex: number): void {\n // Update tab buttons - use aria-selected and tabindex for accessibility\n updateTabButtonStates(this.shadowRoot!, newIndex);\n\n // Check if content for this tab already exists\n let contentElement = this.shadowRoot!.querySelector(`section[role=\"tabpanel\"][data-index=\"${newIndex}\"]`);\n\n if (!contentElement) {\n // Create skeleton immediately (lazy load pattern)\n contentElement = createTabPanel(newIndex, getSkeletonContentHtml());\n\n // Append skeleton to DOM immediately (user sees it right away)\n this.shadowRoot!.querySelector('article')!.appendChild(contentElement);\n this.#tabState.markTabAsRendered(newIndex);\n\n // Load content asynchronously and update when ready\n void this.#loadAndRenderTabContent(newIndex, contentElement as HTMLElement).catch((error: unknown) => {\n console.error('Failed to load tab content:', error);\n (contentElement as HTMLElement).innerHTML = getErrorContentHtml(\n `Failed to load content: ${error instanceof Error ? error.message : String(error)}`\n );\n });\n }\n\n // Update panels - use aria-hidden instead of .hidden class\n updateTabPanelStates(this.shadowRoot!, newIndex);\n }\n\n async #loadAndRenderTabContent(index: number, contentElement: HTMLElement): Promise<void> {\n // Load file content\n const file = this.#getFile(index);\n await ensureFileLoaded(file, false);\n\n // Replace skeleton with actual content\n if (file.error || !file.code) {\n const errorMsg = file.error || 'Failed to load content: No content available';\n contentElement.innerHTML = getErrorContentHtml(errorMsg, true);\n\n // Add retry button event listener\n const retryButton = contentElement.querySelector('.retry-button');\n if (retryButton) {\n retryButton.addEventListener('click', () => {\n void (async () => {\n // Show skeleton during retry\n contentElement.innerHTML = getSkeletonContentHtml();\n await ensureFileLoaded(file, true);\n await this.#loadAndRenderTabContent(index, contentElement);\n })();\n });\n }\n } else {\n contentElement.innerHTML = getCodeContentHtml(file.code);\n // Apply syntax highlighting\n if (window.hljs) {\n applySyntaxHighlighting(contentElement, file.code);\n }\n }\n }\n\n #showError(message: string): void {\n this.shadowRoot!.innerHTML = `\n <div class=\"error\">${escapeHtml(message)}</div>\n `;\n this.#applyStyleSheets(false);\n }\n}\n", "import { GitHubCode } from './github-code';\n\n// Register the custom element\ncustomElements.define('github-code', GitHubCode);\n\n// Export for potential programmatic usage\nexport { GitHubCode };\n"],
5
- "mappings": "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",
6
- "names": ["parseFileAttribute", "fileAttr", "url", "GITHUB_BLOB_PATTERN", "isValidGitHubUrl", "url", "parseGitHubUrl", "match", "owner", "repo", "commit", "path", "rawUrl", "filename", "extractFilenameFromUrl", "fetchCode", "url", "response", "error", "ensureFileLoaded", "file", "isRetry", "code", "highlightJSLoadingPromise", "loadHighlightJS", "resolve", "reject", "script", "errorMsg", "base_light_default", "base_dark_default", "tab_default", "StylesheetManager", "theme", "base_dark_default", "base_light_default", "tab_default", "escapeHtml", "text", "div", "getErrorContentHtml", "errorMessage", "showRetry", "retryButton", "getSkeletonContentHtml", "_", "i", "width", "getCodeContentHtml", "code", "lines", "line", "index", "generateSingleFileTemplate", "filename", "content", "themeStylesheetUrl", "escapeHtml", "generateArticleContent", "generateTabbedTemplate", "tabsHtml", "activeIndex", "panelContent", "getHighlightedLines", "code", "lines", "fullCode", "highlightedLines", "applySyntaxHighlighting", "container", "codeCells", "applyHighlighting", "cell", "index", "resolveTheme", "themeAttr", "getThemeAttribute", "element", "attr", "TabState", "index", "value", "files", "generateTabsHtml", "files", "activeTabIndex", "file", "index", "escapeHtml", "updateTabButtonStates", "shadowRoot", "newActiveIndex", "tab", "isSelected", "updateTabPanelStates", "activeIndex", "panel", "panelIndex", "createTabPanel", "skeletonHtml", "contentElement", "handleTabKeyNavigation", "key", "currentIndex", "maxIndex", "GitHubCode", "#files", "#tabState", "TabState", "#resolvedTheme", "#themeMediaQuery", "#themeChangeHandler", "#getFile", "index", "file", "#setupThemeListener", "#render", "name", "oldValue", "newValue", "previousIndex", "#getResolvedTheme", "resolveTheme", "getThemeAttribute", "#applyStyleSheets", "includeTabStyles", "theme", "baseSheet", "StylesheetManager", "tabSheet", "fileAttr", "#showError", "fileUrls", "parseFileAttribute", "invalidUrl", "url", "isValidGitHubUrl", "sanitizedUrl", "escapeHtml", "rawUrl", "filename", "parseGitHubUrl", "error", "extractFilenameFromUrl", "generateSingleFileTemplate", "getSkeletonContentHtml", "#renderTabsSkeletonStructure", "loadHighlightJS", "errorMsg", "#displayCode", "#displayWithTabs", "themeUrl", "ensureFileLoaded", "contentArea", "generateArticleContent", "getErrorContentHtml", "retryButton", "getCodeContentHtml", "applySyntaxHighlighting", "#switchToTab", "#renderTabsStructure", "activeIndex", "tabsHtml", "generateTabsHtml", "generateTabbedTemplate", "nav", "e", "tab", "newIndex", "keyboardEvent", "currentIndex", "maxIndex", "handleTabKeyNavigation", "newTab", "contentElement", "#loadAndRenderTabContent", "updateTabButtonStates", "createTabPanel", "updateTabPanelStates", "message", "GitHubCode"]
3
+ "sources": ["../src/parsers/file-parser.ts", "../src/parsers/url-parser.ts", "../src/fetching/code-fetcher.ts", "../src/fetching/highlightjs-loader.ts", "../package.json", "../src/styles/base-light.css", "../src/styles/base-dark.css", "../src/styles/tab.css", "../src/styles/stylesheet-manager.ts", "../src/rendering/html-generators.ts", "../src/rendering/template-generators.ts", "../src/rendering/syntax-highlighter.ts", "../src/theme/theme-resolver.ts", "../src/tabs/tab-state.ts", "../src/tabs/tab-controller.ts", "../src/github-code.ts", "../src/index.ts"],
4
+ "sourcesContent": ["/**\n * Parses the comma-separated file attribute into individual URLs\n */\nexport function parseFileAttribute(fileAttr: string): string[] {\n return fileAttr\n .split(',')\n .map((url) => url.trim())\n .filter((url) => url.length > 0);\n}\n", "import type { GitHubUrlParts } from '../types';\n\n/**\n * Regex pattern to validate GitHub blob URLs\n */\n// eslint-disable-next-line no-useless-escape\nconst GITHUB_BLOB_PATTERN = /^https:\\/\\/github\\.com\\/[^\\/]+\\/[^\\/]+\\/blob\\/.+/;\n\n/**\n * Checks if a URL is a valid GitHub blob URL\n */\nexport function isValidGitHubUrl(url: string): boolean {\n return GITHUB_BLOB_PATTERN.test(url);\n}\n\n/**\n * Parses a GitHub blob URL into its components\n * @throws {Error} If the URL cannot be parsed\n */\nexport function parseGitHubUrl(url: string): GitHubUrlParts {\n // eslint-disable-next-line no-useless-escape\n const match = /^https:\\/\\/github\\.com\\/([^\\/]+)\\/([^\\/]+)\\/blob\\/([^\\/]+)\\/(.+)$/.exec(url);\n\n if (!match) {\n throw new Error('Failed to parse GitHub URL');\n }\n\n const owner = match[1];\n const repo = match[2];\n const commit = match[3];\n const path = match[4];\n\n if (!owner || !repo || !commit || !path) {\n throw new Error('Failed to parse GitHub URL');\n }\n\n const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${commit}/${path}`;\n\n const filename = path.split('/').pop() || 'unknown';\n\n return {\n rawUrl,\n filename,\n };\n}\n\n/**\n * Extracts filename from any URL (fallback for non-standard URLs)\n */\nexport function extractFilenameFromUrl(url: string): string {\n try {\n // eslint-disable-next-line no-useless-escape\n const match = /\\/([^\\/]+)$/.exec(url);\n return match?.[1] ?? 'unknown';\n } catch {\n return 'unknown';\n }\n}\n", "import type { FileMetadata } from '../types';\n\n/**\n * Fetches code content from a URL\n * @throws {Error} If the fetch fails (network error, HTTP error, CORS error)\n */\nexport async function fetchCode(url: string): Promise<string> {\n try {\n const response = await fetch(url);\n\n if (!response.ok) {\n throw new Error(`Failed to fetch code (HTTP ${response.status}). Please check if the URL is accessible.`);\n }\n\n return await response.text();\n } catch (error) {\n // Detect CORS errors specifically\n if (error instanceof TypeError && error.message.includes('fetch')) {\n throw new Error(\n `Failed to fetch code from ${url}. ` +\n `This is likely a CORS (Cross-Origin Resource Sharing) error. ` +\n `The server needs to allow requests from this origin. ` +\n `GitHub's raw.githubusercontent.com should work without CORS issues.`\n );\n }\n throw error;\n }\n}\n\n/**\n * Ensures a file's content is loaded, fetching it if necessary\n * @param file The file metadata object to load\n * @param isRetry Whether this is a retry attempt (will force re-fetch)\n */\nexport async function ensureFileLoaded(file: FileMetadata, isRetry = false): Promise<void> {\n // Already loaded (skip if not a retry)\n if (file.loaded && !isRetry) {\n return;\n }\n\n // If retrying, reset the loaded flag to allow re-fetch\n if (isRetry) {\n file.loaded = false;\n file.error = null;\n }\n\n try {\n const code = await fetchCode(file.rawUrl);\n file.code = code;\n file.error = null;\n file.loaded = true;\n } catch (error) {\n file.code = null;\n file.error = error instanceof Error ? error.message : String(error);\n file.loaded = true; // Mark as loaded so we show error (can retry later)\n }\n}\n", "/**\n * Shared promise to prevent multiple simultaneous loads of highlight.js\n */\nlet highlightJSLoadingPromise: Promise<void> | null = null;\n\n/**\n * Track which URL was loaded for introspection\n */\nlet loadedHighlightJSUrl: string | null = null;\n\n/**\n * Track the source of the loaded highlight.js library\n */\nlet highlightJSSource: 'user-provided' | 'cdn-default' | 'global' = 'global';\n\n/**\n * Type definition for highlight.js on window object\n */\ndeclare global {\n interface Window {\n hljs?: {\n highlightAuto: (code: string) => { value: string };\n };\n }\n}\n\n/**\n * Get the URL that was loaded (or will be loaded)\n * Returns \"auto\" if using default behavior or global hljs\n */\nexport function getLoadedHighlightJSUrl(): string {\n return loadedHighlightJSUrl || 'auto';\n}\n\n/**\n * Get the source of the highlight.js library\n */\nexport function getHighlightJSSource(): 'user-provided' | 'cdn-default' | 'global' {\n return highlightJSSource;\n}\n\n/**\n * Loads highlight.js library from CDN if not already loaded.\n * Uses a singleton pattern to ensure only one load attempt happens at a time.\n * @param customUrl - Optional custom URL to load highlight.js from\n */\nexport async function loadHighlightJS(customUrl?: string): Promise<void> {\n // If already loaded globally, track and return\n if (window.hljs) {\n if (!loadedHighlightJSUrl) {\n highlightJSSource = 'global';\n loadedHighlightJSUrl = 'auto';\n }\n return;\n }\n\n // If currently loading, check for URL conflicts and return the existing promise\n if (highlightJSLoadingPromise) {\n if (customUrl && loadedHighlightJSUrl && customUrl !== loadedHighlightJSUrl) {\n console.warn(\n `[github-code] Different highlight.js URLs detected. ` +\n `Already loading from \"${loadedHighlightJSUrl}\", ` +\n `but this instance requested \"${customUrl}\". ` +\n `The first URL will be used for all instances.`\n );\n }\n return highlightJSLoadingPromise;\n }\n\n // Determine which URL to use\n const DEFAULT_URL = 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js';\n const urlToLoad = customUrl || DEFAULT_URL;\n\n // Track state\n loadedHighlightJSUrl = urlToLoad;\n highlightJSSource = customUrl ? 'user-provided' : 'cdn-default';\n\n // Create new loading promise\n highlightJSLoadingPromise = new Promise((resolve, reject) => {\n const script = document.createElement('script');\n script.src = urlToLoad;\n\n // Only add integrity for default CDN URL\n if (!customUrl) {\n script.integrity = 'sha384-RH2xi4eIQ/gjtbs9fUXM68sLSi99C7ZWBRX1vDrVv6GQXRibxXLbwO2NGZB74MbU';\n script.crossOrigin = 'anonymous';\n }\n\n script.onload = () => {\n highlightJSLoadingPromise = null;\n\n // Validate that window.hljs was defined\n if (!window.hljs) {\n loadedHighlightJSUrl = null;\n highlightJSSource = 'global';\n reject(\n new Error(\n `The script at \"${urlToLoad}\" loaded successfully but did not define window.hljs. ` +\n `Please ensure this URL points to a valid highlight.js library.`\n )\n );\n return;\n }\n\n resolve();\n };\n\n script.onerror = () => {\n highlightJSLoadingPromise = null;\n loadedHighlightJSUrl = null;\n highlightJSSource = 'global';\n\n const errorMsg =\n `Failed to load highlight.js library from: ${urlToLoad}\\n` +\n (customUrl\n ? 'Please check that the URL is correct and accessible.'\n : 'If you have a Content Security Policy (CSP), ensure it allows:\\n' +\n ' script-src https://cdnjs.cloudflare.com\\n' +\n ' style-src https://cdnjs.cloudflare.com');\n reject(new Error(errorMsg));\n };\n\n document.head.appendChild(script);\n });\n\n return highlightJSLoadingPromise;\n}\n", "{\n \"name\": \"github-code\",\n \"version\": \"0.1.0\",\n \"description\": \"Custom element for embedding GitHub source files with syntax highlighting\",\n \"type\": \"module\",\n \"main\": \"dist/github-code.min.js\",\n \"module\": \"dist/github-code.min.js\",\n \"exports\": {\n \".\": {\n \"import\": \"./dist/github-code.min.js\",\n \"default\": \"./dist/github-code.min.js\"\n },\n \"./dist/*\": \"./dist/*\"\n },\n \"files\": [\n \"dist\"\n ],\n \"scripts\": {\n \"build\": \"node esbuild.config.mjs\",\n \"dev\": \"node esbuild.config.mjs --watch\",\n \"type-check\": \"tsc --noEmit\",\n \"lint\": \"eslint src\",\n \"lint:fix\": \"eslint src --fix\",\n \"format\": \"prettier --write \\\"src/**/*.ts\\\"\",\n \"format:check\": \"prettier --check \\\"src/**/*.ts\\\"\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"test:ui\": \"vitest --ui\",\n \"test:coverage\": \"vitest run --coverage\",\n \"pretest:e2e\": \"npm run build\",\n \"test:e2e\": \"playwright test\",\n \"test:e2e:ui\": \"playwright test --ui\",\n \"test:e2e:debug\": \"playwright test --debug\",\n \"test:e2e:headed\": \"playwright test --headed\",\n \"test:all\": \"npm run test:coverage && npm run test:e2e\",\n \"test:ci\": \"npm run lint && npm run type-check && npm run build && npm run test:all\"\n },\n \"keywords\": [\n \"web-component\",\n \"github\",\n \"code\",\n \"syntax-highlighting\"\n ],\n \"author\": \"\",\n \"license\": \"MIT\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/destan/github-code.git\"\n },\n \"devDependencies\": {\n \"@eslint/js\": \"^9.39.2\",\n \"@playwright/test\": \"^1.57.0\",\n \"@semantic-release/changelog\": \"^6.0.3\",\n \"@semantic-release/git\": \"^10.0.1\",\n \"@types/node\": \"^25.0.2\",\n \"@vitest/coverage-v8\": \"^4.0.15\",\n \"@vitest/ui\": \"^4.0.15\",\n \"esbuild\": \"0.27.1\",\n \"eslint\": \"^9.39.2\",\n \"eslint-config-prettier\": \"^10.1.8\",\n \"happy-dom\": \"^20.0.11\",\n \"prettier\": \"^3.7.4\",\n \"semantic-release\": \"^24.2.9\",\n \"typescript\": \"5.9.3\",\n \"typescript-eslint\": \"^8.50.0\",\n \"vitest\": \"^4.0.15\"\n },\n \"engines\": {\n \"node\": \">=24.12.0\"\n }\n}\n", "export default \":host {\\n --border-color: #d0d7de;\\n --header-background: #f6f8fa;\\n --header-text-color: #24292f;\\n --line-number-color: #57606a;\\n --tab-color: #57606a;\\n --tab-hover-border: #d0d7de;\\n --tabs-background: #f6f8fa;\\n --code-background: #ffffff;\\n --skeleton-base: #e0e0e0;\\n --skeleton-highlight: #f0f0f0;\\n --error-text-color: #cf222e;\\n --error-background: #ffebe9;\\n --error-border: #ff8182;\\n --button-background: #f6f8fa;\\n --button-text-color: #24292f;\\n --button-border: #d0d7de;\\n --button-hover-background: #f3f4f6;\\n --font-family-base:\\n -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif, 'Apple Color Emoji',\\n 'Segoe UI Emoji';\\n --font-family-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;\\n\\n display: block;\\n min-width: 0;\\n font-family: var(--font-family-base);\\n}\\n\\narticle {\\n border: 1px solid var(--border-color);\\n border-radius: 6px;\\n overflow: hidden;\\n background-color: var(--code-background);\\n}\\n\\nheader {\\n background-color: var(--header-background);\\n padding: 8px 16px;\\n border-bottom: 1px solid var(--border-color);\\n color: var(--header-text-color);\\n font-weight: 600;\\n font-size: 14px;\\n}\\n\\n.code-wrapper {\\n width: 100%;\\n margin: 0;\\n overflow-x: auto;\\n background-color: var(--code-background);\\n}\\n\\n.code-table {\\n display: flex;\\n flex-direction: column;\\n width: 100%;\\n min-width: 0;\\n}\\n\\n.code-row {\\n display: flex;\\n}\\n\\n.line-number {\\n flex-shrink: 0;\\n text-align: right;\\n padding-right: 6px;\\n padding-left: 8px;\\n color: var(--line-number-color);\\n user-select: none;\\n border-right: 1px solid var(--border-color);\\n font-family: var(--font-family-mono);\\n font-size: 12px;\\n line-height: 20px;\\n min-width: 40px;\\n}\\n\\n.code-cell {\\n flex: 1;\\n min-width: 0;\\n padding-left: 2px;\\n font-family: var(--font-family-mono);\\n font-size: 12px;\\n line-height: 20px;\\n white-space: pre;\\n}\\n\\n.error {\\n padding: 16px;\\n color: var(--error-text-color);\\n background-color: var(--error-background);\\n border: 1px solid var(--error-border);\\n border-radius: 6px;\\n font-family: var(--font-family-mono);\\n font-size: 12px;\\n line-height: 1.5;\\n margin: 16px;\\n}\\n\\n.retry-button {\\n margin-top: 12px;\\n padding: 8px 16px;\\n background: var(--button-background);\\n color: var(--button-text-color);\\n border: 1px solid var(--button-border);\\n border-radius: 6px;\\n cursor: pointer;\\n font-size: 14px;\\n font-family: var(--font-family-base);\\n font-weight: 500;\\n transition: background-color 0.2s ease;\\n}\\n\\n.retry-button:hover {\\n background-color: var(--button-hover-background);\\n}\\n\\n/* Skeleton loader styles */\\n.skeleton-line {\\n height: 12px;\\n background: linear-gradient(90deg, var(--skeleton-base) 25%, var(--skeleton-highlight) 50%, var(--skeleton-base) 75%);\\n background-size: 200% 100%;\\n animation: loading 1.5s ease-in-out infinite;\\n border-radius: 4px;\\n margin: 4px 0;\\n}\\n\\n@keyframes loading {\\n 0% {\\n background-position: 200% 0;\\n }\\n 100% {\\n background-position: -200% 0;\\n }\\n}\\n\\n.skeleton-loading .line-number {\\n opacity: 0.3;\\n}\\n\"", "export default \":host {\\n --border-color: #30363d;\\n --header-background: #161b22;\\n --header-text-color: #c9d1d9;\\n --line-number-color: #8b949e;\\n --tab-color: #8b949e;\\n --tab-hover-border: #30363d;\\n --tabs-background: #161b22;\\n --code-background: #0d1117;\\n --skeleton-base: #21262d;\\n --skeleton-highlight: #30363d;\\n --error-text-color: #ff7b72;\\n --error-background: #490202;\\n --error-border: #f85149;\\n --button-background: #21262d;\\n --button-text-color: #c9d1d9;\\n --button-border: #30363d;\\n --button-hover-background: #30363d;\\n --font-family-base:\\n -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif, 'Apple Color Emoji',\\n 'Segoe UI Emoji';\\n --font-family-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;\\n\\n display: block;\\n min-width: 0;\\n font-family: var(--font-family-base);\\n}\\n\\narticle {\\n border: 1px solid var(--border-color);\\n border-radius: 6px;\\n overflow: hidden;\\n background-color: var(--code-background);\\n}\\n\\nheader {\\n background-color: var(--header-background);\\n padding: 8px 16px;\\n border-bottom: 1px solid var(--border-color);\\n color: var(--header-text-color);\\n font-weight: 600;\\n font-size: 14px;\\n}\\n\\n.code-wrapper {\\n width: 100%;\\n margin: 0;\\n overflow-x: auto;\\n background-color: var(--code-background);\\n}\\n\\n.code-table {\\n display: flex;\\n flex-direction: column;\\n width: 100%;\\n min-width: 0;\\n}\\n\\n.code-row {\\n display: flex;\\n}\\n\\n.line-number {\\n flex-shrink: 0;\\n text-align: right;\\n padding-right: 6px;\\n padding-left: 8px;\\n color: var(--line-number-color);\\n user-select: none;\\n border-right: 1px solid var(--border-color);\\n font-family: var(--font-family-mono);\\n font-size: 12px;\\n line-height: 20px;\\n min-width: 40px;\\n}\\n\\n.code-cell {\\n flex: 1;\\n min-width: 0;\\n padding-left: 2px;\\n font-family: var(--font-family-mono);\\n font-size: 12px;\\n line-height: 20px;\\n white-space: pre;\\n}\\n\\n.error {\\n padding: 16px;\\n color: var(--error-text-color);\\n background-color: var(--error-background);\\n border: 1px solid var(--error-border);\\n border-radius: 6px;\\n font-family: var(--font-family-mono);\\n font-size: 12px;\\n line-height: 1.5;\\n margin: 16px;\\n}\\n\\n.retry-button {\\n margin-top: 12px;\\n padding: 8px 16px;\\n background: var(--button-background);\\n color: var(--button-text-color);\\n border: 1px solid var(--button-border);\\n border-radius: 6px;\\n cursor: pointer;\\n font-size: 14px;\\n font-family: var(--font-family-base);\\n font-weight: 500;\\n transition: background-color 0.2s ease;\\n}\\n\\n.retry-button:hover {\\n background-color: var(--button-hover-background);\\n}\\n\\n/* Skeleton loader styles */\\n.skeleton-line {\\n height: 12px;\\n background: linear-gradient(90deg, var(--skeleton-base) 25%, var(--skeleton-highlight) 50%, var(--skeleton-base) 75%);\\n background-size: 200% 100%;\\n animation: loading 1.5s ease-in-out infinite;\\n border-radius: 4px;\\n margin: 4px 0;\\n}\\n\\n@keyframes loading {\\n 0% {\\n background-position: 200% 0;\\n }\\n 100% {\\n background-position: -200% 0;\\n }\\n}\\n\\n.skeleton-loading .line-number {\\n opacity: 0.3;\\n}\\n\"", "export default \"nav[role='tablist'] {\\n display: flex;\\n background-color: var(--tabs-background);\\n border-bottom: 1px solid var(--border-color);\\n overflow-x: auto;\\n}\\n\\nnav > button {\\n padding: 8px 16px;\\n background: none;\\n border: none;\\n border-bottom: 2px solid transparent;\\n color: var(--tab-color);\\n font-size: 14px;\\n font-family: var(--font-family-base);\\n cursor: pointer;\\n white-space: nowrap;\\n transition:\\n color 0.1s ease,\\n border-color 0.1s ease;\\n}\\n\\nnav > button:hover {\\n color: var(--header-text-color);\\n border-bottom-color: var(--tab-hover-border);\\n}\\n\\nnav > button[aria-selected='true'] {\\n color: var(--header-text-color);\\n border-bottom-color: #fd8c73;\\n font-weight: 600;\\n}\\n\\nsection[role='tabpanel'] {\\n display: block;\\n}\\n\\nsection[role='tabpanel'][aria-hidden='true'] {\\n display: none;\\n}\\n\"", "import baseLightCss from './base-light.css';\nimport baseDarkCss from './base-dark.css';\nimport tabCss from './tab.css';\nimport type { ResolvedTheme } from '../types';\n\n/**\n * Manages constructable stylesheets for the component.\n * Implements caching to share stylesheet instances across all component instances.\n */\nexport class StylesheetManager {\n private static baseStylesLight: CSSStyleSheet | null = null;\n private static baseStylesDark: CSSStyleSheet | null = null;\n private static tabStyles: CSSStyleSheet | null = null;\n\n /**\n * Gets the base stylesheet for the specified theme (light or dark).\n * Lazy-loads and caches the stylesheet on first access.\n */\n static getBaseStyleSheet(theme: ResolvedTheme): CSSStyleSheet {\n if (theme === 'dark') {\n if (!this.baseStylesDark) {\n this.baseStylesDark = new CSSStyleSheet();\n this.baseStylesDark.replaceSync(baseDarkCss);\n }\n return this.baseStylesDark;\n } else {\n if (!this.baseStylesLight) {\n this.baseStylesLight = new CSSStyleSheet();\n this.baseStylesLight.replaceSync(baseLightCss);\n }\n return this.baseStylesLight;\n }\n }\n\n /**\n * Gets the tab stylesheet.\n * Lazy-loads and caches the stylesheet on first access.\n */\n static getTabStyleSheet(): CSSStyleSheet {\n if (!this.tabStyles) {\n this.tabStyles = new CSSStyleSheet();\n this.tabStyles.replaceSync(tabCss);\n }\n return this.tabStyles;\n }\n\n /**\n * Gets the highlight.js theme URL for the specified theme.\n */\n static getHighlightJSThemeUrl(theme: ResolvedTheme): string {\n const themeFile = theme === 'dark' ? 'github-dark.min.css' : 'github.min.css';\n return `https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/${themeFile}`;\n }\n}\n", "/**\n * Escapes HTML special characters to prevent XSS\n */\nexport function escapeHtml(text: string): string {\n const div = document.createElement('div');\n div.textContent = text;\n return div.innerHTML;\n}\n\n/**\n * Generates HTML for error display\n */\nexport function getErrorContentHtml(errorMessage: string, showRetry = false): string {\n const retryButton = showRetry ? `<button class=\"retry-button\">Retry</button>` : '';\n return `<div class=\"error\">${escapeHtml(errorMessage)}${retryButton}</div>`;\n}\n\n/**\n * Generates HTML for skeleton loading state\n */\nexport function getSkeletonContentHtml(): string {\n // Generate skeleton lines that look like code\n const skeletonLines = Array.from({ length: 20 }, (_, i) => {\n // Random width between 60-90%\n // Using Math.random() is acceptable here for non-cryptographic purposes\n const width = 60 + Math.random() * 30; // NOSONAR\n return `\n <div class=\"code-row\">\n <div class=\"line-number\">${i + 1}</div>\n <div class=\"skeleton-line\" style=\"width: ${width}%\"></div>\n </div>\n `;\n }).join('');\n\n return `\n <div class=\"code-wrapper skeleton-loading\">\n <div class=\"code-table\">\n ${skeletonLines}\n </div>\n </div>\n `;\n}\n\n/**\n * Generates HTML for code display (without syntax highlighting)\n */\nexport function getCodeContentHtml(code: string | null): string {\n // Safety check for null/undefined code\n if (!code) {\n return getErrorContentHtml('No code content available');\n }\n\n const lines = code.split('\\n');\n\n // Remove only the last empty line if it exists (from final newline)\n if (lines.length > 0 && lines[lines.length - 1] === '') {\n lines.pop();\n }\n\n return `\n <div class=\"code-wrapper hljs\">\n <div class=\"code-table\">\n ${lines\n .map(\n (line, index) => `\n <div class=\"code-row\">\n <div class=\"line-number\">${index + 1}</div>\n <div class=\"code-cell\">${escapeHtml(line) || ' '}</div>\n </div>\n `\n )\n .join('')}\n </div>\n </div>\n `;\n}\n", "import { escapeHtml } from './html-generators';\n\n/**\n * Generates the outer HTML structure for a single file display.\n *\n * @param filename - The filename to show in the header\n * @param content - The inner content HTML (skeleton, code, or error)\n * @param themeStylesheetUrl - URL for the highlight.js theme CSS\n * @returns Complete HTML string for single file layout\n */\nexport function generateSingleFileTemplate(filename: string, content: string, themeStylesheetUrl: string): string {\n return `<link rel=\"stylesheet\" href=\"${themeStylesheetUrl}\">\n<article>\n <header>${escapeHtml(filename)}</header>\n ${content}\n</article>`;\n}\n\n/**\n * Generates the article content for a single file (header + content).\n * Used when updating only the article innards, not the full template.\n *\n * @param filename - The filename to show in the header\n * @param content - The inner content HTML\n * @returns HTML string for article content\n */\nexport function generateArticleContent(filename: string, content: string): string {\n return `<header>${escapeHtml(filename)}</header>\n ${content}`;\n}\n\n/**\n * Generates the outer HTML structure for tabbed file display.\n *\n * @param tabsHtml - Pre-generated HTML for tab buttons\n * @param activeIndex - Index of the currently active tab\n * @param panelContent - Content HTML for the active panel\n * @param themeStylesheetUrl - URL for the highlight.js theme CSS\n * @returns Complete HTML string for tabbed layout\n */\nexport function generateTabbedTemplate(\n tabsHtml: string,\n activeIndex: number,\n panelContent: string,\n themeStylesheetUrl: string\n): string {\n return `<link rel=\"stylesheet\" href=\"${themeStylesheetUrl}\">\n<article>\n <nav role=\"tablist\" aria-label=\"Code files\">${tabsHtml}</nav>\n <section role=\"tabpanel\"\n id=\"panel-${activeIndex}\"\n aria-labelledby=\"tab-${activeIndex}\"\n data-index=\"${activeIndex}\">\n ${panelContent}\n </section>\n</article>`;\n}\n", "/**\n * Gets syntax-highlighted lines from code using highlight.js\n */\nexport function getHighlightedLines(code: string | null): string[] {\n // Safety check for null/undefined code\n if (!code) {\n return [''];\n }\n\n const lines = code.split('\\n');\n\n // Remove only the last empty line if it exists (from final newline)\n if (lines.length > 0 && lines[lines.length - 1] === '') {\n lines.pop();\n }\n\n const fullCode = lines.join('\\n');\n const result = window.hljs!.highlightAuto(fullCode);\n const highlightedLines = result.value.split('\\n');\n\n // Ensure highlightedLines matches lines length\n while (highlightedLines.length > lines.length) {\n highlightedLines.pop();\n }\n\n return highlightedLines;\n}\n\n/**\n * Applies syntax highlighting to code cells within a container\n */\nexport function applySyntaxHighlighting(container: Element | ShadowRoot, code: string): void {\n const codeCells = container.querySelectorAll('.code-cell');\n\n const applyHighlighting = (): void => {\n const highlightedLines = getHighlightedLines(code);\n codeCells.forEach((cell, index) => {\n (cell as HTMLElement).innerHTML = highlightedLines[index] || ' ';\n });\n };\n\n // Use requestIdleCallback to avoid blocking the main thread\n if ('requestIdleCallback' in window) {\n requestIdleCallback(applyHighlighting);\n } else {\n applyHighlighting();\n }\n}\n", "import type { Theme, ResolvedTheme } from '../types';\n\n/**\n * Resolves a theme attribute to an actual theme value.\n * Handles 'auto' theme by detecting system preference.\n *\n * @param themeAttr - The theme attribute value ('light', 'dark', or 'auto')\n * @returns The resolved theme ('light' or 'dark')\n */\nexport function resolveTheme(themeAttr: Theme): ResolvedTheme {\n if (themeAttr === 'dark') {\n return 'dark';\n }\n\n if (themeAttr === 'light') {\n return 'light';\n }\n\n // auto - detect system preference\n const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;\n return prefersDark ? 'dark' : 'light';\n}\n\n/**\n * Gets the theme attribute from an element, defaulting to 'auto'.\n *\n * @param element - The HTML element to get the theme from\n * @returns The theme value\n */\nexport function getThemeAttribute(element: HTMLElement): Theme {\n const attr = element.getAttribute('theme');\n if (attr === 'dark' || attr === 'light') {\n return attr;\n }\n return 'auto';\n}\n\n/**\n * Creates a theme change handler that clears cached theme and triggers re-render.\n *\n * @param onThemeChange - Callback to invoke when theme changes\n * @returns Object with setup and cleanup functions for theme media query listener\n */\nexport function createThemeListener(onThemeChange: () => void): {\n setup: () => MediaQueryList | null;\n cleanup: (mediaQuery: MediaQueryList | null) => void;\n handler: () => void;\n} {\n const handler = onThemeChange;\n\n return {\n setup: () => {\n if (window.matchMedia) {\n const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');\n mediaQuery.addEventListener('change', handler);\n return mediaQuery;\n }\n return null;\n },\n cleanup: (mediaQuery: MediaQueryList | null) => {\n if (mediaQuery) {\n mediaQuery.removeEventListener('change', handler);\n }\n },\n handler,\n };\n}\n", "/**\n * Manages tab state for the component\n */\nexport class TabState {\n private activeTabIndex = 0;\n private renderedTabs = new Set<number>();\n private tabsFullyRendered = false;\n\n /**\n * Gets the currently active tab index\n */\n getActiveTabIndex(): number {\n return this.activeTabIndex;\n }\n\n /**\n * Sets the active tab index\n */\n setActiveTabIndex(index: number): void {\n this.activeTabIndex = index;\n }\n\n /**\n * Checks if tabs structure is fully rendered with event listeners\n */\n areTabsFullyRendered(): boolean {\n return this.tabsFullyRendered;\n }\n\n /**\n * Marks tabs as fully rendered\n */\n setTabsFullyRendered(value: boolean): void {\n this.tabsFullyRendered = value;\n }\n\n /**\n * Checks if a tab has been rendered\n */\n isTabRendered(index: number): boolean {\n return this.renderedTabs.has(index);\n }\n\n /**\n * Marks a tab as rendered\n */\n markTabAsRendered(index: number): void {\n this.renderedTabs.add(index);\n }\n\n /**\n * Clears all rendered tabs\n */\n clearRenderedTabs(): void {\n this.renderedTabs.clear();\n }\n\n /**\n * Gets the total number of tabs\n */\n getTabCount(files: unknown[]): number {\n return files.length;\n }\n\n /**\n * Resets tab state (useful when re-parsing files)\n */\n reset(): void {\n this.renderedTabs.clear();\n this.tabsFullyRendered = false;\n // Note: activeTabIndex is NOT reset here - that's handled by the caller\n }\n}\n", "import type { FileMetadata } from '../types';\nimport { escapeHtml } from '../rendering/html-generators';\n\n/**\n * Generates HTML for tab buttons\n */\nexport function generateTabsHtml(files: FileMetadata[], activeTabIndex: number): string {\n return files\n .map(\n (file, index) => `\n <button role=\"tab\"\n id=\"tab-${index}\"\n aria-selected=\"${index === activeTabIndex ? 'true' : 'false'}\"\n aria-controls=\"panel-${index}\"\n tabindex=\"${index === activeTabIndex ? '0' : '-1'}\"\n data-index=\"${index}\">\n ${escapeHtml(file.filename)}\n </button>\n `\n )\n .join('');\n}\n\n/**\n * Updates tab button states (aria attributes) when switching tabs\n */\nexport function updateTabButtonStates(shadowRoot: ShadowRoot, newActiveIndex: number): void {\n const tabs = shadowRoot.querySelectorAll('nav > button');\n tabs.forEach((tab) => {\n const tabIndex = parseInt((tab as HTMLElement).dataset.index || '0');\n const isSelected = tabIndex === newActiveIndex;\n tab.setAttribute('aria-selected', isSelected ? 'true' : 'false');\n tab.setAttribute('tabindex', isSelected ? '0' : '-1');\n });\n}\n\n/**\n * Updates tab panel visibility states when switching tabs\n */\nexport function updateTabPanelStates(shadowRoot: ShadowRoot, activeIndex: number): void {\n const allPanels = shadowRoot.querySelectorAll('section[role=\"tabpanel\"]');\n allPanels.forEach((panel) => {\n const panelIndex = parseInt((panel as HTMLElement).dataset.index || '0');\n panel.setAttribute('aria-hidden', panelIndex !== activeIndex ? 'true' : 'false');\n });\n}\n\n/**\n * Creates a new tab panel element for lazy loading\n */\nexport function createTabPanel(index: number, skeletonHtml: string): HTMLElement {\n const contentElement = document.createElement('section');\n contentElement.setAttribute('role', 'tabpanel');\n contentElement.setAttribute('id', `panel-${index}`);\n contentElement.setAttribute('aria-labelledby', `tab-${index}`);\n contentElement.dataset.index = String(index);\n contentElement.innerHTML = skeletonHtml;\n return contentElement;\n}\n\n/**\n * Handles keyboard navigation for tabs (Arrow keys, Home, End)\n * Returns the new index if navigation should occur, null otherwise\n */\nexport function handleTabKeyNavigation(key: string, currentIndex: number, maxIndex: number): number | null {\n switch (key) {\n case 'ArrowLeft':\n return currentIndex > 0 ? currentIndex - 1 : maxIndex;\n case 'ArrowRight':\n return currentIndex < maxIndex ? currentIndex + 1 : 0;\n case 'Home':\n return 0;\n case 'End':\n return maxIndex;\n default:\n return null;\n }\n}\n", "import type { FileMetadata, ResolvedTheme, GitHubCodeInfo } from './types';\nimport { parseFileAttribute } from './parsers/file-parser';\nimport { isValidGitHubUrl, parseGitHubUrl, extractFilenameFromUrl } from './parsers/url-parser';\nimport { ensureFileLoaded } from './fetching/code-fetcher';\nimport { loadHighlightJS, getLoadedHighlightJSUrl, getHighlightJSSource } from './fetching/highlightjs-loader';\nimport packageJson from '../package.json';\nimport { StylesheetManager } from './styles/stylesheet-manager';\nimport {\n escapeHtml,\n getErrorContentHtml,\n getSkeletonContentHtml,\n getCodeContentHtml,\n} from './rendering/html-generators';\nimport {\n generateSingleFileTemplate,\n generateArticleContent,\n generateTabbedTemplate,\n} from './rendering/template-generators';\nimport { applySyntaxHighlighting } from './rendering/syntax-highlighter';\nimport { resolveTheme, getThemeAttribute } from './theme/theme-resolver';\nimport { TabState } from './tabs/tab-state';\nimport {\n generateTabsHtml,\n updateTabButtonStates,\n updateTabPanelStates,\n createTabPanel,\n handleTabKeyNavigation,\n} from './tabs/tab-controller';\n\n/**\n * GitHub Code Web Component\n * Displays GitHub file URLs with syntax highlighting\n */\nexport class GitHubCode extends HTMLElement {\n // Private fields\n #files: FileMetadata[] = [];\n #tabState = new TabState();\n #resolvedTheme: ResolvedTheme | null = null;\n #themeMediaQuery: MediaQueryList | null = null;\n #themeChangeHandler: (() => void) | null = null;\n\n constructor() {\n super();\n this.attachShadow({ mode: 'open' });\n }\n\n /**\n * Safely gets a file by index, throwing if out of bounds.\n * This provides type-safe array access with noUncheckedIndexedAccess.\n */\n #getFile(index: number): FileMetadata {\n const file = this.#files[index];\n if (!file) {\n throw new Error(`File at index ${index} not found`);\n }\n return file;\n }\n\n static get observedAttributes(): string[] {\n return ['file', 'theme'];\n }\n\n /**\n * Runtime information about the component and loaded libraries\n */\n static get info(): GitHubCodeInfo {\n return {\n version: packageJson.version,\n highlightjsUrl: getLoadedHighlightJSUrl(),\n highlightjsSource: getHighlightJSSource(),\n };\n }\n\n connectedCallback(): void {\n // Set up theme change listener for reactive theme updates\n this.#setupThemeListener();\n\n // Initial render (will parse files and display)\n void this.#render();\n }\n\n disconnectedCallback(): void {\n // Clean up theme change listener to prevent memory leaks\n if (this.#themeMediaQuery && this.#themeChangeHandler) {\n this.#themeMediaQuery.removeEventListener('change', this.#themeChangeHandler);\n }\n }\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n // Skip initial attribute set (oldValue is null) - connectedCallback handles initial render\n if (oldValue === null || oldValue === newValue) {\n return;\n }\n\n if (name === 'file') {\n // Store current active tab index before re-parsing files\n const previousIndex = this.#tabState.getActiveTabIndex();\n\n // Re-parse files (clear renderedTabs since structure will be rebuilt)\n this.#tabState.reset();\n\n // Re-render with new file URLs\n void this.#render();\n\n // Try to preserve tab index if it still exists\n if (previousIndex < this.#files.length) {\n this.#tabState.setActiveTabIndex(previousIndex);\n } else {\n // Reset to first tab if previous index no longer exists\n this.#tabState.setActiveTabIndex(0);\n }\n } else if (name === 'theme') {\n this.#resolvedTheme = null; // Clear cached theme\n void this.#render();\n }\n }\n\n // Private methods - theming\n #setupThemeListener(): void {\n if (window.matchMedia) {\n this.#themeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');\n this.#themeChangeHandler = () => {\n this.#resolvedTheme = null; // Clear cached theme\n void this.#render(); // Re-render with new theme\n };\n this.#themeMediaQuery.addEventListener('change', this.#themeChangeHandler);\n }\n }\n\n #getResolvedTheme(): ResolvedTheme {\n if (this.#resolvedTheme) {\n return this.#resolvedTheme;\n }\n\n this.#resolvedTheme = resolveTheme(getThemeAttribute(this));\n return this.#resolvedTheme;\n }\n\n // Apply constructable stylesheets to shadow root (CSP-compliant, no 'unsafe-inline' needed)\n #applyStyleSheets(includeTabStyles = false): void {\n const theme = this.#getResolvedTheme();\n const baseSheet = StylesheetManager.getBaseStyleSheet(theme);\n\n if (includeTabStyles) {\n const tabSheet = StylesheetManager.getTabStyleSheet();\n this.shadowRoot!.adoptedStyleSheets = [baseSheet, tabSheet];\n } else {\n this.shadowRoot!.adoptedStyleSheets = [baseSheet];\n }\n }\n\n // Private methods - rendering\n async #render(): Promise<void> {\n const fileAttr = this.getAttribute('file');\n\n if (!fileAttr) {\n this.#showError('Error: \"file\" attribute is required. Please provide a GitHub file URL.');\n return;\n }\n\n const fileUrls = parseFileAttribute(fileAttr);\n\n if (fileUrls.length === 0) {\n this.#showError('Error: \"file\" attribute is required. Please provide a GitHub file URL.');\n return;\n }\n\n // Validate all URLs\n const invalidUrl = fileUrls.find((url) => !isValidGitHubUrl(url));\n if (invalidUrl) {\n // Sanitize URL before using in error message to prevent XSS\n const sanitizedUrl = escapeHtml(invalidUrl);\n this.#showError(\n `Error: Invalid GitHub URL format: ${sanitizedUrl}. Expected format: https://github.com/{owner}/{repo}/blob/{commit}/{path}`\n );\n return;\n }\n\n // Parse URLs to get file metadata first (don't fetch content yet - lazy load on demand)\n this.#files = fileUrls.map((url) => {\n try {\n const { rawUrl, filename } = parseGitHubUrl(url);\n return {\n filename,\n rawUrl,\n url,\n code: null,\n error: null,\n loaded: false,\n };\n } catch (error) {\n const filename = extractFilenameFromUrl(url);\n return {\n filename,\n rawUrl: url,\n url,\n code: null,\n error: error instanceof Error ? error.message : String(error),\n loaded: true, // Parse error - no point retrying\n };\n }\n });\n\n // Render skeleton UI immediately (tabs or single file structure)\n if (fileUrls.length === 1) {\n // Single file: show header + skeleton\n const file = this.#getFile(0);\n if (file.error) {\n this.#showError(`Error loading code: ${file.error}`);\n return;\n }\n // Show skeleton immediately\n this.shadowRoot!.innerHTML = generateSingleFileTemplate(\n file.filename,\n getSkeletonContentHtml(),\n StylesheetManager.getHighlightJSThemeUrl(this.#getResolvedTheme())\n );\n this.#applyStyleSheets(false);\n } else {\n // Multiple files: show tabs + skeleton immediately\n this.#renderTabsSkeletonStructure();\n }\n\n // Load highlight.js in background (UI already visible)\n try {\n // Read custom URL attribute (treat \"auto\" as default)\n const customUrlAttr = this.getAttribute('highlightjs-url');\n const customUrl = customUrlAttr && customUrlAttr !== 'auto' ? customUrlAttr : undefined;\n\n await loadHighlightJS(customUrl);\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : String(error);\n this.#showError(`Error loading highlight.js: ${errorMsg}`);\n return;\n }\n\n // Now load actual content\n if (fileUrls.length === 1) {\n await this.#displayCode(0);\n } else {\n await this.#displayWithTabs();\n }\n }\n\n async #displayCode(index: number): Promise<void> {\n const file = this.#getFile(index);\n const themeUrl = StylesheetManager.getHighlightJSThemeUrl(this.#getResolvedTheme());\n\n // Render skeleton immediately\n this.shadowRoot!.innerHTML = generateSingleFileTemplate(file.filename, getSkeletonContentHtml(), themeUrl);\n this.#applyStyleSheets(false);\n\n // Load content in background and update when ready\n await ensureFileLoaded(file, false);\n\n const contentArea = this.shadowRoot!.querySelector('article');\n if (file.error) {\n contentArea!.innerHTML = generateArticleContent(file.filename, getErrorContentHtml(file.error, true));\n // Add retry button event listener\n const retryButton = contentArea!.querySelector('.retry-button');\n if (retryButton) {\n retryButton.addEventListener('click', () => {\n void (async () => {\n // Show skeleton during retry\n contentArea!.innerHTML = generateArticleContent(file.filename, getSkeletonContentHtml());\n await ensureFileLoaded(file, true);\n await this.#displayCode(index);\n })();\n });\n }\n } else {\n contentArea!.innerHTML = generateArticleContent(file.filename, getCodeContentHtml(file.code));\n if (window.hljs) {\n applySyntaxHighlighting(this.shadowRoot!, file.code!);\n }\n }\n }\n\n async #displayWithTabs(): Promise<void> {\n if (!this.#tabState.areTabsFullyRendered()) {\n // First full render - build entire structure with event listeners\n await this.#renderTabsStructure();\n this.#tabState.setTabsFullyRendered(true);\n } else {\n // Tab switching - update only what's needed\n this.#switchToTab(this.#tabState.getActiveTabIndex());\n }\n }\n\n #renderTabsSkeletonStructure(): void {\n const activeIndex = this.#tabState.getActiveTabIndex();\n const tabsHtml = generateTabsHtml(this.#files, activeIndex);\n const themeUrl = StylesheetManager.getHighlightJSThemeUrl(this.#getResolvedTheme());\n\n this.shadowRoot!.innerHTML = generateTabbedTemplate(tabsHtml, activeIndex, getSkeletonContentHtml(), themeUrl);\n this.#applyStyleSheets(true);\n }\n\n async #renderTabsStructure(): Promise<void> {\n const activeIndex = this.#tabState.getActiveTabIndex();\n const tabsHtml = generateTabsHtml(this.#files, activeIndex);\n const themeUrl = StylesheetManager.getHighlightJSThemeUrl(this.#getResolvedTheme());\n\n // Render tabs and skeleton immediately\n this.shadowRoot!.innerHTML = generateTabbedTemplate(tabsHtml, activeIndex, getSkeletonContentHtml(), themeUrl);\n this.#applyStyleSheets(true);\n\n // Use event delegation on parent nav element to prevent race conditions\n const nav = this.shadowRoot!.querySelector('nav[role=\"tablist\"]');\n nav!.addEventListener('click', (e) => {\n const tab = (e.target as Element).closest('button[role=\"tab\"]');\n if (tab) {\n const newIndex = parseInt((tab as HTMLElement).dataset.index || '0');\n if (newIndex !== this.#tabState.getActiveTabIndex()) {\n this.#tabState.setActiveTabIndex(newIndex);\n void this.#displayWithTabs();\n }\n }\n });\n\n // Add keyboard navigation for accessibility\n nav!.addEventListener('keydown', (e) => {\n const keyboardEvent = e as KeyboardEvent;\n const tab = (keyboardEvent.target as Element).closest('button[role=\"tab\"]');\n if (!tab) {\n return;\n }\n\n const currentIndex = this.#tabState.getActiveTabIndex();\n const maxIndex = this.#files.length - 1;\n\n const newIndex = handleTabKeyNavigation(keyboardEvent.key, currentIndex, maxIndex);\n\n if (newIndex !== null) {\n e.preventDefault();\n this.#tabState.setActiveTabIndex(newIndex);\n void this.#displayWithTabs().then(() => {\n // Focus the newly selected tab\n const newTab = this.shadowRoot!.querySelector(`button[data-index=\"${newIndex}\"]`);\n if (newTab) {\n (newTab as HTMLElement).focus();\n }\n });\n }\n });\n\n // Mark this tab as rendered\n this.#tabState.markTabAsRendered(activeIndex);\n\n // Load active file content in background and update when ready\n const contentElement = this.shadowRoot!.querySelector('section[role=\"tabpanel\"]');\n await this.#loadAndRenderTabContent(activeIndex, contentElement as HTMLElement).catch((error: unknown) => {\n console.error('Failed to load tab content:', error);\n (contentElement as HTMLElement).innerHTML = getErrorContentHtml(\n `Failed to load content: ${error instanceof Error ? error.message : String(error)}`\n );\n });\n }\n\n #switchToTab(newIndex: number): void {\n // Update tab buttons - use aria-selected and tabindex for accessibility\n updateTabButtonStates(this.shadowRoot!, newIndex);\n\n // Check if content for this tab already exists\n let contentElement = this.shadowRoot!.querySelector(`section[role=\"tabpanel\"][data-index=\"${newIndex}\"]`);\n\n if (!contentElement) {\n // Create skeleton immediately (lazy load pattern)\n contentElement = createTabPanel(newIndex, getSkeletonContentHtml());\n\n // Append skeleton to DOM immediately (user sees it right away)\n this.shadowRoot!.querySelector('article')!.appendChild(contentElement);\n this.#tabState.markTabAsRendered(newIndex);\n\n // Load content asynchronously and update when ready\n void this.#loadAndRenderTabContent(newIndex, contentElement as HTMLElement).catch((error: unknown) => {\n console.error('Failed to load tab content:', error);\n (contentElement as HTMLElement).innerHTML = getErrorContentHtml(\n `Failed to load content: ${error instanceof Error ? error.message : String(error)}`\n );\n });\n }\n\n // Update panels - use aria-hidden instead of .hidden class\n updateTabPanelStates(this.shadowRoot!, newIndex);\n }\n\n async #loadAndRenderTabContent(index: number, contentElement: HTMLElement): Promise<void> {\n // Load file content\n const file = this.#getFile(index);\n await ensureFileLoaded(file, false);\n\n // Replace skeleton with actual content\n if (file.error || !file.code) {\n const errorMsg = file.error || 'Failed to load content: No content available';\n contentElement.innerHTML = getErrorContentHtml(errorMsg, true);\n\n // Add retry button event listener\n const retryButton = contentElement.querySelector('.retry-button');\n if (retryButton) {\n retryButton.addEventListener('click', () => {\n void (async () => {\n // Show skeleton during retry\n contentElement.innerHTML = getSkeletonContentHtml();\n await ensureFileLoaded(file, true);\n await this.#loadAndRenderTabContent(index, contentElement);\n })();\n });\n }\n } else {\n contentElement.innerHTML = getCodeContentHtml(file.code);\n // Apply syntax highlighting\n if (window.hljs) {\n applySyntaxHighlighting(contentElement, file.code);\n }\n }\n }\n\n #showError(message: string): void {\n this.shadowRoot!.innerHTML = `\n <div class=\"error\">${escapeHtml(message)}</div>\n `;\n this.#applyStyleSheets(false);\n }\n}\n", "import { GitHubCode } from './github-code';\n\n// Register the custom element\ncustomElements.define('github-code', GitHubCode);\n\n// Export for potential programmatic usage\nexport { GitHubCode };\n"],
5
+ "mappings": "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",
6
+ "names": ["parseFileAttribute", "fileAttr", "url", "GITHUB_BLOB_PATTERN", "isValidGitHubUrl", "url", "parseGitHubUrl", "match", "owner", "repo", "commit", "path", "rawUrl", "filename", "extractFilenameFromUrl", "fetchCode", "url", "response", "error", "ensureFileLoaded", "file", "isRetry", "code", "highlightJSLoadingPromise", "loadedHighlightJSUrl", "highlightJSSource", "getLoadedHighlightJSUrl", "getHighlightJSSource", "loadHighlightJS", "customUrl", "urlToLoad", "resolve", "reject", "script", "errorMsg", "package_default", "base_light_default", "base_dark_default", "tab_default", "StylesheetManager", "theme", "base_dark_default", "base_light_default", "tab_default", "escapeHtml", "text", "div", "getErrorContentHtml", "errorMessage", "showRetry", "retryButton", "getSkeletonContentHtml", "_", "i", "width", "getCodeContentHtml", "code", "lines", "line", "index", "generateSingleFileTemplate", "filename", "content", "themeStylesheetUrl", "escapeHtml", "generateArticleContent", "generateTabbedTemplate", "tabsHtml", "activeIndex", "panelContent", "getHighlightedLines", "code", "lines", "fullCode", "highlightedLines", "applySyntaxHighlighting", "container", "codeCells", "applyHighlighting", "cell", "index", "resolveTheme", "themeAttr", "getThemeAttribute", "element", "attr", "TabState", "index", "value", "files", "generateTabsHtml", "files", "activeTabIndex", "file", "index", "escapeHtml", "updateTabButtonStates", "shadowRoot", "newActiveIndex", "tab", "isSelected", "updateTabPanelStates", "activeIndex", "panel", "panelIndex", "createTabPanel", "skeletonHtml", "contentElement", "handleTabKeyNavigation", "key", "currentIndex", "maxIndex", "GitHubCode", "#files", "#tabState", "TabState", "#resolvedTheme", "#themeMediaQuery", "#themeChangeHandler", "#getFile", "index", "file", "package_default", "getLoadedHighlightJSUrl", "getHighlightJSSource", "#setupThemeListener", "#render", "name", "oldValue", "newValue", "previousIndex", "#getResolvedTheme", "resolveTheme", "getThemeAttribute", "#applyStyleSheets", "includeTabStyles", "theme", "baseSheet", "StylesheetManager", "tabSheet", "fileAttr", "#showError", "fileUrls", "parseFileAttribute", "invalidUrl", "url", "isValidGitHubUrl", "sanitizedUrl", "escapeHtml", "rawUrl", "filename", "parseGitHubUrl", "error", "extractFilenameFromUrl", "generateSingleFileTemplate", "getSkeletonContentHtml", "#renderTabsSkeletonStructure", "customUrlAttr", "loadHighlightJS", "errorMsg", "#displayCode", "#displayWithTabs", "themeUrl", "ensureFileLoaded", "contentArea", "generateArticleContent", "getErrorContentHtml", "retryButton", "getCodeContentHtml", "applySyntaxHighlighting", "#switchToTab", "#renderTabsStructure", "activeIndex", "tabsHtml", "generateTabsHtml", "generateTabbedTemplate", "nav", "e", "tab", "newIndex", "keyboardEvent", "currentIndex", "maxIndex", "handleTabKeyNavigation", "newTab", "contentElement", "#loadAndRenderTabContent", "updateTabButtonStates", "createTabPanel", "updateTabPanelStates", "message", "GitHubCode"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "github-code",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Custom element for embedding GitHub source files with syntax highlighting",
5
5
  "type": "module",
6
6
  "main": "dist/github-code.min.js",
@@ -43,6 +43,10 @@
43
43
  ],
44
44
  "author": "",
45
45
  "license": "MIT",
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "git+https://github.com/destan/github-code.git"
49
+ },
46
50
  "devDependencies": {
47
51
  "@eslint/js": "^9.39.2",
48
52
  "@playwright/test": "^1.57.0",
package/README.adoc DELETED
@@ -1,87 +0,0 @@
1
- = github-code
2
- :toc:
3
-
4
- image:https://img.shields.io/npm/v/github-code[npm version,link=https://www.npmjs.com/package/github-code]
5
- image:https://img.shields.io/npm/l/github-code[License]
6
- image:https://img.shields.io/badge/highlight.js-syntax%20highlighting-blue[highlight.js]
7
-
8
- A web component for displaying GitHub files with syntax highlighting.
9
-
10
- == Installation
11
-
12
- Include the script in your HTML:
13
-
14
- [source,html]
15
- ----
16
- <script type="module" src="https://cdn.jsdelivr.net/npm/github-code/dist/github-code.min.js"></script>
17
- ----
18
-
19
- Or install via npm:
20
-
21
- [source,bash]
22
- ----
23
- npm install github-code
24
- ----
25
-
26
- == Usage
27
-
28
- === Single File
29
-
30
- [source,html]
31
- ----
32
- <github-code
33
- file="https://github.com/owner/repo/blob/main/src/example.ts">
34
- </github-code>
35
- ----
36
-
37
- === Multiple Files (Tabbed)
38
-
39
- Comma-separate URLs to display files as tabs:
40
-
41
- [source,html]
42
- ----
43
- <github-code
44
- file="https://github.com/owner/repo/blob/main/src/app.ts,
45
- https://github.com/owner/repo/blob/main/src/utils.ts">
46
- </github-code>
47
- ----
48
-
49
- == Attributes
50
-
51
- [cols="1,1,3"]
52
- |===
53
- | Attribute | Default | Description
54
-
55
- | `file` | _(required)_ | GitHub blob URL(s). Comma-separated for multiple files.
56
- | `theme` | `auto` | `light`, `dark`, or `auto` (follows system preference)
57
- |===
58
-
59
- == URL Format
60
-
61
- URLs must be GitHub blob URLs:
62
-
63
- ----
64
- https://github.com/{owner}/{repo}/blob/{branch|commit}/{path}
65
- ----
66
-
67
- == Features
68
-
69
- * Syntax highlighting via highlight.js (auto-detected language)
70
- * Lazy loading (content fetched on demand)
71
- * Keyboard-accessible tabs (Arrow keys, Home, End)
72
- * CSP-compliant (no inline styles)
73
- * Shadow DOM encapsulation
74
-
75
- == Development
76
-
77
- [source,bash]
78
- ----
79
- npm install
80
- npm run build # Build dist files
81
- npm run dev # Watch mode
82
- npm run test:ci # Full CI check (lint + typecheck + build + tests)
83
- ----
84
-
85
- == License
86
-
87
- MIT