@dominikcz/greg 0.9.27

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 (183) hide show
  1. package/README.md +397 -0
  2. package/bin/greg.js +241 -0
  3. package/bin/init.js +351 -0
  4. package/bin/templates/docs/getting-started.md +47 -0
  5. package/bin/templates/docs/index.md +11 -0
  6. package/bin/templates/greg.config.js +39 -0
  7. package/bin/templates/greg.config.ts +38 -0
  8. package/bin/templates/index.html +16 -0
  9. package/bin/templates/src/App.svelte +5 -0
  10. package/bin/templates/src/app.css +20 -0
  11. package/bin/templates/src/main.js +9 -0
  12. package/bin/templates/svelte.config.js +1 -0
  13. package/bin/templates/tsconfig.json +21 -0
  14. package/bin/templates/vite.config.js +23 -0
  15. package/docs/__partials/markdown/examples/basic.md +4 -0
  16. package/docs/__partials/markdown/examples/diff.md +10 -0
  17. package/docs/__partials/markdown/examples/focus.md +5 -0
  18. package/docs/__partials/markdown/examples/language-title.md +3 -0
  19. package/docs/__partials/markdown/examples/line-highlighting.md +5 -0
  20. package/docs/__partials/markdown/examples/line-numbers.md +5 -0
  21. package/docs/__partials/note.md +4 -0
  22. package/docs/guide/__shared-warning.md +4 -0
  23. package/docs/guide/asset-handling.md +88 -0
  24. package/docs/guide/deploying.md +162 -0
  25. package/docs/guide/getting-started.md +334 -0
  26. package/docs/guide/index.md +23 -0
  27. package/docs/guide/localization.md +290 -0
  28. package/docs/guide/markdown/code.md +95 -0
  29. package/docs/guide/markdown/components-and-mermaid.md +43 -0
  30. package/docs/guide/markdown/containers.md +110 -0
  31. package/docs/guide/markdown/header-anchors.md +34 -0
  32. package/docs/guide/markdown/includes.md +84 -0
  33. package/docs/guide/markdown/index.md +20 -0
  34. package/docs/guide/markdown/inline-attributes.md +21 -0
  35. package/docs/guide/markdown/links-and-toc.md +64 -0
  36. package/docs/guide/markdown/math.md +54 -0
  37. package/docs/guide/markdown/syntax-highlighting.md +75 -0
  38. package/docs/guide/routing.md +150 -0
  39. package/docs/guide/using-svelte.md +88 -0
  40. package/docs/guide/versioning.md +281 -0
  41. package/docs/incompatibilities.md +48 -0
  42. package/docs/index.md +43 -0
  43. package/docs/reference/badge.md +100 -0
  44. package/docs/reference/carbon-ads.md +46 -0
  45. package/docs/reference/code-group.md +126 -0
  46. package/docs/reference/home-page.md +232 -0
  47. package/docs/reference/index.md +18 -0
  48. package/docs/reference/markdowndocs.md +275 -0
  49. package/docs/reference/outline.md +79 -0
  50. package/docs/reference/search.md +263 -0
  51. package/docs/reference/steps.md +200 -0
  52. package/docs/reference/team-page.md +189 -0
  53. package/docs/reference/theme.md +150 -0
  54. package/fakeDocsGenerator/generate_docs.js +310 -0
  55. package/package.json +92 -0
  56. package/scripts/build-versions.js +609 -0
  57. package/scripts/generate-static.js +79 -0
  58. package/scripts/render-markdown.js +420 -0
  59. package/src/lib/MarkdownDocs/AiChat.svelte +936 -0
  60. package/src/lib/MarkdownDocs/BackToTop.svelte +68 -0
  61. package/src/lib/MarkdownDocs/Breadcrumb.svelte +68 -0
  62. package/src/lib/MarkdownDocs/DocsNavigation.svelte +149 -0
  63. package/src/lib/MarkdownDocs/DocsSiteHeader.svelte +758 -0
  64. package/src/lib/MarkdownDocs/DocsVersionSwitcher.svelte +103 -0
  65. package/src/lib/MarkdownDocs/MarkdownDocs.svelte +2115 -0
  66. package/src/lib/MarkdownDocs/MarkdownRenderer.svelte +487 -0
  67. package/src/lib/MarkdownDocs/Outline.svelte +238 -0
  68. package/src/lib/MarkdownDocs/PrevNext.svelte +115 -0
  69. package/src/lib/MarkdownDocs/SearchModal.svelte +1241 -0
  70. package/src/lib/MarkdownDocs/TreeView.svelte +32 -0
  71. package/src/lib/MarkdownDocs/TreeViewItem.svelte +219 -0
  72. package/src/lib/MarkdownDocs/VersionOutdatedNotice.svelte +72 -0
  73. package/src/lib/MarkdownDocs/__tests__/codeDirectives.test.js +54 -0
  74. package/src/lib/MarkdownDocs/__tests__/common.test.js +41 -0
  75. package/src/lib/MarkdownDocs/__tests__/docsExamplesLint.test.js +77 -0
  76. package/src/lib/MarkdownDocs/__tests__/fixtures/docs/markdown/__partial-basic.md +3 -0
  77. package/src/lib/MarkdownDocs/__tests__/fixtures/docs/markdown/snippet.js +9 -0
  78. package/src/lib/MarkdownDocs/__tests__/fixtures/includes/part.md +11 -0
  79. package/src/lib/MarkdownDocs/__tests__/fixtures/includes/wrapper.md +5 -0
  80. package/src/lib/MarkdownDocs/__tests__/fixtures/snippets/sample.js +8 -0
  81. package/src/lib/MarkdownDocs/__tests__/fixtures/snippets/sample.md +5 -0
  82. package/src/lib/MarkdownDocs/__tests__/helpers.js +67 -0
  83. package/src/lib/MarkdownDocs/__tests__/localeUtils.test.js +204 -0
  84. package/src/lib/MarkdownDocs/__tests__/markdown.test.js +704 -0
  85. package/src/lib/MarkdownDocs/__tests__/markdownRendererRuntime.test.js +65 -0
  86. package/src/lib/MarkdownDocs/__tests__/searchIndexBuilder.test.js +117 -0
  87. package/src/lib/MarkdownDocs/__tests__/sqliteStore.test.js +202 -0
  88. package/src/lib/MarkdownDocs/__tests__/useRouter.test.js +16 -0
  89. package/src/lib/MarkdownDocs/ai/adapters/customAdapter.js +14 -0
  90. package/src/lib/MarkdownDocs/ai/adapters/customAdapter.ts +43 -0
  91. package/src/lib/MarkdownDocs/ai/adapters/ollamaAdapter.js +81 -0
  92. package/src/lib/MarkdownDocs/ai/adapters/ollamaAdapter.ts +116 -0
  93. package/src/lib/MarkdownDocs/ai/adapters/openaiAdapter.js +92 -0
  94. package/src/lib/MarkdownDocs/ai/adapters/openaiAdapter.ts +137 -0
  95. package/src/lib/MarkdownDocs/ai/aiProvider.ts +31 -0
  96. package/src/lib/MarkdownDocs/ai/characters.js +52 -0
  97. package/src/lib/MarkdownDocs/ai/characters.ts +69 -0
  98. package/src/lib/MarkdownDocs/ai/chunkStore.ts +25 -0
  99. package/src/lib/MarkdownDocs/ai/chunker.js +85 -0
  100. package/src/lib/MarkdownDocs/ai/chunker.ts +135 -0
  101. package/src/lib/MarkdownDocs/ai/docLinker.js +26 -0
  102. package/src/lib/MarkdownDocs/ai/docLinker.ts +36 -0
  103. package/src/lib/MarkdownDocs/ai/promptBuilder.js +33 -0
  104. package/src/lib/MarkdownDocs/ai/promptBuilder.ts +53 -0
  105. package/src/lib/MarkdownDocs/ai/ragPipeline.js +54 -0
  106. package/src/lib/MarkdownDocs/ai/ragPipeline.ts +106 -0
  107. package/src/lib/MarkdownDocs/ai/stores/memoryStore.js +88 -0
  108. package/src/lib/MarkdownDocs/ai/stores/memoryStore.ts +112 -0
  109. package/src/lib/MarkdownDocs/ai/stores/sqliteStore.ts +372 -0
  110. package/src/lib/MarkdownDocs/ai/types.ts +71 -0
  111. package/src/lib/MarkdownDocs/aiServer.js +288 -0
  112. package/src/lib/MarkdownDocs/codeDirectives.js +191 -0
  113. package/src/lib/MarkdownDocs/codeFenceInfo.js +45 -0
  114. package/src/lib/MarkdownDocs/codeGroup.ts +46 -0
  115. package/src/lib/MarkdownDocs/common.ts +47 -0
  116. package/src/lib/MarkdownDocs/docsUtils.js +281 -0
  117. package/src/lib/MarkdownDocs/index.plugins.js +22 -0
  118. package/src/lib/MarkdownDocs/layouts/LayoutDoc.svelte +8 -0
  119. package/src/lib/MarkdownDocs/layouts/LayoutHome.svelte +58 -0
  120. package/src/lib/MarkdownDocs/layouts/LayoutPage.svelte +9 -0
  121. package/src/lib/MarkdownDocs/loadGregConfig.js +82 -0
  122. package/src/lib/MarkdownDocs/localeUtils.ts +682 -0
  123. package/src/lib/MarkdownDocs/markdownRendererRuntime.ts +314 -0
  124. package/src/lib/MarkdownDocs/mermaidThemes.js +319 -0
  125. package/src/lib/MarkdownDocs/navigationUtils.js +22 -0
  126. package/src/lib/MarkdownDocs/rehypeCodeGroup.js +326 -0
  127. package/src/lib/MarkdownDocs/rehypeCodeTitle.js +96 -0
  128. package/src/lib/MarkdownDocs/rehypeToc.js +170 -0
  129. package/src/lib/MarkdownDocs/remarkCodeMeta.js +22 -0
  130. package/src/lib/MarkdownDocs/remarkContainers.js +329 -0
  131. package/src/lib/MarkdownDocs/remarkCustomAnchors.js +42 -0
  132. package/src/lib/MarkdownDocs/remarkEscapeSvelte.js +33 -0
  133. package/src/lib/MarkdownDocs/remarkGlobalComponents.js +65 -0
  134. package/src/lib/MarkdownDocs/remarkImports.js +461 -0
  135. package/src/lib/MarkdownDocs/remarkImportsBrowser.js +349 -0
  136. package/src/lib/MarkdownDocs/remarkInlineAttrs.js +95 -0
  137. package/src/lib/MarkdownDocs/remarkMathToHtml.js +138 -0
  138. package/src/lib/MarkdownDocs/searchIndexBuilder.js +497 -0
  139. package/src/lib/MarkdownDocs/searchServer.js +263 -0
  140. package/src/lib/MarkdownDocs/treeViewTypes.ts +11 -0
  141. package/src/lib/MarkdownDocs/useRouter.svelte.ts +114 -0
  142. package/src/lib/MarkdownDocs/useSplitter.svelte.ts +33 -0
  143. package/src/lib/MarkdownDocs/versioningDefaults.js +20 -0
  144. package/src/lib/MarkdownDocs/vitePluginAiServer.js +204 -0
  145. package/src/lib/MarkdownDocs/vitePluginCopyDocs.js +153 -0
  146. package/src/lib/MarkdownDocs/vitePluginFrontmatter.js +109 -0
  147. package/src/lib/MarkdownDocs/vitePluginGregConfig.js +108 -0
  148. package/src/lib/MarkdownDocs/vitePluginSearchIndex.js +57 -0
  149. package/src/lib/MarkdownDocs/vitePluginSearchServer.js +190 -0
  150. package/src/lib/components/Badge.svelte +59 -0
  151. package/src/lib/components/Button.svelte +138 -0
  152. package/src/lib/components/CarbonAds.svelte +99 -0
  153. package/src/lib/components/CodeGroup.svelte +102 -0
  154. package/src/lib/components/Feature.svelte +209 -0
  155. package/src/lib/components/Features.svelte +123 -0
  156. package/src/lib/components/Hero.svelte +399 -0
  157. package/src/lib/components/Image.svelte +128 -0
  158. package/src/lib/components/Link.svelte +105 -0
  159. package/src/lib/components/SocialLink.svelte +84 -0
  160. package/src/lib/components/SocialLinks.svelte +33 -0
  161. package/src/lib/components/Steps.svelte +143 -0
  162. package/src/lib/components/TeamMember.svelte +273 -0
  163. package/src/lib/components/TeamMembers.svelte +81 -0
  164. package/src/lib/components/TeamPage.svelte +65 -0
  165. package/src/lib/components/TeamPageSection.svelte +108 -0
  166. package/src/lib/components/TeamPageTitle.svelte +89 -0
  167. package/src/lib/components/index.js +24 -0
  168. package/src/lib/portal/context.js +12 -0
  169. package/src/lib/portal/index.js +3 -0
  170. package/src/lib/portal/portal.svelte +14 -0
  171. package/src/lib/portal/slot.svelte +8 -0
  172. package/src/lib/scss/__code.scss +128 -0
  173. package/src/lib/scss/__containers.scss +99 -0
  174. package/src/lib/scss/__markdown.scss +447 -0
  175. package/src/lib/scss/__scrollbar.scss +60 -0
  176. package/src/lib/scss/__steps.scss +100 -0
  177. package/src/lib/scss/__theme.scss +238 -0
  178. package/src/lib/scss/__toc.scss +55 -0
  179. package/src/lib/scss/__utilities.scss +7 -0
  180. package/src/lib/scss/greg.scss +9 -0
  181. package/src/lib/spinner/spinner.svelte +42 -0
  182. package/svelte.config.js +146 -0
  183. package/types/index.d.ts +456 -0
