@beforesemicolon/builder 1.8.0 → 1.8.11

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.
Files changed (51) hide show
  1. package/dist/cjs/docs/markdown-layout/index.js +1 -0
  2. package/dist/cjs/docs/markdown-layout/marked-extension.js +1 -0
  3. package/dist/cjs/docs/markdown-layout/parser.js +7 -0
  4. package/dist/cjs/docs/markdown-layout/renderer.js +1 -0
  5. package/dist/cjs/docs/markdown-layout/types.js +1 -0
  6. package/dist/cjs/docs/run.js +76 -1
  7. package/dist/cjs/docs/templates/fading-citrus/layouts/_code-snippet.js +53 -0
  8. package/dist/cjs/docs/templates/fading-citrus/layouts/_layout-utils.js +89 -0
  9. package/dist/cjs/docs/templates/fading-citrus/layouts/document.js +6 -3
  10. package/dist/cjs/docs/templates/fading-citrus/layouts/landing-cta.js +30 -0
  11. package/dist/cjs/docs/templates/fading-citrus/layouts/landing-ecosystem.js +43 -0
  12. package/dist/cjs/docs/templates/fading-citrus/layouts/landing-features.js +25 -0
  13. package/dist/cjs/docs/templates/fading-citrus/layouts/landing-hero.js +81 -0
  14. package/dist/cjs/docs/templates/fading-citrus/layouts/landing-install.js +116 -0
  15. package/dist/cjs/docs/templates/fading-citrus/layouts/landing-showcase.js +125 -0
  16. package/dist/cjs/docs/templates/fading-citrus/layouts/landing.js +7 -617
  17. package/dist/cjs/docs/templates/fading-citrus/stylesheets/common.css +5 -4
  18. package/dist/cjs/docs/templates/fading-citrus/stylesheets/documentation.css +5 -71
  19. package/dist/cjs/docs/templates/fading-citrus/stylesheets/landing.css +4 -1
  20. package/dist/cjs/docs/templates/fading-citrus/template.config.js +101 -2
  21. package/dist/cjs/docs/types.js +1 -1
  22. package/dist/esm/docs/markdown-layout/index.js +1 -0
  23. package/dist/esm/docs/markdown-layout/marked-extension.js +1 -0
  24. package/dist/esm/docs/markdown-layout/parser.js +7 -0
  25. package/dist/esm/docs/markdown-layout/renderer.js +1 -0
  26. package/dist/esm/docs/markdown-layout/types.js +0 -0
  27. package/dist/esm/docs/run.js +76 -1
  28. package/dist/esm/docs/templates/fading-citrus/layouts/_code-snippet.js +53 -0
  29. package/dist/esm/docs/templates/fading-citrus/layouts/_layout-utils.js +89 -0
  30. package/dist/esm/docs/templates/fading-citrus/layouts/document.js +6 -3
  31. package/dist/esm/docs/templates/fading-citrus/layouts/landing-cta.js +30 -0
  32. package/dist/esm/docs/templates/fading-citrus/layouts/landing-ecosystem.js +43 -0
  33. package/dist/esm/docs/templates/fading-citrus/layouts/landing-features.js +25 -0
  34. package/dist/esm/docs/templates/fading-citrus/layouts/landing-hero.js +81 -0
  35. package/dist/esm/docs/templates/fading-citrus/layouts/landing-install.js +116 -0
  36. package/dist/esm/docs/templates/fading-citrus/layouts/landing-showcase.js +125 -0
  37. package/dist/esm/docs/templates/fading-citrus/layouts/landing.js +7 -617
  38. package/dist/esm/docs/templates/fading-citrus/stylesheets/common.css +5 -4
  39. package/dist/esm/docs/templates/fading-citrus/stylesheets/documentation.css +5 -71
  40. package/dist/esm/docs/templates/fading-citrus/stylesheets/landing.css +4 -1
  41. package/dist/esm/docs/templates/fading-citrus/template.config.js +101 -2
  42. package/dist/types/docs/markdown-layout/index.d.ts +4 -0
  43. package/dist/types/docs/markdown-layout/marked-extension.d.ts +8 -0
  44. package/dist/types/docs/markdown-layout/parser.d.ts +12 -0
  45. package/dist/types/docs/markdown-layout/renderer.d.ts +3 -0
  46. package/dist/types/docs/markdown-layout/types.d.ts +35 -0
  47. package/dist/types/docs/run.d.ts +12 -1
  48. package/dist/types/docs/types.d.ts +22 -0
  49. package/package.json +3 -2
  50. package/dist/cjs/docs/templates/fading-citrus/layouts/_copy-code.js +0 -50
  51. package/dist/esm/docs/templates/fading-citrus/layouts/_copy-code.js +0 -50
