create-nativecore 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 +10 -18
- package/bin/index.mjs +407 -489
- package/package.json +4 -3
- package/template/.env.example +28 -0
- package/template/.htmlhintrc +14 -0
- package/template/api/data/dashboard.json +11 -0
- package/template/api/data/users.json +18 -0
- package/template/api/mockApi.js +161 -0
- package/template/assets/icon.svg +13 -0
- package/template/assets/logo.svg +25 -0
- package/template/eslint.config.js +94 -0
- package/template/index.html +137 -0
- package/template/manifest.json +19 -0
- package/template/public/.well-known/security.txt +9 -0
- package/template/public/_headers +24 -0
- package/template/public/_redirects +14 -0
- package/template/public/assets/icon.svg +13 -0
- package/template/public/assets/logo.svg +25 -0
- package/template/public/manifest.json +19 -0
- package/template/public/robots.txt +13 -0
- package/template/public/sitemap.xml +27 -0
- package/template/scripts/build-for-bots.mjs +121 -0
- package/template/scripts/convert-to-ts.mjs +106 -0
- package/template/scripts/fix-encoding.mjs +38 -0
- package/template/scripts/fix-svg-paths.mjs +32 -0
- package/template/scripts/generate-cf-router.mjs +52 -0
- package/template/scripts/inject-dev-tools.mjs +41 -0
- package/template/scripts/inject-version.mjs +65 -0
- package/template/scripts/make-component.mjs +445 -0
- package/template/scripts/make-component.mjs.backup +432 -0
- package/template/scripts/make-controller.mjs +119 -0
- package/template/scripts/make-core-component.mjs +303 -0
- package/template/scripts/make-view.mjs +346 -0
- package/template/scripts/minify.mjs +71 -0
- package/template/scripts/prepare-static-assets.mjs +141 -0
- package/template/scripts/prompt-bot-build.mjs +223 -0
- package/template/scripts/remove-component.mjs +170 -0
- package/template/scripts/remove-core-component.mjs +156 -0
- package/template/scripts/remove-dev.mjs +13 -0
- package/template/scripts/remove-view.mjs +200 -0
- package/template/scripts/strip-dev-blocks.mjs +30 -0
- package/template/scripts/watch-compile.mjs +69 -0
- package/template/server.js +1066 -0
- package/template/src/app.ts +115 -0
- package/template/src/components/appRegistry.ts +8 -0
- package/template/src/components/core/app-footer.ts +27 -0
- package/template/src/components/core/app-header.ts +175 -0
- package/template/src/components/core/app-sidebar.ts +238 -0
- package/template/src/components/core/loading-spinner.ts +25 -0
- package/template/src/components/core/nc-a.ts +313 -0
- package/template/src/components/core/nc-accordion.ts +186 -0
- package/template/src/components/core/nc-alert.ts +153 -0
- package/template/src/components/core/nc-animation.ts +1150 -0
- package/template/src/components/core/nc-autocomplete.ts +271 -0
- package/template/src/components/core/nc-avatar-group.ts +113 -0
- package/template/src/components/core/nc-avatar.ts +148 -0
- package/template/src/components/core/nc-badge.ts +86 -0
- package/template/src/components/core/nc-bottom-nav.ts +214 -0
- package/template/src/components/core/nc-breadcrumb.ts +96 -0
- package/template/src/components/core/nc-button.ts +307 -0
- package/template/src/components/core/nc-card.ts +160 -0
- package/template/src/components/core/nc-checkbox.ts +282 -0
- package/template/src/components/core/nc-chip.ts +115 -0
- package/template/src/components/core/nc-code.ts +314 -0
- package/template/src/components/core/nc-collapsible.ts +154 -0
- package/template/src/components/core/nc-color-picker.ts +268 -0
- package/template/src/components/core/nc-copy-button.ts +119 -0
- package/template/src/components/core/nc-date-picker.ts +443 -0
- package/template/src/components/core/nc-div.ts +280 -0
- package/template/src/components/core/nc-divider.ts +81 -0
- package/template/src/components/core/nc-drawer.ts +230 -0
- package/template/src/components/core/nc-dropdown.ts +178 -0
- package/template/src/components/core/nc-empty-state.ts +134 -0
- package/template/src/components/core/nc-file-upload.ts +354 -0
- package/template/src/components/core/nc-form.ts +312 -0
- package/template/src/components/core/nc-image.ts +184 -0
- package/template/src/components/core/nc-input.ts +383 -0
- package/template/src/components/core/nc-kbd.ts +48 -0
- package/template/src/components/core/nc-menu-item.ts +193 -0
- package/template/src/components/core/nc-menu.ts +376 -0
- package/template/src/components/core/nc-modal.ts +238 -0
- package/template/src/components/core/nc-nav-item.ts +151 -0
- package/template/src/components/core/nc-number-input.ts +350 -0
- package/template/src/components/core/nc-otp-input.ts +235 -0
- package/template/src/components/core/nc-pagination.ts +178 -0
- package/template/src/components/core/nc-popover.ts +260 -0
- package/template/src/components/core/nc-progress-circular.ts +119 -0
- package/template/src/components/core/nc-progress.ts +134 -0
- package/template/src/components/core/nc-radio.ts +235 -0
- package/template/src/components/core/nc-rating.ts +266 -0
- package/template/src/components/core/nc-rich-text.ts +283 -0
- package/template/src/components/core/nc-scroll-top.ts +116 -0
- package/template/src/components/core/nc-select.ts +452 -0
- package/template/src/components/core/nc-skeleton.ts +107 -0
- package/template/src/components/core/nc-slider.ts +285 -0
- package/template/src/components/core/nc-snackbar.ts +230 -0
- package/template/src/components/core/nc-splash.ts +343 -0
- package/template/src/components/core/nc-stepper.ts +247 -0
- package/template/src/components/core/nc-switch.ts +281 -0
- package/template/src/components/core/nc-tab-item.ts +138 -0
- package/template/src/components/core/nc-table.ts +279 -0
- package/template/src/components/core/nc-tabs.ts +554 -0
- package/template/src/components/core/nc-tag-input.ts +279 -0
- package/template/src/components/core/nc-textarea.ts +216 -0
- package/template/src/components/core/nc-time-picker.ts +438 -0
- package/template/src/components/core/nc-timeline.ts +186 -0
- package/template/src/components/core/nc-tooltip.ts +143 -0
- package/template/src/components/frameworkRegistry.ts +68 -0
- package/template/src/components/preloadRegistry.ts +28 -0
- package/template/src/components/registry.ts +8 -0
- package/template/src/components/ui/dashboard-signal-lab.ts +284 -0
- package/template/src/constants/apiEndpoints.ts +27 -0
- package/template/src/constants/errorMessages.ts +23 -0
- package/template/src/constants/index.ts +8 -0
- package/template/src/constants/routePaths.ts +15 -0
- package/template/src/constants/storageKeys.ts +18 -0
- package/template/src/controllers/dashboard.controller.ts +200 -0
- package/template/src/controllers/home.controller.ts +21 -0
- package/template/src/controllers/index.ts +11 -0
- package/template/src/controllers/login.controller.ts +131 -0
- package/template/src/core/component.ts +354 -0
- package/template/src/core/errorHandler.ts +85 -0
- package/template/src/core/gpu-animation.ts +604 -0
- package/template/src/core/http.ts +173 -0
- package/template/src/core/lazyComponents.ts +90 -0
- package/template/src/core/router.ts +642 -0
- package/template/src/core/signals.ts +146 -0
- package/template/src/core/state.ts +248 -0
- package/template/src/dev/component-editor.ts +1363 -0
- package/template/src/dev/component-overlay.ts +278 -0
- package/template/src/dev/context-menu.ts +223 -0
- package/template/src/dev/denc-tools.ts +250 -0
- package/template/src/dev/hmr.ts +189 -0
- package/template/src/dev/nfbs.code-workspace +27 -0
- package/template/src/dev/outline-panel.ts +1247 -0
- package/template/src/middleware/auth.middleware.ts +23 -0
- package/template/src/routes/routes.ts +38 -0
- package/template/src/services/api.service.ts +394 -0
- package/template/src/services/auth.service.ts +176 -0
- package/template/src/services/index.ts +8 -0
- package/template/src/services/logger.service.ts +74 -0
- package/template/src/services/storage.service.ts +88 -0
- package/template/src/stores/appStore.ts +57 -0
- package/template/src/stores/uiStore.ts +36 -0
- package/template/src/styles/core-variables.css +219 -0
- package/template/src/styles/core.css +710 -0
- package/template/src/styles/main.css +3164 -0
- package/template/src/styles/variables.css +152 -0
- package/template/src/types/global.d.ts +47 -0
- package/template/src/utils/cacheBuster.ts +20 -0
- package/template/src/utils/dom.ts +149 -0
- package/template/src/utils/events.ts +203 -0
- package/template/src/utils/form.ts +176 -0
- package/template/src/utils/formatters.ts +169 -0
- package/template/src/utils/helpers.ts +195 -0
- package/template/src/utils/markdown.ts +307 -0
- package/template/src/utils/sidebar.ts +96 -0
- package/template/src/utils/smoothScroll.ts +85 -0
- package/template/src/utils/templates.ts +23 -0
- package/template/src/utils/validation.ts +73 -0
- package/template/src/views/protected/dashboard.html +293 -0
- package/template/src/views/public/home.html +150 -0
- package/template/src/views/public/login.html +102 -0
- package/template/tests/unit/component.test.ts +87 -0
- package/template/tests/unit/computed.test.ts +79 -0
- package/template/tests/unit/form.test.ts +68 -0
- package/template/tests/unit/formatters.test.ts +49 -0
- package/template/tests/unit/lazy-components.test.ts +59 -0
- package/template/tests/unit/markdown.test.ts +62 -0
- package/template/tests/unit/router.test.ts +112 -0
- package/template/tests/unit/signals.test.ts +54 -0
- package/template/tests/unit/validation.test.ts +50 -0
- package/template/tsconfig.build.json +21 -0
- package/template/tsconfig.json +51 -0
- package/template/vitest.config.ts +36 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal markdown renderer for the NativeCore docs page.
|
|
3
|
+
*
|
|
4
|
+
* Scope:
|
|
5
|
+
* - headings (#, ##, ###, ####)
|
|
6
|
+
* - paragraphs
|
|
7
|
+
* - unordered / ordered lists
|
|
8
|
+
* - blockquotes
|
|
9
|
+
* - fenced code blocks
|
|
10
|
+
* - horizontal rules
|
|
11
|
+
* - inline code, links, bold, italics
|
|
12
|
+
*
|
|
13
|
+
* This intentionally avoids external runtime dependencies.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export interface MarkdownHeading {
|
|
17
|
+
level: number;
|
|
18
|
+
text: string;
|
|
19
|
+
id: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface MarkdownRenderResult {
|
|
23
|
+
html: string;
|
|
24
|
+
headings: MarkdownHeading[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface MarkdownRenderOptions {
|
|
28
|
+
headingIdCounts?: Map<string, number>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface MarkdownSection {
|
|
32
|
+
id: string;
|
|
33
|
+
title: string;
|
|
34
|
+
html: string;
|
|
35
|
+
headings: MarkdownHeading[];
|
|
36
|
+
isChapter: boolean;
|
|
37
|
+
aliases: string[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function escapeHtml(input: string): string {
|
|
41
|
+
return input
|
|
42
|
+
.replace(/&/g, '&')
|
|
43
|
+
.replace(/</g, '<')
|
|
44
|
+
.replace(/>/g, '>')
|
|
45
|
+
.replace(/"/g, '"');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function slugify(text: string): string {
|
|
49
|
+
return text
|
|
50
|
+
.toLowerCase()
|
|
51
|
+
.trim()
|
|
52
|
+
.replace(/[^a-z0-9\s-]/g, '')
|
|
53
|
+
.replace(/\s+/g, '-')
|
|
54
|
+
.replace(/-+/g, '-');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function renderInline(text: string): string {
|
|
58
|
+
let out = escapeHtml(text);
|
|
59
|
+
|
|
60
|
+
out = out.replace(/`([^`]+)`/g, '<code>$1</code>');
|
|
61
|
+
out = out.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
|
|
62
|
+
out = out.replace(/\*([^*]+)\*/g, '<em>$1</em>');
|
|
63
|
+
out = out.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
|
|
64
|
+
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function renderMarkdown(markdown: string, options: MarkdownRenderOptions = {}): MarkdownRenderResult {
|
|
69
|
+
const lines = markdown.replace(/\r\n/g, '\n').split('\n');
|
|
70
|
+
const html: string[] = [];
|
|
71
|
+
const headings: MarkdownHeading[] = [];
|
|
72
|
+
const headingIdCounts = options.headingIdCounts ?? new Map<string, number>();
|
|
73
|
+
|
|
74
|
+
let paragraphBuffer: string[] = [];
|
|
75
|
+
let inCodeBlock = false;
|
|
76
|
+
let codeLang = '';
|
|
77
|
+
let codeLines: string[] = [];
|
|
78
|
+
let inUl = false;
|
|
79
|
+
let inOl = false;
|
|
80
|
+
let inBlockquote = false;
|
|
81
|
+
|
|
82
|
+
const flushParagraph = () => {
|
|
83
|
+
if (paragraphBuffer.length === 0) return;
|
|
84
|
+
const text = paragraphBuffer.join(' ').trim();
|
|
85
|
+
if (text) html.push(`<p>${renderInline(text)}</p>`);
|
|
86
|
+
paragraphBuffer = [];
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const closeLists = () => {
|
|
90
|
+
if (inUl) {
|
|
91
|
+
html.push('</ul>');
|
|
92
|
+
inUl = false;
|
|
93
|
+
}
|
|
94
|
+
if (inOl) {
|
|
95
|
+
html.push('</ol>');
|
|
96
|
+
inOl = false;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const closeBlockquote = () => {
|
|
101
|
+
if (inBlockquote) {
|
|
102
|
+
html.push('</blockquote>');
|
|
103
|
+
inBlockquote = false;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
for (const line of lines) {
|
|
108
|
+
const trimmed = line.trim();
|
|
109
|
+
|
|
110
|
+
if (trimmed.startsWith('```')) {
|
|
111
|
+
flushParagraph();
|
|
112
|
+
closeLists();
|
|
113
|
+
closeBlockquote();
|
|
114
|
+
|
|
115
|
+
if (!inCodeBlock) {
|
|
116
|
+
inCodeBlock = true;
|
|
117
|
+
codeLang = trimmed.slice(3).trim();
|
|
118
|
+
codeLines = [];
|
|
119
|
+
} else {
|
|
120
|
+
const className = codeLang ? ` class="language-${escapeHtml(codeLang)}"` : '';
|
|
121
|
+
html.push(`<pre><code${className}>${escapeHtml(codeLines.join('\n'))}</code></pre>`);
|
|
122
|
+
inCodeBlock = false;
|
|
123
|
+
codeLang = '';
|
|
124
|
+
codeLines = [];
|
|
125
|
+
}
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (inCodeBlock) {
|
|
130
|
+
codeLines.push(line);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (!trimmed) {
|
|
135
|
+
flushParagraph();
|
|
136
|
+
closeLists();
|
|
137
|
+
closeBlockquote();
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const headingMatch = /^(#{1,4})\s+(.+)$/.exec(trimmed);
|
|
142
|
+
if (headingMatch) {
|
|
143
|
+
flushParagraph();
|
|
144
|
+
closeLists();
|
|
145
|
+
closeBlockquote();
|
|
146
|
+
|
|
147
|
+
const level = headingMatch[1].length;
|
|
148
|
+
const text = headingMatch[2].trim();
|
|
149
|
+
const baseId = slugify(text) || 'section';
|
|
150
|
+
const nextCount = (headingIdCounts.get(baseId) || 0) + 1;
|
|
151
|
+
headingIdCounts.set(baseId, nextCount);
|
|
152
|
+
const id = nextCount === 1 ? baseId : `${baseId}-${nextCount}`;
|
|
153
|
+
headings.push({ level, text, id });
|
|
154
|
+
html.push(`<h${level} id="${id}">${renderInline(text)}</h${level}>`);
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (/^---+$/.test(trimmed) || /^\*\*\*+$/.test(trimmed)) {
|
|
159
|
+
flushParagraph();
|
|
160
|
+
closeLists();
|
|
161
|
+
closeBlockquote();
|
|
162
|
+
html.push('<hr>');
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const ulMatch = /^[-*]\s+(.+)$/.exec(trimmed);
|
|
167
|
+
if (ulMatch) {
|
|
168
|
+
flushParagraph();
|
|
169
|
+
closeBlockquote();
|
|
170
|
+
if (inOl) {
|
|
171
|
+
html.push('</ol>');
|
|
172
|
+
inOl = false;
|
|
173
|
+
}
|
|
174
|
+
if (!inUl) {
|
|
175
|
+
html.push('<ul>');
|
|
176
|
+
inUl = true;
|
|
177
|
+
}
|
|
178
|
+
html.push(`<li>${renderInline(ulMatch[1])}</li>`);
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const olMatch = /^\d+\.\s+(.+)$/.exec(trimmed);
|
|
183
|
+
if (olMatch) {
|
|
184
|
+
flushParagraph();
|
|
185
|
+
closeBlockquote();
|
|
186
|
+
if (inUl) {
|
|
187
|
+
html.push('</ul>');
|
|
188
|
+
inUl = false;
|
|
189
|
+
}
|
|
190
|
+
if (!inOl) {
|
|
191
|
+
html.push('<ol>');
|
|
192
|
+
inOl = true;
|
|
193
|
+
}
|
|
194
|
+
html.push(`<li>${renderInline(olMatch[1])}</li>`);
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const quoteMatch = /^>\s?(.*)$/.exec(trimmed);
|
|
199
|
+
if (quoteMatch) {
|
|
200
|
+
flushParagraph();
|
|
201
|
+
closeLists();
|
|
202
|
+
if (!inBlockquote) {
|
|
203
|
+
html.push('<blockquote>');
|
|
204
|
+
inBlockquote = true;
|
|
205
|
+
}
|
|
206
|
+
html.push(`<p>${renderInline(quoteMatch[1])}</p>`);
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
paragraphBuffer.push(trimmed);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
flushParagraph();
|
|
214
|
+
closeLists();
|
|
215
|
+
closeBlockquote();
|
|
216
|
+
|
|
217
|
+
if (inCodeBlock) {
|
|
218
|
+
const className = codeLang ? ` class="language-${escapeHtml(codeLang)}"` : '';
|
|
219
|
+
html.push(`<pre><code${className}>${escapeHtml(codeLines.join('\n'))}</code></pre>`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
html: html.join('\n'),
|
|
224
|
+
headings,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function renderMarkdownToc(headings: MarkdownHeading[]): string {
|
|
229
|
+
const items = headings
|
|
230
|
+
.filter(heading => heading.level >= 2 && heading.level <= 4)
|
|
231
|
+
.map(heading => `
|
|
232
|
+
<a class="docs-toc__link docs-toc__link--h${heading.level}" href="#${heading.id}">${escapeHtml(heading.text)}</a>
|
|
233
|
+
`)
|
|
234
|
+
.join('');
|
|
235
|
+
|
|
236
|
+
return items || '<span class="docs-toc__empty">No headings found.</span>';
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function splitMarkdownIntoSections(markdown: string): MarkdownSection[] {
|
|
240
|
+
const lines = markdown.replace(/\r\n/g, '\n').split('\n');
|
|
241
|
+
const chapterLinkAliases = new Map<string, string>();
|
|
242
|
+
const chapterStarts = lines.reduce<Array<{ index: number; title: string }>>((sections, line, index) => {
|
|
243
|
+
const headingMatch = /^##\s+(.+)$/.exec(line.trim());
|
|
244
|
+
if (headingMatch && headingMatch[1].trim().startsWith('Chapter ')) {
|
|
245
|
+
sections.push({ index, title: headingMatch[1].trim() });
|
|
246
|
+
}
|
|
247
|
+
return sections;
|
|
248
|
+
}, []);
|
|
249
|
+
|
|
250
|
+
for (const match of markdown.matchAll(/\[[^\]]+\]\(#(chapter-(\d+)[^)]+)\)/g)) {
|
|
251
|
+
const alias = match[1];
|
|
252
|
+
const chapterNumber = match[2];
|
|
253
|
+
if (!chapterLinkAliases.has(chapterNumber)) {
|
|
254
|
+
chapterLinkAliases.set(chapterNumber, alias);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (chapterStarts.length === 0) {
|
|
259
|
+
const rendered = renderMarkdown(markdown);
|
|
260
|
+
return [{
|
|
261
|
+
id: rendered.headings[0]?.id ?? 'overview',
|
|
262
|
+
title: rendered.headings.find(heading => heading.level <= 2)?.text ?? 'Overview',
|
|
263
|
+
html: rendered.html,
|
|
264
|
+
headings: rendered.headings,
|
|
265
|
+
isChapter: false,
|
|
266
|
+
aliases: [],
|
|
267
|
+
}];
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const sharedHeadingIdCounts = new Map<string, number>();
|
|
271
|
+
const sections: MarkdownSection[] = [];
|
|
272
|
+
const overviewMarkdown = lines.slice(0, chapterStarts[0].index).join('\n').trim();
|
|
273
|
+
|
|
274
|
+
if (overviewMarkdown) {
|
|
275
|
+
const renderedOverview = renderMarkdown(overviewMarkdown, { headingIdCounts: sharedHeadingIdCounts });
|
|
276
|
+
sections.push({
|
|
277
|
+
id: 'overview',
|
|
278
|
+
title: 'Overview',
|
|
279
|
+
html: renderedOverview.html,
|
|
280
|
+
headings: renderedOverview.headings,
|
|
281
|
+
isChapter: false,
|
|
282
|
+
aliases: [],
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
chapterStarts.forEach((chapterStart, index) => {
|
|
287
|
+
const chapterEnd = chapterStarts[index + 1]?.index ?? lines.length;
|
|
288
|
+
const chapterMarkdown = lines.slice(chapterStart.index, chapterEnd).join('\n').trim();
|
|
289
|
+
const renderedChapter = renderMarkdown(chapterMarkdown, { headingIdCounts: sharedHeadingIdCounts });
|
|
290
|
+
const chapterHeading = renderedChapter.headings.find(heading => heading.level === 2) ?? renderedChapter.headings[0];
|
|
291
|
+
const chapterNumber = /^Chapter\s+(\d+)/i.exec(chapterHeading?.text ?? chapterStart.title)?.[1];
|
|
292
|
+
const chapterAlias = chapterNumber ? chapterLinkAliases.get(chapterNumber) : undefined;
|
|
293
|
+
const sectionId = chapterAlias ?? chapterHeading?.id ?? slugify(chapterStart.title);
|
|
294
|
+
const aliases = chapterHeading?.id && chapterHeading.id !== sectionId ? [chapterHeading.id] : [];
|
|
295
|
+
|
|
296
|
+
sections.push({
|
|
297
|
+
id: sectionId,
|
|
298
|
+
title: chapterHeading?.text ?? chapterStart.title,
|
|
299
|
+
html: renderedChapter.html,
|
|
300
|
+
headings: renderedChapter.headings,
|
|
301
|
+
isChapter: true,
|
|
302
|
+
aliases,
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
return sections;
|
|
307
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sidebar Initialization and Management
|
|
3
|
+
*/
|
|
4
|
+
import auth from '../services/auth.service.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Initialize sidebar functionality
|
|
8
|
+
*/
|
|
9
|
+
export function initSidebar() {
|
|
10
|
+
const sidebar = document.getElementById('appSidebar');
|
|
11
|
+
const appLayout = document.querySelector('.app-layout');
|
|
12
|
+
|
|
13
|
+
// Listen for sidebar toggle events from nc-sidebar component
|
|
14
|
+
sidebar?.addEventListener('toggle', ((e: CustomEvent) => {
|
|
15
|
+
const isCollapsed = e.detail.collapsed;
|
|
16
|
+
|
|
17
|
+
if (appLayout) {
|
|
18
|
+
if (isCollapsed) {
|
|
19
|
+
appLayout.classList.add('sidebar-collapsed');
|
|
20
|
+
} else {
|
|
21
|
+
appLayout.classList.remove('sidebar-collapsed');
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Save state
|
|
26
|
+
localStorage.setItem('sidebar-collapsed', isCollapsed.toString());
|
|
27
|
+
}) as EventListener);
|
|
28
|
+
|
|
29
|
+
// Restore saved state on load
|
|
30
|
+
const savedCollapsed = localStorage.getItem('sidebar-collapsed') === 'true';
|
|
31
|
+
if (savedCollapsed && sidebar) {
|
|
32
|
+
sidebar.setAttribute('collapsed', '');
|
|
33
|
+
appLayout?.classList.add('sidebar-collapsed');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Update sidebar visibility based on auth state
|
|
37
|
+
function updateSidebar() {
|
|
38
|
+
const isAuthenticated = auth.isAuthenticated();
|
|
39
|
+
|
|
40
|
+
// Toggle body class for sidebar visibility (no flash)
|
|
41
|
+
if (isAuthenticated) {
|
|
42
|
+
document.body.classList.add('sidebar-enabled');
|
|
43
|
+
if (appLayout) appLayout.classList.remove('no-sidebar');
|
|
44
|
+
} else {
|
|
45
|
+
document.body.classList.remove('sidebar-enabled');
|
|
46
|
+
if (appLayout) appLayout.classList.add('no-sidebar');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Toggle menu items based on auth state
|
|
50
|
+
const homeLink = document.querySelector('.sidebar-item.home-link') as HTMLElement;
|
|
51
|
+
const aboutLink = document.querySelector('.sidebar-item.about-link') as HTMLElement;
|
|
52
|
+
const dashboardLink = document.querySelector('.sidebar-item.dashboard-link') as HTMLElement;
|
|
53
|
+
const componentsLink = document.querySelector('.sidebar-item.components-link') as HTMLElement;
|
|
54
|
+
const underConstructionLink = document.querySelector('.sidebar-item.under-construction-link') as HTMLElement;
|
|
55
|
+
const logoutLink = document.querySelector('.sidebar-item.logout-link') as HTMLElement;
|
|
56
|
+
|
|
57
|
+
if (homeLink) homeLink.style.display = isAuthenticated ? 'none' : 'flex';
|
|
58
|
+
if (aboutLink) aboutLink.style.display = isAuthenticated ? 'none' : 'flex';
|
|
59
|
+
if (dashboardLink) dashboardLink.style.display = isAuthenticated ? 'flex' : 'none';
|
|
60
|
+
if (componentsLink) componentsLink.style.display = isAuthenticated ? 'flex' : 'none';
|
|
61
|
+
if (underConstructionLink) underConstructionLink.style.display = isAuthenticated ? 'flex' : 'none';
|
|
62
|
+
if (logoutLink) logoutLink.style.display = isAuthenticated ? 'flex' : 'none';
|
|
63
|
+
|
|
64
|
+
// Update active link
|
|
65
|
+
updateActiveSidebarLink();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Handle sidebar logout button
|
|
69
|
+
const sidebarLogoutBtn = document.getElementById('sidebarLogoutBtn');
|
|
70
|
+
if (sidebarLogoutBtn) {
|
|
71
|
+
sidebarLogoutBtn.addEventListener('click', () => {
|
|
72
|
+
auth.logout();
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Update active link on page load
|
|
77
|
+
function updateActiveSidebarLink() {
|
|
78
|
+
const currentPath = window.location.pathname;
|
|
79
|
+
const sidebarItems = document.querySelectorAll('.sidebar-item');
|
|
80
|
+
|
|
81
|
+
sidebarItems.forEach(item => {
|
|
82
|
+
item.classList.remove('active');
|
|
83
|
+
const href = item.getAttribute('href');
|
|
84
|
+
if (href === currentPath || (currentPath === '/' && href === '/')) {
|
|
85
|
+
item.classList.add('active');
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Listen for auth changes
|
|
91
|
+
window.addEventListener('auth-change', updateSidebar);
|
|
92
|
+
window.addEventListener('pageloaded', updateActiveSidebarLink);
|
|
93
|
+
|
|
94
|
+
// Initial update
|
|
95
|
+
updateSidebar();
|
|
96
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smooth Scrolling Enhancement
|
|
3
|
+
* Makes mouse wheel scrolling smoother by intercepting wheel events
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export function initSmoothScroll() {
|
|
7
|
+
// Only apply on desktop (not mobile)
|
|
8
|
+
if ('ontouchstart' in window) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let isScrolling = false;
|
|
13
|
+
let targetScrollY = window.scrollY;
|
|
14
|
+
let currentScrollY = window.scrollY;
|
|
15
|
+
const smoothness = 0.15; // Lower = smoother but slower
|
|
16
|
+
|
|
17
|
+
function smoothScrollStep() {
|
|
18
|
+
if (Math.abs(targetScrollY - currentScrollY) < 0.5) {
|
|
19
|
+
currentScrollY = targetScrollY;
|
|
20
|
+
isScrolling = false;
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
currentScrollY += (targetScrollY - currentScrollY) * smoothness;
|
|
25
|
+
window.scrollTo(0, currentScrollY);
|
|
26
|
+
requestAnimationFrame(smoothScrollStep);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
window.addEventListener('wheel', (e: WheelEvent) => {
|
|
30
|
+
e.preventDefault();
|
|
31
|
+
|
|
32
|
+
// Calculate new target scroll position
|
|
33
|
+
targetScrollY += e.deltaY;
|
|
34
|
+
|
|
35
|
+
// Clamp to valid range
|
|
36
|
+
const maxScroll = document.documentElement.scrollHeight - window.innerHeight;
|
|
37
|
+
targetScrollY = Math.max(0, Math.min(targetScrollY, maxScroll));
|
|
38
|
+
|
|
39
|
+
// Start smooth scrolling animation if not already running
|
|
40
|
+
if (!isScrolling) {
|
|
41
|
+
isScrolling = true;
|
|
42
|
+
requestAnimationFrame(smoothScrollStep);
|
|
43
|
+
}
|
|
44
|
+
}, { passive: false });
|
|
45
|
+
|
|
46
|
+
// Handle keyboard scrolling
|
|
47
|
+
window.addEventListener('keydown', (e: KeyboardEvent) => {
|
|
48
|
+
const scrollAmount = 100;
|
|
49
|
+
const pageScrollAmount = window.innerHeight * 0.8;
|
|
50
|
+
|
|
51
|
+
switch (e.key) {
|
|
52
|
+
case 'ArrowDown':
|
|
53
|
+
e.preventDefault();
|
|
54
|
+
targetScrollY = Math.min(targetScrollY + scrollAmount, document.documentElement.scrollHeight - window.innerHeight);
|
|
55
|
+
break;
|
|
56
|
+
case 'ArrowUp':
|
|
57
|
+
e.preventDefault();
|
|
58
|
+
targetScrollY = Math.max(targetScrollY - scrollAmount, 0);
|
|
59
|
+
break;
|
|
60
|
+
case 'PageDown':
|
|
61
|
+
e.preventDefault();
|
|
62
|
+
targetScrollY = Math.min(targetScrollY + pageScrollAmount, document.documentElement.scrollHeight - window.innerHeight);
|
|
63
|
+
break;
|
|
64
|
+
case 'PageUp':
|
|
65
|
+
e.preventDefault();
|
|
66
|
+
targetScrollY = Math.max(targetScrollY - pageScrollAmount, 0);
|
|
67
|
+
break;
|
|
68
|
+
case 'Home':
|
|
69
|
+
e.preventDefault();
|
|
70
|
+
targetScrollY = 0;
|
|
71
|
+
break;
|
|
72
|
+
case 'End':
|
|
73
|
+
e.preventDefault();
|
|
74
|
+
targetScrollY = document.documentElement.scrollHeight - window.innerHeight;
|
|
75
|
+
break;
|
|
76
|
+
default:
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!isScrolling) {
|
|
81
|
+
isScrolling = true;
|
|
82
|
+
requestAnimationFrame(smoothScrollStep);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template Literal Helpers
|
|
3
|
+
*
|
|
4
|
+
* These are no-op functions that exist purely for syntax highlighting.
|
|
5
|
+
* They have ZERO runtime overhead - just pass through the string unchanged.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* HTML template literal tag for syntax highlighting
|
|
10
|
+
* Usage: html`<div>content</div>`
|
|
11
|
+
*
|
|
12
|
+
* This does nothing at runtime - it's only for VS Code highlighting.
|
|
13
|
+
* Install "lit-html" extension for full HTML/CSS syntax highlighting.
|
|
14
|
+
*/
|
|
15
|
+
export const html = (strings: TemplateStringsArray, ...values: any[]): string =>
|
|
16
|
+
String.raw({ raw: strings }, ...values);
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* CSS template literal tag for syntax highlighting
|
|
20
|
+
* Usage: css`.class { color: red; }`
|
|
21
|
+
*/
|
|
22
|
+
export const css = (strings: TemplateStringsArray, ...values: any[]): string =>
|
|
23
|
+
String.raw({ raw: strings }, ...values);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation Utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export function isValidEmail(email: string): boolean {
|
|
6
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
7
|
+
return emailRegex.test(email);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function isRequired(value: any): boolean {
|
|
11
|
+
if (value === null || value === undefined) return false;
|
|
12
|
+
if (typeof value === 'string') return value.trim().length > 0;
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function minLength(min: number): (value: any) => boolean {
|
|
17
|
+
return (value: any) => {
|
|
18
|
+
if (!value) return false;
|
|
19
|
+
return String(value).length >= min;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function maxLength(max: number): (value: any) => boolean {
|
|
24
|
+
return (value: any) => {
|
|
25
|
+
if (!value) return true;
|
|
26
|
+
return String(value).length <= max;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function matchesPattern(pattern: RegExp): (value: any) => boolean {
|
|
31
|
+
return (value: any) => {
|
|
32
|
+
if (!value) return false;
|
|
33
|
+
return pattern.test(String(value));
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function isNumber(value: any): boolean {
|
|
38
|
+
return !isNaN(parseFloat(value)) && isFinite(value);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function isInteger(value: any): boolean {
|
|
42
|
+
return Number.isInteger(Number(value));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function isValidURL(url: string): boolean {
|
|
46
|
+
try {
|
|
47
|
+
new URL(url);
|
|
48
|
+
return true;
|
|
49
|
+
} catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function isValidDate(date: any): boolean {
|
|
55
|
+
return date instanceof Date && !isNaN(date.getTime());
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
type Validator = (value: any) => boolean;
|
|
59
|
+
|
|
60
|
+
export function validateForm(values: Record<string, any>, rules: Record<string, Validator[]>): Record<string, string> {
|
|
61
|
+
const errors: Record<string, string> = {};
|
|
62
|
+
|
|
63
|
+
for (const [field, validators] of Object.entries(rules)) {
|
|
64
|
+
for (const validator of validators) {
|
|
65
|
+
if (!validator(values[field])) {
|
|
66
|
+
errors[field] = `Validation failed for ${field}`;
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return errors;
|
|
73
|
+
}
|