@@ -0,0 +1,32 @@
1
+ <script lang="ts">
2
+ import TreeViewItem from "./TreeViewItem.svelte";
3
+ import type { TreeViewItem as TreeViewItemType } from "./treeViewTypes";
4
+
5
+ interface TreeViewProps {
6
+ tree: TreeViewItemType[];
7
+ active: string;
8
+ class?: string;
9
+ navigate: (path: string) => void;
10
+ }
11
+ let {
12
+ tree,
13
+ active = "",
14
+ class: className = "",
15
+ navigate,
16
+ }: TreeViewProps = $props();
17
+ </script>
18
+
19
+ <ul class="tree-view {className}">
20
+ {#each tree as item (item.link || item.label)}
21
+ <TreeViewItem {item} depth={0} {active} {navigate} />
22
+ {/each}
23
+ </ul>
24
+
25
+ <style>
26
+ .tree-view {
27
+ margin: 0;
28
+ list-style: none;
29
+ padding-inline-start: 0;
30
+ user-select: none;
31
+ }
32
+ </style>
@@ -0,0 +1,219 @@
1
+ <script module lang="ts">
2
+ // based on https://svelte.dev/playground/82b00644720a4ca2bdb89c6a653ec987
3
+ // retain module scoped expansion state for each tree node
4
+ const _expansionState = new Map<string, boolean>();
5
+ </script>
6
+
7
+ <script lang="ts">
8
+ import Self from "./TreeViewItem.svelte";
9
+ import type { TreeViewItem } from "./treeViewTypes";
10
+ import { ChevronRight } from "@lucide/svelte";
11
+ import { withBase } from "./common";
12
+
13
+ interface TreeViewItemProps {
14
+ item: TreeViewItem;
15
+ active: string;
16
+ depth: number;
17
+ navigate: (path: string) => void;
18
+ }
19
+ let {
20
+ item,
21
+ active = "",
22
+ depth = 0,
23
+ navigate,
24
+ }: TreeViewItemProps = $props();
25
+
26
+ const EXTERNAL_RE = /^(?:[a-z][a-z\d+\-.]*:|\/\/)/i;
27
+
28
+ // Inicjalizacja stanu rozwinięcia
29
+ let expanded = $state(false);
30
+ // Automatycznie rozwiń jeśli aktywna ścieżka jest w tej gałęzi
31
+ let shouldExpand = $derived(
32
+ active.startsWith(item.link) && item.link !== active,
33
+ );
34
+ $effect(() => {
35
+ if (shouldExpand && !expanded) expanded = true;
36
+ });
37
+
38
+ function toggle() {
39
+ _expansionState.set(item.link, !expanded);
40
+ expanded = !expanded;
41
+ }
42
+
43
+ function shouldHandleWithRouter() {
44
+ if (!item.link) return false;
45
+ if (EXTERNAL_RE.test(item.link)) return false;
46
+ if (item.target && item.target !== "_self") return false;
47
+ return true;
48
+ }
49
+
50
+ function handleClick(e: MouseEvent) {
51
+ if (item.target === "_blank") {
52
+ e.preventDefault();
53
+ window.open(item.link, "_blank", "noopener,noreferrer");
54
+ return;
55
+ }
56
+
57
+ if (!shouldHandleWithRouter()) return;
58
+ e.preventDefault();
59
+ navigate(item.link);
60
+ }
61
+
62
+ function handleToggleClick(e: MouseEvent) {
63
+ if (item.target === "_blank") {
64
+ e.preventDefault();
65
+ window.open(item.link, "_blank", "noopener,noreferrer");
66
+ return;
67
+ }
68
+
69
+ if (!shouldHandleWithRouter()) return;
70
+ e.preventDefault();
71
+ toggle();
72
+ navigate(item.link);
73
+ }
74
+ </script>
75
+
76
+ <li>
77
+ {#if item.children.length}
78
+ <a
79
+ href={withBase(item.link)}
80
+ target={item.target}
81
+ rel={item.rel}
82
+ onclick={handleToggleClick}
83
+ class="arrow"
84
+ class:active={active == item.link}
85
+ >
86
+ <span class="chevron" class:expanded>
87
+ <ChevronRight />
88
+ </span>
89
+ {#if item.status}<span class={item.status}
90
+ ></span>{/if}{item.label}{#if item.badge}<span
91
+ class="nav-badge {item.badge.type ?? 'tip'}"
92
+ >{item.badge.text}</span
93
+ >{/if}
94
+ </a>
95
+ {#if expanded}
96
+ <ul>
97
+ {#each item.children as child}
98
+ <Self item={child} depth={depth + 1} {active} {navigate} />
99
+ {/each}
100
+ </ul>
101
+ {/if}
102
+ {:else}
103
+ <a
104
+ href={withBase(item.link)}
105
+ target={item.target}
106
+ rel={item.rel}
107
+ class="no-arrow"
108
+ class:active={active == item.link}
109
+ onclick={handleClick}
110
+ >{#if item.status}<span class={item.status}
111
+ ></span>{/if}{item.label}{#if item.badge}<span
112
+ class="nav-badge {item.badge.type ?? 'tip'}"
113
+ >{item.badge.text}</span
114
+ >{/if}</a
115
+ >
116
+ {/if}
117
+ </li>
118
+
119
+ <style lang="scss">
120
+ li {
121
+ line-height: 1.5rem;
122
+ margin: 0.1rem 0;
123
+ }
124
+
125
+ ul {
126
+ list-style: none;
127
+ padding-inline-start: 0.85rem;
128
+ margin: 0.1rem 0 0 0.6rem;
129
+ border-left: 1px solid var(--greg-border-color);
130
+ }
131
+
132
+ .no-arrow {
133
+ padding-left: 1.1rem;
134
+ }
135
+
136
+ .arrow {
137
+ cursor: pointer;
138
+ display: flex;
139
+
140
+ .chevron {
141
+ display: flex;
142
+ align-items: center;
143
+ transition: transform 0.2s ease;
144
+ transform: rotate(0deg);
145
+ flex-shrink: 0;
146
+
147
+ &.expanded {
148
+ transform: rotate(90deg);
149
+ }
150
+ }
151
+ }
152
+
153
+ a {
154
+ padding: 0.3rem 0.5rem;
155
+ display: block;
156
+ text-decoration: none;
157
+ color: var(--greg-menu-color);
158
+ border-radius: 5px;
159
+ font-size: 0.875rem;
160
+ line-height: 1.5rem;
161
+
162
+ span {
163
+ vertical-align: bottom;
164
+ }
165
+
166
+ &:hover {
167
+ background-color: var(--greg-menu-hover-background);
168
+ color: var(--greg-color);
169
+ }
170
+
171
+ &.active {
172
+ background-color: var(--greg-accent-light);
173
+ color: var(--greg-accent);
174
+ font-weight: 600;
175
+
176
+ .arrow {
177
+ color: var(--greg-accent);
178
+ }
179
+
180
+ &:hover {
181
+ background-color: var(--greg-accent-light);
182
+ }
183
+ }
184
+ }
185
+
186
+ .nav-badge {
187
+ display: inline-block;
188
+ margin-left: 0.4rem;
189
+ padding: 0 7px;
190
+ border-radius: 10px;
191
+ font-size: 11px;
192
+ font-weight: 500;
193
+ line-height: 18px;
194
+ vertical-align: middle;
195
+ white-space: nowrap;
196
+ border: 1px solid transparent;
197
+
198
+ &.tip {
199
+ background-color: var(--greg-tip-bg);
200
+ border-color: var(--greg-tip-border);
201
+ color: var(--greg-tip-text);
202
+ }
203
+ &.info {
204
+ background-color: var(--greg-info-bg);
205
+ border-color: var(--greg-info-border);
206
+ color: var(--greg-info-text);
207
+ }
208
+ &.warning {
209
+ background-color: var(--greg-warning-bg);
210
+ border-color: var(--greg-warning-border);
211
+ color: var(--greg-warning-text);
212
+ }
213
+ &.danger {
214
+ background-color: var(--greg-danger-bg);
215
+ border-color: var(--greg-danger-border);
216
+ color: var(--greg-danger-text);
217
+ }
218
+ }
219
+ </style>
@@ -0,0 +1,72 @@
1
+ <script lang="ts">
2
+ type Props = {
3
+ currentTitle: string;
4
+ defaultTitle: string;
5
+ message?: string;
6
+ actionLabel?: string;
7
+ onGoToDefault: () => void;
8
+ };
9
+
10
+ let {
11
+ currentTitle,
12
+ defaultTitle,
13
+ message = "",
14
+ actionLabel = "Go to latest",
15
+ onGoToDefault,
16
+ }: Props = $props();
17
+
18
+ const resolvedMessage = $derived(
19
+ String(message || "").trim() ||
20
+ `You are viewing an older documentation version (${currentTitle}). ${defaultTitle} is currently recommended.`,
21
+ );
22
+ </script>
23
+
24
+ <div class="version-notice" role="status" aria-live="polite">
25
+ <p>{resolvedMessage}</p>
26
+ <button type="button" onclick={onGoToDefault}>{actionLabel}</button>
27
+ </div>
28
+
29
+ <style lang="scss">
30
+ .version-notice {
31
+ display: flex;
32
+ align-items: center;
33
+ justify-content: space-between;
34
+ gap: 0.75rem;
35
+ padding: 0.55rem 1.25rem;
36
+ border-bottom: 1px solid color-mix(in srgb, var(--greg-accent) 25%, var(--greg-border-color));
37
+ background: color-mix(in srgb, var(--greg-accent-light) 55%, var(--greg-header-background));
38
+ color: var(--greg-color);
39
+ flex-shrink: 0;
40
+
41
+ p {
42
+ margin: 0;
43
+ font-size: 0.86rem;
44
+ line-height: 1.35;
45
+ }
46
+
47
+ button {
48
+ border: 1px solid color-mix(in srgb, var(--greg-accent) 45%, var(--greg-border-color));
49
+ background: var(--greg-header-background);
50
+ color: var(--greg-accent);
51
+ font: inherit;
52
+ font-size: 0.8rem;
53
+ font-weight: 600;
54
+ border-radius: 6px;
55
+ padding: 0.25rem 0.55rem;
56
+ cursor: pointer;
57
+
58
+ &:hover {
59
+ border-color: var(--greg-accent);
60
+ background: var(--greg-accent-light);
61
+ }
62
+ }
63
+ }
64
+
65
+ @media (max-width: 800px) {
66
+ .version-notice {
67
+ flex-direction: column;
68
+ align-items: flex-start;
69
+ padding: 0.55rem 0.75rem;
70
+ }
71
+ }
72
+ </style>
@@ -0,0 +1,54 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { parseCodeDirectives, decorateHighlightedCodeHtml } from '../codeDirectives.js';
3
+ import { normalizeCodeFenceInfo } from '../codeFenceInfo.js';
4
+
5
+ describe('codeDirectives', () => {
6
+ it('parses meta line ranges and strips inline code markers', () => {
7
+ const parsed = parseCodeDirectives('const a = 1; // [!code ++]\nconst token = a;', '{2} [demo.ts]');
8
+ expect(parsed.cleanedCode).toContain('const a = 1;');
9
+ expect(parsed.cleanedCode).not.toContain('[!code');
10
+ expect(parsed.lineInfo[0].diffAdd).toBe(true);
11
+ expect(parsed.lineInfo[1].highlight).toBe(true);
12
+ });
13
+
14
+ it('decorates line wrappers with classes', () => {
15
+ const directives = parseCodeDirectives('const oldValue = 1; // [!code --]\nconst token = oldValue; // [!code focus]', '{1}');
16
+ const html = '<pre><code><span class="line">const oldValue = 1;</span>\n<span class="line">const token = oldValue;</span></code></pre>';
17
+ const out = decorateHighlightedCodeHtml(html, directives);
18
+
19
+ expect(out).toContain('line highlighted diff remove');
20
+ expect(out).toContain('line focused');
21
+ expect(out).toContain('has-focused-lines');
22
+ });
23
+
24
+ it('does not strip markers inside markdown source fences', () => {
25
+ const parsed = parseCodeDirectives('const oldValue = 1; // [!code --]\n', '', 'md');
26
+ expect(parsed.cleanedCode).toContain('[!code --]');
27
+ expect(parsed.cleanedCode.endsWith('\n')).toBe(false);
28
+ expect(parsed.lineInfo).toEqual([]);
29
+ });
30
+
31
+ it('supports line numbers metadata with custom start index', () => {
32
+ const parsed = parseCodeDirectives('const a = 1;\nconst b = 2;', 'line-numbers=10', 'ts');
33
+ expect(parsed.lineNumbers).toEqual({ enabled: true, start: 10 });
34
+
35
+ const html = '<pre><code><span class="line">const a = 1;</span>\n<span class="line">const b = 2;</span></code></pre>';
36
+ const out = decorateHighlightedCodeHtml(html, parsed);
37
+ expect(out).toContain('data-line-numbers="true"');
38
+ expect(out).toContain('data-line="10"');
39
+ expect(out).toContain('data-line="11"');
40
+ });
41
+
42
+ it('enables line numbers with default start when no index is provided', () => {
43
+ const parsed = parseCodeDirectives('const a = 1;', 'line-numbers', 'ts');
44
+ expect(parsed.lineNumbers).toEqual({ enabled: true, start: 1 });
45
+ });
46
+
47
+ it('normalizes vitepress-style lang/meta forms', () => {
48
+ expect(normalizeCodeFenceInfo('ts{2}', '')).toEqual({ lang: 'ts', meta: '{2}' });
49
+ expect(normalizeCodeFenceInfo('ts:line-numbers=10', '{1}')).toEqual({
50
+ lang: 'ts',
51
+ meta: '{1} line-numbers=10',
52
+ });
53
+ });
54
+ });
@@ -0,0 +1,41 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ import { pathConfig, withoutBase } from '../common';
4
+
5
+ function setConfig(config) {
6
+ Object.assign(pathConfig, config);
7
+ }
8
+
9
+ beforeEach(() => {
10
+ setConfig({ base: '/greg/', srcDir: 'my_docs', docsBase: 'documentation' });
11
+ });
12
+
13
+ describe('common.withoutBase', () => {
14
+ it('maps {base}/{docsBase}/... to {srcDir}/... when all fields are set', () => {
15
+ setConfig({ base: '/greg/', srcDir: 'my_docs', docsBase: 'documentation' });
16
+
17
+ expect(withoutBase('/greg/documentation/guide/getting-started')).toBe('/documentation/guide/getting-started');
18
+ expect(withoutBase('/greg/documentation')).toBe('/documentation');
19
+ });
20
+
21
+ it('keeps old base-strip behavior for paths outside /{base}/{docsBase}', () => {
22
+ setConfig({ base: '/greg/', srcDir: 'my_docs', docsBase: 'documentation' });
23
+
24
+ expect(withoutBase('/greg/guide/getting-started')).toBe('/guide/getting-started');
25
+ expect(withoutBase('/something-else/guide')).toBe('/something-else/guide');
26
+ });
27
+
28
+ it('supports base="/" and still maps /{docsBase} to {srcDir}', () => {
29
+ setConfig({ base: '/', srcDir: 'content_docs', docsBase: 'documentation' });
30
+
31
+ expect(withoutBase('/documentation/guide/intro')).toBe('/documentation/guide/intro');
32
+ expect(withoutBase('/content_docs/guide/intro')).toBe('/content_docs/guide/intro');
33
+ });
34
+
35
+ it('keeps external URLs and relative paths unchanged', () => {
36
+ setConfig({ base: '/greg/', srcDir: 'my_docs', docsBase: 'documentation' });
37
+
38
+ expect(withoutBase('https://example.com/docs')).toBe('https://example.com/docs');
39
+ expect(withoutBase('guide/getting-started')).toBe('guide/getting-started');
40
+ });
41
+ });
@@ -0,0 +1,77 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import fs from 'node:fs/promises';
3
+ import path from 'node:path';
4
+
5
+ const DOCS_MARKDOWN_DIR = path.join(process.cwd(), 'docs', 'guide', 'markdown');
6
+
7
+ function isMdExampleFenceStart(line) {
8
+ return /^`{3,4}md\b/.test(line.trim());
9
+ }
10
+
11
+ function isFenceClose(line, tickCount) {
12
+ return new RegExp(`^\\s*${'`'.repeat(tickCount)}\\s*$`).test(line);
13
+ }
14
+
15
+ function findMdExampleViolations(content) {
16
+ const lines = content.split(/\r?\n/);
17
+ const violations = [];
18
+
19
+ for (let i = 0; i < lines.length; i++) {
20
+ const startLine = lines[i].trim();
21
+ if (!isMdExampleFenceStart(startLine)) continue;
22
+
23
+ const tickCount = (startLine.match(/^`+/) ?? [''])[0].length;
24
+ let closeIdx = -1;
25
+ for (let j = i + 1; j < lines.length; j++) {
26
+ if (isFenceClose(lines[j], tickCount)) {
27
+ closeIdx = j;
28
+ break;
29
+ }
30
+ }
31
+
32
+ if (closeIdx === -1) {
33
+ violations.push(`Line ${i + 1}: unclosed markdown example fence`);
34
+ continue;
35
+ }
36
+
37
+ let cursor = closeIdx + 1;
38
+ let hasOutput = false;
39
+ for (; cursor < lines.length; cursor++) {
40
+ const line = lines[cursor].trim();
41
+ if (/^Output\b/i.test(line)) {
42
+ hasOutput = true;
43
+ break;
44
+ }
45
+ if (isMdExampleFenceStart(line)) break;
46
+ }
47
+
48
+ if (!hasOutput) {
49
+ violations.push(`Line ${i + 1}: missing Output section after markdown example`);
50
+ }
51
+
52
+ i = closeIdx;
53
+ }
54
+
55
+ return violations;
56
+ }
57
+
58
+ describe('docs lint - markdown examples', () => {
59
+ it('ensures every md source example has an Output section', async () => {
60
+ const entries = await fs.readdir(DOCS_MARKDOWN_DIR, { withFileTypes: true });
61
+ const mdFiles = entries
62
+ .filter((entry) => entry.isFile() && entry.name.endsWith('.md'))
63
+ .map((entry) => path.join(DOCS_MARKDOWN_DIR, entry.name));
64
+
65
+ const failures = [];
66
+
67
+ for (const filePath of mdFiles) {
68
+ const content = await fs.readFile(filePath, 'utf8');
69
+ const violations = findMdExampleViolations(content);
70
+ for (const violation of violations) {
71
+ failures.push(`${path.relative(process.cwd(), filePath)}: ${violation}`);
72
+ }
73
+ }
74
+
75
+ expect(failures, failures.join('\n')).toEqual([]);
76
+ });
77
+ });
@@ -0,0 +1,3 @@
1
+ ### Configuration
2
+
3
+ Can be created using `.foorc.json`.
@@ -0,0 +1,9 @@
1
+ function foo() {
2
+ return 42;
3
+ }
4
+ export default foo;
5
+
6
+ // #region snippet
7
+ // region snippet line 1
8
+ ..
9
+ // #endregion snippet
@@ -0,0 +1,11 @@
1
+ ## Included section
2
+
3
+ Some **bold** text.
4
+
5
+ ::: info
6
+ Included container.
7
+ :::
8
+
9
+ ### Nested part
10
+
11
+ Nested content.
@@ -0,0 +1,5 @@
1
+ Before include.
2
+
3
+ <!--@include: ./part.md#included-section-->
4
+
5
+ After include.
@@ -0,0 +1,8 @@
1
+ const one = 1;
2
+ const two = 2;
3
+ const three = 3;
4
+
5
+ // #region demo
6
+ console.log('region line 1');
7
+ console.log('region line 2');
8
+ // #endregion demo
@@ -0,0 +1,5 @@
1
+ ## Snippet heading
2
+
3
+ ::: info
4
+ This should stay raw when imported with <<<
5
+ :::
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Test helpers — builds a unified processor matching svelte.config.js setup,
3
+ * but outputs plain HTML (no Svelte compilation).
4
+ */
5
+ import { unified } from 'unified';
6
+ import remarkParse from 'remark-parse';
7
+ import remarkRehype from 'remark-rehype';
8
+ import rehypeStringify from 'rehype-stringify';
9
+ import rehypeSlug from 'rehype-slug';
10
+ import rehypeAutolinkHeadings from 'rehype-autolink-headings';
11
+ import rehypeCodeGroup from '../rehypeCodeGroup.js';
12
+ import rehypeCodeTitle from '../rehypeCodeTitle.js';
13
+ import { remarkContainers, rehypeContainers } from '../remarkContainers.js';
14
+ import { rehypeTocPlaceholder } from '../rehypeToc.js';
15
+ import { remarkCodeMeta } from '../remarkCodeMeta.js';
16
+ import { remarkImports } from '../remarkImports.js';
17
+
18
+ const defaultImportsOptions = { sourceRoot: globalThis.process.cwd(), docsDir: 'docs' };
19
+
20
+ // Single shared processor — created once so shiki's lazy grammar loading
21
+ // only happens on first call, not per-test.
22
+ let _processor = null;
23
+
24
+ async function getProcessor(opts = {}) {
25
+ // If custom options are passed, build a one-off processor
26
+ if (opts.containers || opts.toc || opts.imports) {
27
+ return unified()
28
+ .use(remarkParse)
29
+ .use(remarkImports, opts.imports ?? defaultImportsOptions)
30
+ .use(remarkCodeMeta)
31
+ .use(remarkContainers, opts.containers ?? {})
32
+ .use(remarkRehype, { allowDangerousHtml: true })
33
+ .use(rehypeSlug)
34
+ .use(rehypeAutolinkHeadings, { behavior: 'prepend', properties: { class: 'header-anchor', ariaHidden: 'true', tabIndex: -1 }, content: { type: 'text', value: '#' } })
35
+ .use(rehypeContainers, opts.containers ?? {})
36
+ .use(rehypeCodeGroup)
37
+ .use(rehypeCodeTitle)
38
+ .use(rehypeTocPlaceholder, opts.toc ?? {})
39
+ .use(rehypeStringify, { allowDangerousHtml: true });
40
+ }
41
+ if (!_processor) {
42
+ _processor = unified()
43
+ .use(remarkParse)
44
+ .use(remarkImports, defaultImportsOptions)
45
+ .use(remarkCodeMeta)
46
+ .use(remarkContainers)
47
+ .use(remarkRehype, { allowDangerousHtml: true })
48
+ .use(rehypeSlug)
49
+ .use(rehypeAutolinkHeadings, { behavior: 'prepend', properties: { class: 'header-anchor', ariaHidden: 'true', tabIndex: -1 }, content: { type: 'text', value: '#' } })
50
+ .use(rehypeContainers)
51
+ .use(rehypeCodeGroup)
52
+ .use(rehypeCodeTitle)
53
+ .use(rehypeTocPlaceholder)
54
+ .use(rehypeStringify, { allowDangerousHtml: true });
55
+ }
56
+ return _processor;
57
+ }
58
+
59
+ export async function process(markdown, opts = {}) {
60
+ const processor = await getProcessor(opts);
61
+ const input = opts.vfile
62
+ ? { ...opts.vfile, value: markdown }
63
+ : opts.filename
64
+ ? { value: markdown, path: opts.filename }
65
+ : markdown;
66
+ return String(await processor.process(input));
67
+ }