@@ -115,7 +115,6 @@ h6 {
115
115
  font-weight: 800;
116
116
  }
117
117
 
118
- h1 a,
119
118
  h2 a,
120
119
  h3 a,
121
120
  h4 a,
@@ -126,7 +125,6 @@ h6 a {
126
125
  color: inherit;
127
126
  }
128
127
 
129
- h1 a::before,
130
128
  h2 a::before,
131
129
  h3 a::before,
132
130
  h4 a::before,
@@ -138,12 +136,15 @@ h6 a::before {
138
136
  position: absolute;
139
137
  top: 50%;
140
138
  left: 0;
141
- transform: translate(calc(-100% - 8px), -50%);
139
+ transform: translate(calc(-100% - 4px), -50%);
142
140
  color: var(--primary);
143
141
  transition: opacity 0.2s ease;
144
142
  }
145
143
 
146
- h1 a:hover::before,
144
+ h2 a::before {
145
+ font-size: 50%;
146
+ }
147
+
147
148
  h2 a:hover::before,
148
149
  h3 a:hover::before,
149
150
  h4 a:hover::before,
@@ -155,13 +155,17 @@ body {
155
155
  }
156
156
 
157
157
  #documentation article h2 {
158
- font-size: 2.75rem;
158
+ font-size: 2rem;
159
159
  font-weight: 850;
160
160
  letter-spacing: -0.03rem;
161
161
  margin-bottom: 24px;
162
162
  line-height: 1.15;
163
163
  }
164
164
 
165
+ #documentation article h2:first-of-type {
166
+ font-size: 3rem;
167
+ }
168
+
165
169
  #documentation article h3 {
166
170
  font-weight: 700;
167
171
  font-size: 1.75rem;
@@ -377,64 +381,6 @@ body {
377
381
  display: none;
378
382
  }
379
383
 
380
- #documentation.docs-index article h2:first-child::before {
381
- content: '// documentation';
382
- display: block;
383
- font-family: var(--font-mono);
384
- font-size: 0.85rem;
385
- font-weight: 500;
386
- color: var(--primary);
387
- text-transform: uppercase;
388
- letter-spacing: 0.15rem;
389
- margin-bottom: 18px;
390
- }
391
-
392
- #documentation.docs-index article h2:first-child {
393
- font-size: clamp(3.2rem, 5vw, 4.2rem);
394
- margin-bottom: 22px;
395
- }
396
-
397
- #documentation.docs-index article h2:first-child + p {
398
- font-size: 1.15rem;
399
- max-width: 900px;
400
- color: oklch(0.76 0.012 250);
401
- }
402
-
403
- #documentation.docs-index article h2:first-child + p + p {
404
- display: none;
405
- }
406
-
407
- #documentation.docs-index article h2:first-child + p + p + ul {
408
- display: grid;
409
- grid-template-columns: repeat(3, 1fr);
410
- gap: 16px;
411
- padding: 0;
412
- margin: 34px 0 64px;
413
- list-style: none;
414
- }
415
-
416
- #documentation.docs-index article h2:first-child + p + p + ul li {
417
- margin: 0;
418
- padding: 24px;
419
- min-height: 140px;
420
- border: 1px solid oklch(0.3 0.02 250 / 0.7);
421
- border-radius: 12px;
422
- background: oklch(0.22 0.018 250 / 0.36);
423
- color: var(--muted-foreground);
424
- line-height: 1.6;
425
- }
426
-
427
- #documentation.docs-index article h2:first-child + p + p + ul code {
428
- display: block;
429
- width: fit-content;
430
- margin-bottom: 14px;
431
- border: 0;
432
- background: transparent;
433
- padding: 0;
434
- color: var(--primary);
435
- font-size: 1.05rem;
436
- }
437
-
438
384
  #documentation.docs-index article h3 {
