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 +83 -0
- package/dist/github-code.js +135 -8
- package/dist/github-code.js.map +3 -3
- package/dist/github-code.min.js +14 -13
- package/dist/github-code.min.js.map +4 -4
- package/package.json +5 -1
- package/README.adoc +0 -87
package/README.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# github-code
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/github-code)
|
|
4
|
+
[](https://bundlephobia.com/package/github-code)
|
|
5
|
+
[](https://github.com/destan/github-code/blob/main/LICENSE)
|
|
6
|
+
[](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
|
package/dist/github-code.js
CHANGED
|
@@ -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
|
-
|
|
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 =
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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}`);
|
package/dist/github-code.js.map
CHANGED
|
@@ -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
|
}
|
package/dist/github-code.min.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
function
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
347
|
-
${e}`}function
|
|
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
|
|
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
|
|
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
|
|
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",
|
|
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": "AAGO,SAASA,EAAmBC,EAA4B,CAC7D,OAAOA,EACJ,MAAM,GAAG,EACT,IAAKC,GAAQA,EAAI,KAAK,CAAC,EACvB,OAAQA,GAAQA,EAAI,OAAS,CAAC,CACnC,CCFA,IAAMC,EAAsB,mDAKrB,SAASC,EAAiBC,EAAsB,CACrD,OAAOF,EAAoB,KAAKE,CAAG,CACrC,CAMO,SAASC,EAAeD,EAA6B,CAE1D,IAAME,EAAQ,oEAAoE,KAAKF,CAAG,EAE1F,GAAI,CAACE,EACH,MAAM,IAAI,MAAM,4BAA4B,EAG9C,IAAMC,EAAQD,EAAM,CAAC,EACfE,EAAOF,EAAM,CAAC,EACdG,EAASH,EAAM,CAAC,EAChBI,EAAOJ,EAAM,CAAC,EAEpB,GAAI,CAACC,GAAS,CAACC,GAAQ,CAACC,GAAU,CAACC,EACjC,MAAM,IAAI,MAAM,4BAA4B,EAG9C,IAAMC,EAAS,qCAAqCJ,CAAK,IAAIC,CAAI,IAAIC,CAAM,IAAIC,CAAI,GAE7EE,EAAWF,EAAK,MAAM,GAAG,EAAE,IAAI,GAAK,UAE1C,MAAO,CACL,OAAAC,EACA,SAAAC,CACF,CACF,CAKO,SAASC,EAAuBT,EAAqB,CAC1D,GAAI,CAGF,MADc,cAAc,KAAKA,CAAG,IACrB,CAAC,GAAK,SACvB,MAAQ,CACN,MAAO,SACT,CACF,CCnDA,eAAsBU,EAAUC,EAA8B,CAC5D,GAAI,CACF,IAAMC,EAAW,MAAM,MAAMD,CAAG,EAEhC,GAAI,CAACC,EAAS,GACZ,MAAM,IAAI,MAAM,8BAA8BA,EAAS,MAAM,2CAA2C,EAG1G,OAAO,MAAMA,EAAS,KAAK,CAC7B,OAASC,EAAO,CAEd,MAAIA,aAAiB,WAAaA,EAAM,QAAQ,SAAS,OAAO,EACxD,IAAI,MACR,6BAA6BF,CAAG,yLAIlC,EAEIE,CACR,CACF,CAOA,eAAsBC,EAAiBC,EAAoBC,EAAU,GAAsB,CAEzF,GAAI,EAAAD,EAAK,QAAU,CAACC,GAKpB,CAAIA,IACFD,EAAK,OAAS,GACdA,EAAK,MAAQ,MAGf,GAAI,CACF,IAAME,EAAO,MAAMP,EAAUK,EAAK,MAAM,EACxCA,EAAK,KAAOE,EACZF,EAAK,MAAQ,KACbA,EAAK,OAAS,EAChB,OAASF,EAAO,CACdE,EAAK,KAAO,KACZA,EAAK,MAAQF,aAAiB,MAAQA,EAAM,QAAU,OAAOA,CAAK,EAClEE,EAAK,OAAS,EAChB,EACF,CCrDA,IAAIG,EAAkD,KAiBtD,eAAsBC,GAAiC,CAErD,GAAI,QAAO,KAKX,OAAID,IAKJA,EAA4B,IAAI,QAAQ,CAACE,EAASC,IAAW,CAC3D,IAAMC,EAAS,SAAS,cAAc,QAAQ,EAC9CA,EAAO,IAAM,+EACbA,EAAO,UAAY,0EACnBA,EAAO,YAAc,YACrBA,EAAO,OAAS,IAAM,CACpBJ,EAA4B,KAC5BE,EAAQ,CACV,EACAE,EAAO,QAAU,IAAM,CACrBJ,EAA4B,KAC5B,IAAMK,EACJ;AAAA;AAAA,0CAIFF,EAAO,IAAI,MAAME,CAAQ,CAAC,CAC5B,EACA,SAAS,KAAK,YAAYD,CAAM,CAClC,CAAC,EAEMJ,EACT,CCtDA,IAAOM,EAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ECAf,IAAOC,EAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ECAf,IAAOC,EAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ECSR,IAAMC,EAAN,KAAwB,CAC7B,OAAe,gBAAwC,KACvD,OAAe,eAAuC,KACtD,OAAe,UAAkC,KAMjD,OAAO,kBAAkBC,EAAqC,CAC5D,OAAIA,IAAU,QACP,KAAK,iBACR,KAAK,eAAiB,IAAI,cAC1B,KAAK,eAAe,YAAYC,CAAW,GAEtC,KAAK,iBAEP,KAAK,kBACR,KAAK,gBAAkB,IAAI,cAC3B,KAAK,gBAAgB,YAAYC,CAAY,GAExC,KAAK,gBAEhB,CAMA,OAAO,kBAAkC,CACvC,OAAK,KAAK,YACR,KAAK,UAAY,IAAI,cACrB,KAAK,UAAU,YAAYC,CAAM,GAE5B,KAAK,SACd,CAKA,OAAO,uBAAuBH,EAA8B,CAE1D,MAAO,sEADWA,IAAU,OAAS,sBAAwB,gBACyB,EACxF,CACF,EClDO,SAASI,EAAWC,EAAsB,CAC/C,IAAMC,EAAM,SAAS,cAAc,KAAK,EACxC,OAAAA,EAAI,YAAcD,EACXC,EAAI,SACb,CAKO,SAASC,EAAoBC,EAAsBC,EAAY,GAAe,CACnF,IAAMC,EAAcD,EAAY,8CAAgD,GAChF,MAAO,sBAAsBL,EAAWI,CAAY,CAAC,GAAGE,CAAW,QACrE,CAKO,SAASC,GAAiC,CAc/C,MAAO;AAAA;AAAA;AAAA,sBAZe,MAAM,KAAK,CAAE,OAAQ,EAAG,EAAG,CAACC,EAAGC,IAAM,CAGzD,IAAMC,EAAQ,GAAK,KAAK,OAAO,EAAI,GACnC,MAAO;AAAA;AAAA,+CAEoCD,EAAI,CAAC;AAAA,+DACWC,CAAK;AAAA;AAAA,aAGlE,CAAC,EAAE,KAAK,EAAE,CAKuB;AAAA;AAAA;AAAA,SAInC,CAKO,SAASC,EAAmBC,EAA6B,CAE9D,GAAI,CAACA,EACH,OAAOT,EAAoB,2BAA2B,EAGxD,IAAMU,EAAQD,EAAK,MAAM;AAAA,CAAI,EAG7B,OAAIC,EAAM,OAAS,GAAKA,EAAMA,EAAM,OAAS,CAAC,IAAM,IAClDA,EAAM,IAAI,EAGL;AAAA;AAAA;AAAA,kBAGSA,EACC,IACC,CAACC,EAAMC,IAAU;AAAA;AAAA,mDAEcA,EAAQ,CAAC;AAAA,iDACXf,EAAWc,CAAI,GAAK,GAAG;AAAA;AAAA,iBAGtD,EACC,KAAK,EAAE,CAAC;AAAA;AAAA;AAAA,KAI3B,CCjEO,SAASE,EAA2BC,EAAkBC,EAAiBC,EAAoC,CAChH,MAAO,gCAAgCA,CAAkB;AAAA;AAAA,cAE7CC,EAAWH,CAAQ,CAAC;AAAA,MAC5BC,CAAO;AAAA,WAEb,CAUO,SAASG,EAAuBJ,EAAkBC,EAAyB,CAChF,MAAO,WAAWE,EAAWH,CAAQ,CAAC;AAAA,MAClCC,CAAO,EACb,CAWO,SAASI,EACdC,EACAC,EACAC,EACAN,EACQ,CACR,MAAO,gCAAgCA,CAAkB;AAAA;AAAA,kDAETI,CAAQ;AAAA;AAAA,yBAEjCC,CAAW;AAAA,oCACAA,CAAW;AAAA,2BACpBA,CAAW;AAAA,UAC5BC,CAAY;AAAA;AAAA,WAGtB,CCrDO,SAASC,EAAoBC,EAA+B,CAEjE,GAAI,CAACA,EACH,MAAO,CAAC,EAAE,EAGZ,IAAMC,EAAQD,EAAK,MAAM;AAAA,CAAI,EAGzBC,EAAM,OAAS,GAAKA,EAAMA,EAAM,OAAS,CAAC,IAAM,IAClDA,EAAM,IAAI,EAGZ,IAAMC,EAAWD,EAAM,KAAK;AAAA,CAAI,EAE1BE,EADS,OAAO,KAAM,cAAcD,CAAQ,EAClB,MAAM,MAAM;AAAA,CAAI,EAGhD,KAAOC,EAAiB,OAASF,EAAM,QACrCE,EAAiB,IAAI,EAGvB,OAAOA,CACT,CAKO,SAASC,EAAwBC,EAAiCL,EAAoB,CAC3F,IAAMM,EAAYD,EAAU,iBAAiB,YAAY,EAEnDE,EAAoB,IAAY,CACpC,IAAMJ,EAAmBJ,EAAoBC,CAAI,EACjDM,EAAU,QAAQ,CAACE,EAAMC,IAAU,CAChCD,EAAqB,UAAYL,EAAiBM,CAAK,GAAK,GAC/D,CAAC,CACH,EAGI,wBAAyB,OAC3B,oBAAoBF,CAAiB,EAErCA,EAAkB,CAEtB,CCtCO,SAASG,EAAaC,EAAiC,CAC5D,OAAIA,IAAc,OACT,OAGLA,IAAc,QACT,QAIW,OAAO,YAAc,OAAO,WAAW,8BAA8B,EAAE,QACtE,OAAS,OAChC,CAQO,SAASC,EAAkBC,EAA6B,CAC7D,IAAMC,EAAOD,EAAQ,aAAa,OAAO,EACzC,OAAIC,IAAS,QAAUA,IAAS,QACvBA,EAEF,MACT,CChCO,IAAMC,EAAN,KAAe,CACZ,eAAiB,EACjB,aAAe,IAAI,IACnB,kBAAoB,GAK5B,mBAA4B,CAC1B,OAAO,KAAK,cACd,CAKA,kBAAkBC,EAAqB,CACrC,KAAK,eAAiBA,CACxB,CAKA,sBAAgC,CAC9B,OAAO,KAAK,iBACd,CAKA,qBAAqBC,EAAsB,CACzC,KAAK,kBAAoBA,CAC3B,CAKA,cAAcD,EAAwB,CACpC,OAAO,KAAK,aAAa,IAAIA,CAAK,CACpC,CAKA,kBAAkBA,EAAqB,CACrC,KAAK,aAAa,IAAIA,CAAK,CAC7B,CAKA,mBAA0B,CACxB,KAAK,aAAa,MAAM,CAC1B,CAKA,YAAYE,EAA0B,CACpC,OAAOA,EAAM,MACf,CAKA,OAAc,CACZ,KAAK,aAAa,MAAM,EACxB,KAAK,kBAAoB,EAE3B,CACF,EClEO,SAASC,EAAiBC,EAAuBC,EAAgC,CACtF,OAAOD,EACJ,IACC,CAACE,EAAMC,IAAU;AAAA;AAAA,0BAEGA,CAAK;AAAA,iCACEA,IAAUF,EAAiB,OAAS,OAAO;AAAA,uCACrCE,CAAK;AAAA,4BAChBA,IAAUF,EAAiB,IAAM,IAAI;AAAA,8BACnCE,CAAK;AAAA,cACrBC,EAAWF,EAAK,QAAQ,CAAC;AAAA;AAAA,KAGnC,EACC,KAAK,EAAE,CACZ,CAKO,SAASG,EAAsBC,EAAwBC,EAA8B,CAC7ED,EAAW,iBAAiB,cAAc,EAClD,QAASE,GAAQ,CAEpB,IAAMC,EADW,SAAUD,EAAoB,QAAQ,OAAS,GAAG,IACnCD,EAChCC,EAAI,aAAa,gBAAiBC,EAAa,OAAS,OAAO,EAC/DD,EAAI,aAAa,WAAYC,EAAa,IAAM,IAAI,CACtD,CAAC,CACH,CAKO,SAASC,EAAqBJ,EAAwBK,EAA2B,CACpEL,EAAW,iBAAiB,0BAA0B,EAC9D,QAASM,GAAU,CAC3B,IAAMC,EAAa,SAAUD,EAAsB,QAAQ,OAAS,GAAG,EACvEA,EAAM,aAAa,cAAeC,IAAeF,EAAc,OAAS,OAAO,CACjF,CAAC,CACH,CAKO,SAASG,EAAeX,EAAeY,EAAmC,CAC/E,IAAMC,EAAiB,SAAS,cAAc,SAAS,EACvD,OAAAA,EAAe,aAAa,OAAQ,UAAU,EAC9CA,EAAe,aAAa,KAAM,SAASb,CAAK,EAAE,EAClDa,EAAe,aAAa,kBAAmB,OAAOb,CAAK,EAAE,EAC7Da,EAAe,QAAQ,MAAQ,OAAOb,CAAK,EAC3Ca,EAAe,UAAYD,EACpBC,CACT,CAMO,SAASC,EAAuBC,EAAaC,EAAsBC,EAAiC,CACzG,OAAQF,EAAK,CACX,IAAK,YACH,OAAOC,EAAe,EAAIA,EAAe,EAAIC,EAC/C,IAAK,aACH,OAAOD,EAAeC,EAAWD,EAAe,EAAI,EACtD,IAAK,OACH,MAAO,GACT,IAAK,MACH,OAAOC,EACT,QACE,OAAO,IACX,CACF,CC7CO,IAAMC,EAAN,cAAyB,WAAY,CAE1CC,GAAyB,CAAC,EAC1BC,GAAY,IAAIC,EAChBC,GAAuC,KACvCC,GAA0C,KAC1CC,GAA2C,KAE3C,aAAc,CACZ,MAAM,EACN,KAAK,aAAa,CAAE,KAAM,MAAO,CAAC,CACpC,CAMAC,GAASC,EAA6B,CACpC,IAAMC,EAAO,KAAKR,GAAOO,CAAK,EAC9B,GAAI,CAACC,EACH,MAAM,IAAI,MAAM,iBAAiBD,CAAK,YAAY,EAEpD,OAAOC,CACT,CAEA,WAAW,oBAA+B,CACxC,MAAO,CAAC,MAAM,CAChB,CAEA,mBAA0B,CAExB,KAAKC,GAAoB,EAGpB,KAAKC,GAAQ,CACpB,CAEA,sBAA6B,CAEvB,KAAKN,IAAoB,KAAKC,IAChC,KAAKD,GAAiB,oBAAoB,SAAU,KAAKC,EAAmB,CAEhF,CAEA,yBAAyBM,EAAcC,EAAyBC,EAA+B,CAE7F,GAAIF,IAAS,QAAUC,IAAa,MAAQA,IAAaC,EAAU,CAEjE,IAAMC,EAAgB,KAAKb,GAAU,kBAAkB,EAGvD,KAAKA,GAAU,MAAM,EAGhB,KAAKS,GAAQ,EAGdI,EAAgB,KAAKd,GAAO,OAC9B,KAAKC,GAAU,kBAAkBa,CAAa,EAG9C,KAAKb,GAAU,kBAAkB,CAAC,CAEtC,CACF,CAGAQ,IAA4B,CACtB,OAAO,aACT,KAAKL,GAAmB,OAAO,WAAW,8BAA8B,EACxE,KAAKC,GAAsB,IAAM,CAC/B,KAAKF,GAAiB,KACjB,KAAKO,GAAQ,CACpB,EACA,KAAKN,GAAiB,iBAAiB,SAAU,KAAKC,EAAmB,EAE7E,CAEAU,IAAmC,CACjC,OAAI,KAAKZ,GACA,KAAKA,IAGd,KAAKA,GAAiBa,EAAaC,EAAkB,IAAI,CAAC,EACnD,KAAKd,GACd,CAGAe,GAAkBC,EAAmB,GAAa,CAChD,IAAMC,EAAQ,KAAKL,GAAkB,EAC/BM,EAAYC,EAAkB,kBAAkBF,CAAK,EAE3D,GAAID,EAAkB,CACpB,IAAMI,EAAWD,EAAkB,iBAAiB,EACpD,KAAK,WAAY,mBAAqB,CAACD,EAAWE,CAAQ,CAC5D,MACE,KAAK,WAAY,mBAAqB,CAACF,CAAS,CAEpD,CAGA,KAAMX,IAAyB,CAC7B,IAAMc,EAAW,KAAK,aAAa,MAAM,EAEzC,GAAI,CAACA,EAAU,CACb,KAAKC,GAAW,wEAAwE,EACxF,MACF,CAEA,IAAMC,EAAWC,EAAmBH,CAAQ,EAE5C,GAAIE,EAAS,SAAW,EAAG,CACzB,KAAKD,GAAW,wEAAwE,EACxF,MACF,CAGA,IAAMG,EAAaF,EAAS,KAAMG,GAAQ,CAACC,EAAiBD,CAAG,CAAC,EAChE,GAAID,EAAY,CAEd,IAAMG,EAAeC,EAAWJ,CAAU,EAC1C,KAAKH,GACH,qCAAqCM,CAAY,2EACnD,EACA,MACF,CA4BA,GAzBA,KAAK/B,GAAS0B,EAAS,IAAKG,GAAQ,CAClC,GAAI,CACF,GAAM,CAAE,OAAAI,EAAQ,SAAAC,CAAS,EAAIC,EAAeN,CAAG,EAC/C,MAAO,CACL,SAAAK,EACA,OAAAD,EACA,IAAAJ,EACA,KAAM,KACN,MAAO,KACP,OAAQ,EACV,CACF,OAASO,EAAO,CAEd,MAAO,CACL,SAFeC,EAAuBR,CAAG,EAGzC,OAAQA,EACR,IAAAA,EACA,KAAM,KACN,MAAOO,aAAiB,MAAQA,EAAM,QAAU,OAAOA,CAAK,EAC5D,OAAQ,EACV,CACF,CACF,CAAC,EAGGV,EAAS,SAAW,EAAG,CAEzB,IAAMlB,EAAO,KAAKF,GAAS,CAAC,EAC5B,GAAIE,EAAK,MAAO,CACd,KAAKiB,GAAW,uBAAuBjB,EAAK,KAAK,EAAE,EACnD,MACF,CAEA,KAAK,WAAY,UAAY8B,EAC3B9B,EAAK,SACL+B,EAAuB,EACvBjB,EAAkB,uBAAuB,KAAKP,GAAkB,CAAC,CACnE,EACA,KAAKG,GAAkB,EAAK,CAC9B,MAEE,KAAKsB,GAA6B,EAIpC,GAAI,CACF,MAAMC,EAAgB,CACxB,OAASL,EAAO,CACd,IAAMM,EAAWN,aAAiB,MAAQA,EAAM,QAAU,OAAOA,CAAK,EACtE,KAAKX,GAAW,+BAA+BiB,CAAQ,EAAE,EACzD,MACF,CAGIhB,EAAS,SAAW,EACtB,MAAM,KAAKiB,GAAa,CAAC,EAEzB,MAAM,KAAKC,GAAiB,CAEhC,CAEA,KAAMD,GAAapC,EAA8B,CAC/C,IAAMC,EAAO,KAAKF,GAASC,CAAK,EAC1BsC,EAAWvB,EAAkB,uBAAuB,KAAKP,GAAkB,CAAC,EAGlF,KAAK,WAAY,UAAYuB,EAA2B9B,EAAK,SAAU+B,EAAuB,EAAGM,CAAQ,EACzG,KAAK3B,GAAkB,EAAK,EAG5B,MAAM4B,EAAiBtC,EAAM,EAAK,EAElC,IAAMuC,EAAc,KAAK,WAAY,cAAc,SAAS,EAC5D,GAAIvC,EAAK,MAAO,CACduC,EAAa,UAAYC,EAAuBxC,EAAK,SAAUyC,EAAoBzC,EAAK,MAAO,EAAI,CAAC,EAEpG,IAAM0C,EAAcH,EAAa,cAAc,eAAe,EAC1DG,GACFA,EAAY,iBAAiB,QAAS,IAAM,EACpC,UAEJH,EAAa,UAAYC,EAAuBxC,EAAK,SAAU+B,EAAuB,CAAC,EACvF,MAAMO,EAAiBtC,EAAM,EAAI,EACjC,MAAM,KAAKmC,GAAapC,CAAK,KAEjC,CAAC,CAEL,MACEwC,EAAa,UAAYC,EAAuBxC,EAAK,SAAU2C,EAAmB3C,EAAK,IAAI,CAAC,EACxF,OAAO,MACT4C,EAAwB,KAAK,WAAa5C,EAAK,IAAK,CAG1D,CAEA,KAAMoC,IAAkC,CACjC,KAAK3C,GAAU,qBAAqB,EAMvC,KAAKoD,GAAa,KAAKpD,GAAU,kBAAkB,CAAC,GAJpD,MAAM,KAAKqD,GAAqB,EAChC,KAAKrD,GAAU,qBAAqB,EAAI,EAK5C,CAEAuC,IAAqC,CACnC,IAAMe,EAAc,KAAKtD,GAAU,kBAAkB,EAC/CuD,EAAWC,EAAiB,KAAKzD,GAAQuD,CAAW,EACpDV,EAAWvB,EAAkB,uBAAuB,KAAKP,GAAkB,CAAC,EAElF,KAAK,WAAY,UAAY2C,EAAuBF,EAAUD,EAAahB,EAAuB,EAAGM,CAAQ,EAC7G,KAAK3B,GAAkB,EAAI,CAC7B,CAEA,KAAMoC,IAAsC,CAC1C,IAAMC,EAAc,KAAKtD,GAAU,kBAAkB,EAC/CuD,EAAWC,EAAiB,KAAKzD,GAAQuD,CAAW,EACpDV,EAAWvB,EAAkB,uBAAuB,KAAKP,GAAkB,CAAC,EAGlF,KAAK,WAAY,UAAY2C,EAAuBF,EAAUD,EAAahB,EAAuB,EAAGM,CAAQ,EAC7G,KAAK3B,GAAkB,EAAI,EAG3B,IAAMyC,EAAM,KAAK,WAAY,cAAc,qBAAqB,EAChEA,EAAK,iBAAiB,QAAUC,GAAM,CACpC,IAAMC,EAAOD,EAAE,OAAmB,QAAQ,oBAAoB,EAC9D,GAAIC,EAAK,CACP,IAAMC,EAAW,SAAUD,EAAoB,QAAQ,OAAS,GAAG,EAC/DC,IAAa,KAAK7D,GAAU,kBAAkB,IAChD,KAAKA,GAAU,kBAAkB6D,CAAQ,EACpC,KAAKlB,GAAiB,EAE/B,CACF,CAAC,EAGDe,EAAK,iBAAiB,UAAYC,GAAM,CACtC,IAAMG,EAAgBH,EAEtB,GAAI,CADSG,EAAc,OAAmB,QAAQ,oBAAoB,EAExE,OAGF,IAAMC,EAAe,KAAK/D,GAAU,kBAAkB,EAChDgE,EAAW,KAAKjE,GAAO,OAAS,EAEhC8D,EAAWI,EAAuBH,EAAc,IAAKC,EAAcC,CAAQ,EAE7EH,IAAa,OACfF,EAAE,eAAe,EACjB,KAAK3D,GAAU,kBAAkB6D,CAAQ,EACpC,KAAKlB,GAAiB,EAAE,KAAK,IAAM,CAEtC,IAAMuB,EAAS,KAAK,WAAY,cAAc,sBAAsBL,CAAQ,IAAI,EAC5EK,GACDA,EAAuB,MAAM,CAElC,CAAC,EAEL,CAAC,EAGD,KAAKlE,GAAU,kBAAkBsD,CAAW,EAG5C,IAAMa,EAAiB,KAAK,WAAY,cAAc,0BAA0B,EAChF,MAAM,KAAKC,GAAyBd,EAAaa,CAA6B,EAAE,MAAOhC,GAAmB,CACxG,QAAQ,MAAM,8BAA+BA,CAAK,EACjDgC,EAA+B,UAAYnB,EAC1C,2BAA2Bb,aAAiB,MAAQA,EAAM,QAAU,OAAOA,CAAK,CAAC,EACnF,CACF,CAAC,CACH,CAEAiB,GAAaS,EAAwB,CAEnCQ,EAAsB,KAAK,WAAaR,CAAQ,EAGhD,IAAIM,EAAiB,KAAK,WAAY,cAAc,wCAAwCN,CAAQ,IAAI,EAEnGM,IAEHA,EAAiBG,EAAeT,EAAUvB,EAAuB,CAAC,EAGlE,KAAK,WAAY,cAAc,SAAS,EAAG,YAAY6B,CAAc,EACrE,KAAKnE,GAAU,kBAAkB6D,CAAQ,EAGpC,KAAKO,GAAyBP,EAAUM,CAA6B,EAAE,MAAOhC,GAAmB,CACpG,QAAQ,MAAM,8BAA+BA,CAAK,EACjDgC,EAA+B,UAAYnB,EAC1C,2BAA2Bb,aAAiB,MAAQA,EAAM,QAAU,OAAOA,CAAK,CAAC,EACnF,CACF,CAAC,GAIHoC,EAAqB,KAAK,WAAaV,CAAQ,CACjD,CAEA,KAAMO,GAAyB9D,EAAe6D,EAA4C,CAExF,IAAM5D,EAAO,KAAKF,GAASC,CAAK,EAIhC,GAHA,MAAMuC,EAAiBtC,EAAM,EAAK,EAG9BA,EAAK,OAAS,CAACA,EAAK,KAAM,CAC5B,IAAMkC,EAAWlC,EAAK,OAAS,+CAC/B4D,EAAe,UAAYnB,EAAoBP,EAAU,EAAI,EAG7D,IAAMQ,EAAckB,EAAe,cAAc,eAAe,EAC5DlB,GACFA,EAAY,iBAAiB,QAAS,IAAM,EACpC,UAEJkB,EAAe,UAAY7B,EAAuB,EAClD,MAAMO,EAAiBtC,EAAM,EAAI,EACjC,MAAM,KAAK6D,GAAyB9D,EAAO6D,CAAc,KAE7D,CAAC,CAEL,MACEA,EAAe,UAAYjB,EAAmB3C,EAAK,IAAI,EAEnD,OAAO,MACT4C,EAAwBgB,EAAgB5D,EAAK,IAAI,CAGvD,CAEAiB,GAAWgD,EAAuB,CAChC,KAAK,WAAY,UAAY;AAAA,yBACRzC,EAAWyC,CAAO,CAAC;AAAA,MAExC,KAAKvD,GAAkB,EAAK,CAC9B,CACF,EC9YA,eAAe,OAAO,cAAewD,CAAU",
|
|
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.
|
|
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
|