439
385
  border-bottom: 0;
440
386
  padding-bottom: 0;
@@ -583,21 +529,9 @@ body {
583
529
  }
584
530
  }
585
531
 
586
- @media screen and (max-width: 860px) {
587
- #documentation.docs-index article h2:first-child + p + p + ul {
588
- grid-template-columns: 1fr;
589
- }
590
- }
591
-
592
532
  @media screen and (max-width: 640px) {
593
533
  #documentation {
594
534
  padding-left: 20px;
595
535
  padding-right: 20px;
596
536
  }
597
-
598
- #documentation article h2,
599
- #documentation.docs-index article h2:first-child {
600
- font-size: 2.25rem;
601
- line-height: 2.5rem;
602
- }
603
537
  }
@@ -163,7 +163,6 @@
163
163
 
164
164
  .hero-stats {
165
165
  display: flex;
166
- align-items: center;
167
166
  gap: 32px;
168
167
  }
169
168
 
@@ -1485,6 +1484,10 @@
1485
1484
  }
1486
1485
 
1487
1486
  @media screen and (max-width: 768px) {
1487
+ .install-command code {
1488
+ font-size: 0.9rem;
1489
+ }
1490
+
1488
1491
  .hero-section {
1489
1492
  padding: 112px 0 80px;
1490
1493
  }
@@ -1,14 +1,113 @@
1
- import { renderCodeBlock } from './layouts/_code-snippet.js'
1
+ import {
2
+ renderCodeBlock,
3
+ renderCodeCopyScript,
4
+ } from './layouts/_code-snippet.js'
5
+ import landingHero from './layouts/landing-hero.js'
6
+ import landingEcosystem from './layouts/landing-ecosystem.js'
7
+ import landingFeatures from './layouts/landing-features.js'
8
+ import landingShowcase, {
9
+ renderShowcaseScript,
10
+ } from './layouts/landing-showcase.js'
11
+ import landingInstall, {
12
+ renderInstallScript,
13
+ } from './layouts/landing-install.js'
14
+ import landingCta from './layouts/landing-cta.js'
2
15
 
3
16
  export default {
17
+ markdownLayouts: {
18
+ 'landing-hero': landingHero,
19
+ 'landing-ecosystem': landingEcosystem,
20
+ 'landing-features': landingFeatures,
21
+ 'landing-showcase': landingShowcase,
22
+ 'landing-install': landingInstall,
23
+ 'landing-cta': landingCta,
24
+ },
4
25
  markedOptions: {
5
26
  renderer: {
6
27
  code({ lang, raw }) {
7
28
  const rawCode = raw
8
29
  .replace(/^\s*```[^\n]*\n/, '')
9
30
  .replace(/\n\s*```\s*$/, '')
10
- return renderCodeBlock('snippet', rawCode, lang)
31
+ return renderCodeBlock('', rawCode, lang)
11
32
  },
12
33
  },
13
34
  },
35
+ scripts: {
36
+ 'code-copy': {
37
+ match: 'code-copy-btn',
38
+ render: renderCodeCopyScript,
39
+ },
40
+ showcase: {
41
+ match: 'data-fading-citrus-showcase',
42
+ render: renderShowcaseScript,
43
+ },
44
+ install: {
45
+ match: 'data-fading-citrus-install',
46
+ render: renderInstallScript,
47
+ },
48
+ },
49
+ theme: {
50
+ light: {
51
+ '--background': 'oklch(0.98 0.006 250)',
52
+ '--foreground': 'oklch(0.18 0.015 250)',
53
+ '--card': 'oklch(1 0 0)',
54
+ '--primary': 'oklch(0.66 0.18 45)',
55
+ '--primary-glow': 'oklch(0.72 0.19 50)',
56
+ '--primary-foreground': 'oklch(0.98 0.006 250)',
57
+ '--secondary': 'oklch(0.93 0.01 250)',
58
+ '--muted': 'oklch(0.94 0.01 250)',
59
+ '--muted-foreground': 'oklch(0.42 0.02 250)',
60
+ '--accent': 'oklch(0.58 0.16 200)',
61
+ '--border': 'oklch(0.86 0.018 250)',
62
+ '--ring': 'oklch(0.66 0.18 45)',
63
+ '--code-bg': 'oklch(0.16 0.015 250)',
64
+ '--syntax-comment': 'oklch(0.55 0.02 250)',
65
+ '--syntax-keyword': 'oklch(0.78 0.19 50)',
66
+ '--syntax-string': 'oklch(0.78 0.15 145)',
67
+ '--syntax-tag': 'oklch(0.72 0.16 200)',
68
+ '--syntax-attr': 'oklch(0.82 0.14 90)',
69
+ '--gradient-hero':
70
+ 'radial-gradient(ellipse at top, oklch(0.86 0.08 45 / 0.35), transparent 60%)',
71
+ '--gradient-primary':
72
+ 'linear-gradient(135deg, var(--primary), var(--primary-glow))',
73
+ '--gradient-text':
74
+ 'linear-gradient(135deg, oklch(0.18 0.015 250), oklch(0.35 0.03 250))',
75
+ '--gradient-border':
76
+ 'linear-gradient(135deg, oklch(0.66 0.18 45 / 0.45), oklch(0.58 0.16 200 / 0.25))',
77
+ '--shadow-glow': '0 0 60px -15px oklch(0.66 0.18 45 / 0.35)',
78
+ '--shadow-card':
79
+ '0 1px 0 0 oklch(1 0 0 / 0.75) inset, 0 20px 40px -20px oklch(0.18 0.015 250 / 0.18)',
80
+ },
81
+ dark: {
82
+ '--background': 'oklch(0.18 0.015 250)',
83
+ '--foreground': 'oklch(0.96 0.005 250)',
84
+ '--card': 'oklch(0.22 0.018 250)',
85
+ '--primary': 'oklch(0.74 0.18 45)',
86
+ '--primary-glow': 'oklch(0.78 0.19 50)',
87
+ '--primary-foreground': 'oklch(0.18 0.02 250)',
88
+ '--secondary': 'oklch(0.27 0.02 250)',
89
+ '--muted': 'oklch(0.25 0.018 250)',
90
+ '--muted-foreground': 'oklch(0.68 0.02 250)',
91
+ '--accent': 'oklch(0.7 0.16 200)',
92
+ '--border': 'oklch(0.3 0.02 250)',
93
+ '--ring': 'oklch(0.74 0.18 45)',
94
+ '--code-bg': 'oklch(0.16 0.015 250)',
95
+ '--syntax-comment': 'oklch(0.55 0.02 250)',
96
+ '--syntax-keyword': 'oklch(0.78 0.19 50)',
97
+ '--syntax-string': 'oklch(0.78 0.15 145)',
98
+ '--syntax-tag': 'oklch(0.72 0.16 200)',
99
+ '--syntax-attr': 'oklch(0.82 0.14 90)',
100
+ '--gradient-hero':
101
+ 'radial-gradient(ellipse at top, oklch(0.28 0.08 45 / 0.35), transparent 60%)',
102
+ '--gradient-primary':
103
+ 'linear-gradient(135deg, var(--primary), var(--primary-glow))',
104
+ '--gradient-text':
105
+ 'linear-gradient(135deg, oklch(0.98 0.005 250), oklch(0.78 0.05 250))',
106
+ '--gradient-border':
107
+ 'linear-gradient(135deg, oklch(0.74 0.18 45 / 0.5), oklch(0.7 0.16 200 / 0.3))',
108
+ '--shadow-glow': '0 0 60px -15px oklch(0.74 0.18 45 / 0.5)',
109
+ '--shadow-card':
110
+ '0 1px 0 0 oklch(1 0 0 / 0.05) inset, 0 20px 40px -20px oklch(0 0 0 / 0.5)',
111
+ },
112
+ },
14
113
  }
@@ -0,0 +1,4 @@
1
+ export * from './marked-extension.ts';
2
+ export * from './parser.ts';
3
+ export * from './renderer.ts';
4
+ export * from './types.ts';
@@ -0,0 +1,8 @@
1
+ import { Marked, MarkedExtension } from 'marked';
2
+ import { MarkdownLayoutHandler } from './types.ts';
3
+ export interface MarkdownLayoutExtensionOptions {
4
+ marked: Marked;
5
+ handlers?: Record<string, MarkdownLayoutHandler>;
6
+ renderMarkdown?: (markdown: string) => string;
7
+ }
8
+ export declare const createMarkdownLayoutExtension: ({ marked, handlers, renderMarkdown, }: MarkdownLayoutExtensionOptions) => MarkedExtension;
@@ -0,0 +1,12 @@
1
+ import { MarkdownLayoutNode, MarkdownLayoutOptions } from './types.ts';
2
+ export declare const parseOptions: (input?: string) => MarkdownLayoutOptions;
3
+ export declare const parseLayoutHeader: (line: string) => {
4
+ layoutType: string;
5
+ options: MarkdownLayoutOptions;
6
+ };
7
+ export declare const parseItemHeader: (line: string) => {
8
+ name: string | null;
9
+ options: MarkdownLayoutOptions;
10
+ };
11
+ export declare const parseLayoutBlock: (block: string) => MarkdownLayoutNode;
12
+ export declare const readLayoutBlock: (src: string) => string | null;
@@ -0,0 +1,3 @@
1
+ import { MarkdownLayoutHandler, MarkdownLayoutNode, MarkdownLayoutRenderContext } from './types.ts';
2
+ export declare const renderDefaultMarkdownLayout: (node: MarkdownLayoutNode, context: MarkdownLayoutRenderContext) => string;
3
+ export declare const renderMarkdownLayout: (node: MarkdownLayoutNode, context: MarkdownLayoutRenderContext, handlers?: Record<string, MarkdownLayoutHandler>) => string;
@@ -0,0 +1,35 @@
1
+ import { Marked } from 'marked';
2
+ export type MarkdownLayoutOptionValue = string | number | boolean;
3
+ export interface MarkdownLayoutOptions {
4
+ [key: string]: MarkdownLayoutOptionValue;
5
+ }
6
+ export interface MarkdownLayoutItemNode {
7
+ name: string | null;
8
+ options: MarkdownLayoutOptions;
9
+ body: string;
10
+ }
11
+ export interface MarkdownLayoutNode {
12
+ layoutType: string;
13
+ options: MarkdownLayoutOptions;
14
+ items: MarkdownLayoutItemNode[];
15
+ raw: string;
16
+ }
17
+ export interface MarkdownLayoutToken extends MarkdownLayoutNode {
18
+ type: 'markdownLayout';
19
+ }
20
+ export interface MarkdownLayoutRenderContext {
21
+ marked: Marked;
22
+ renderMarkdown(markdown: string): string;
23
+ renderDefault(node: MarkdownLayoutNode): string;
24
+ renderParts(node: MarkdownLayoutNode): MarkdownLayoutRenderedPart[];
25
+ }
26
+ export type MarkdownLayoutHandler = (layout: MarkdownLayoutHandlerInput, context: MarkdownLayoutRenderContext) => string;
27
+ export interface MarkdownLayoutRenderedPart extends MarkdownLayoutItemNode {
28
+ html: string;
29
+ }
30
+ export interface MarkdownLayoutHandlerInput {
31
+ type: string;
32
+ options: MarkdownLayoutOptions;
33
+ parts: MarkdownLayoutRenderedPart[];
34
+ raw: string;
35
+ }
@@ -1,8 +1,19 @@
1
1
  import { MarkedExtension } from 'marked';
2
+ type GeneratedFilesConfig = {
3
+ sitemap: boolean;
4
+ robots: boolean;
5
+ llms: boolean;
6
+ llmsFull: boolean;
7
+ netlify: boolean;
8
+ };
9
+ type GeneratedFilesOptions = boolean | Partial<GeneratedFilesConfig>;
2
10
  export interface BuildDocsOptions {
3
11
  publicDir?: string;
4
12
  srcDir?: string;
5
13
  markedOptions?: MarkedExtension;
6
14
  template?: string;
15
+ siteUrl?: string;
16
+ generatedFiles?: GeneratedFilesOptions;
7
17
  }
8
- export declare const buildDocs: ({ srcDir, publicDir, markedOptions, template, }?: BuildDocsOptions) => Promise<void>;
18
+ export declare const buildDocs: ({ srcDir, publicDir, markedOptions, template, siteUrl, generatedFiles, }?: BuildDocsOptions) => Promise<void>;
19
+ export {};
@@ -1,3 +1,16 @@
1
+ import { MarkedExtension } from 'marked';
2
+ import { MarkdownLayoutHandler } from './markdown-layout/types.ts';
3
+ export type DocsThemeMode = Record<string, string | number>;
4
+ export interface DocsTheme {
5
+ light?: DocsThemeMode;
6
+ dark?: DocsThemeMode;
7
+ }
8
+ export type DocsScriptMatcher = string | string[] | RegExp | ((html: string) => boolean);
9
+ export interface DocsScriptDefinition {
10
+ match?: DocsScriptMatcher;
11
+ render: () => string;
12
+ }
13
+ export type DocsScriptRegistry = Record<string, false | DocsScriptDefinition | (() => string)>;
1
14
  export interface PageProps {
2
15
  name?: string;
3
16
  path?: string;
@@ -16,6 +29,9 @@ export interface PageProps {
16
29
  version: string;
17
30
  [key: string]: unknown;
18
31
  };
32
+ renderMarkdown?: (markdown: string) => string;
33
+ scripts?: string[];
34
+ themeStylesheet?: string;
19
35
  }
20
36
  export interface CustomOptions {
21
37
  name?: string;
@@ -37,3 +53,9 @@ export interface CustomOptions {
37
53
  [key: string]: unknown;
38
54
  }
39
55
  export type SiteMap = Map<string, CustomOptions | SiteMap>;
56
+ export interface DocsTemplateConfig {
57
+ markedOptions?: MarkedExtension;
58
+ markdownLayouts?: Record<string, MarkdownLayoutHandler>;
59
+ scripts?: DocsScriptRegistry;
60
+ theme?: DocsTheme;
61
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@beforesemicolon/builder",
3
- "version": "1.8.0",
3
+ "version": "1.8.11",
4
4
  "description": "Utilities to build npm packages and documentation website",
5
5
  "engines": {
6
6
  "node": ">=18.16.0"
@@ -21,7 +21,8 @@
21
21
  "scripts": {
22
22
  "build": "rm -rf dist && tsc --emitDeclarationOnly && tsx ./build.ts",
23
23
  "lint": "eslint ./src && prettier --check .",
24
- "format": "eslint ./src --fix && prettier --write ."
24
+ "format": "eslint ./src --fix && prettier --write .",
25
+ "test": "tsx src/docs/markdown-layout/markdown-layout.spec.ts"
25
26
  },
26
27
  "author": "Elson Correia",
27
28
  "license": "BSD-3-Clause",
@@ -1,50 +0,0 @@
1
- export default () => `
2
- <script type="application/javascript">
3
- // handle all possible copy snippet code button on the page
4
- // this implementation is in sync with CodeSnippet partial
5
- document.querySelectorAll('.code-copy-btn').forEach((copyBtn) => {
6
- const snippet = copyBtn.closest('.code-snippet')
7
- const hasCode = snippet?.dataset.code || snippet?.querySelector('code') || snippet?.querySelector('.content')
8
- if (hasCode) {
9
- copyBtn.style.visibility = 'visible'
10
- }
11
- })
12
-
13
- document.addEventListener('click', (e) => {
14
- const copyBtn = e.target.closest('.code-copy-btn')
15
- if (!copyBtn) return
16
-
17
- const snippet = copyBtn.closest('.code-snippet')
18
- if (!snippet) return
19
-
20
- let code = snippet.dataset.code ?? ''
21
-
22
- if (!code) {
23
- const codeEl = snippet.querySelector('code')
24
- if (codeEl) {
25
- const clone = codeEl.cloneNode(true)
26
- clone.querySelectorAll('.line-number').forEach(el => el.remove())
27
- code = clone.textContent ?? ''
28
- }
29
- }
30
-
31
- if (!code) {
32
- code = snippet.querySelector('.content')?.textContent ?? ''
33
- }
34
-
35
- // no code, no copy button
36
- if (code.trim().length) {
37
- clearTimeout(copyBtn._timer)
38
- navigator.clipboard.writeText(code.trim())
39
- copyBtn.textContent = 'copied'
40
- copyBtn.setAttribute('aria-label', 'Code copied')
41
- copyBtn.classList.add('copied')
42
- copyBtn._timer = setTimeout(() => {
43
- copyBtn.textContent = 'copy'
44
- copyBtn.setAttribute('aria-label', 'Copy code')
45
- copyBtn.classList.remove('copied')
46
- }, 1000)
47
- }
48
- })
49
- </script>
50
- `
@@ -1,50 +0,0 @@
1
- export default () => `
2
- <script type="application/javascript">
3
- // handle all possible copy snippet code button on the page
4
- // this implementation is in sync with CodeSnippet partial
5
- document.querySelectorAll('.code-copy-btn').forEach((copyBtn) => {
6
- const snippet = copyBtn.closest('.code-snippet')
7
- const hasCode = snippet?.dataset.code || snippet?.querySelector('code') || snippet?.querySelector('.content')
8
- if (hasCode) {
9
- copyBtn.style.visibility = 'visible'
10
- }
11
- })
12
-
13
- document.addEventListener('click', (e) => {
14
- const copyBtn = e.target.closest('.code-copy-btn')
15
- if (!copyBtn) return
16
-
17
- const snippet = copyBtn.closest('.code-snippet')
18
- if (!snippet) return
19
-
20
- let code = snippet.dataset.code ?? ''
21
-
22
- if (!code) {
23
- const codeEl = snippet.querySelector('code')
24
- if (codeEl) {
25
- const clone = codeEl.cloneNode(true)
26
- clone.querySelectorAll('.line-number').forEach(el => el.remove())
27
- code = clone.textContent ?? ''
28
- }
29
- }
30
-
31
- if (!code) {
32
- code = snippet.querySelector('.content')?.textContent ?? ''
33
- }
34
-
35
- // no code, no copy button
36
- if (code.trim().length) {
37
- clearTimeout(copyBtn._timer)
38
- navigator.clipboard.writeText(code.trim())
39
- copyBtn.textContent = 'copied'
40
- copyBtn.setAttribute('aria-label', 'Code copied')
41
- copyBtn.classList.add('copied')
42
- copyBtn._timer = setTimeout(() => {
43
- copyBtn.textContent = 'copy'
44
- copyBtn.setAttribute('aria-label', 'Copy code')
45
- copyBtn.classList.remove('copied')
46
- }, 1000)
47
- }
48
- })
49
- </script>
50
- `