czon 0.3.4 → 0.3.6

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.
@@ -29,7 +29,7 @@ const ContentPage = props => {
29
29
  react_1.default.createElement("script", { src: "https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4", defer: true }),
30
30
  react_1.default.createElement("style", null, style_1.style)),
31
31
  react_1.default.createElement("body", null,
32
- react_1.default.createElement(PageLayout_1.PageLayout, { header: react_1.default.createElement(CZONHeader_1.CZONHeader, { ctx: props.ctx }), navigator: react_1.default.createElement("nav", { className: "sidebar" },
32
+ react_1.default.createElement(PageLayout_1.PageLayout, { header: react_1.default.createElement(CZONHeader_1.CZONHeader, { ctx: props.ctx, lang: props.lang, file: props.file }), navigator: react_1.default.createElement("nav", { className: "sidebar" },
33
33
  react_1.default.createElement(Navigator_1.Navigator, { ctx: props.ctx, file: props.file, lang: props.lang })), main: react_1.default.createElement("main", { className: "content" },
34
34
  react_1.default.createElement(ContentMeta_1.ContentMeta, { ctx: props.ctx, file: props.file, lang: props.lang }),
35
35
  react_1.default.createElement("div", { className: "content-body" },
@@ -106,7 +106,44 @@ const ContentPage = props => {
106
106
  }, 300);
107
107
  }
108
108
 
109
- `))));
109
+ `),
110
+ react_1.default.createElement("script", { id: "embla-lib", src: "https://unpkg.com/embla-carousel/embla-carousel.umd.js", defer: true }),
111
+ react_1.default.createElement("script", { id: "embla-autoplay-lib", src: "https://unpkg.com/embla-carousel-autoplay/embla-carousel-autoplay.umd.js", defer: true }),
112
+ react_1.default.createElement("script", null, `
113
+ Promise.all([
114
+ new Promise(resolve => {document.getElementById('embla-lib').addEventListener('load', resolve)}),
115
+ new Promise(resolve => {document.getElementById('embla-autoplay-lib').addEventListener('load', resolve)}),
116
+ ]).then(() => {
117
+ console.log('Embla Carousel and Autoplay loaded');
118
+ renderEmblaCarousels();
119
+ });
120
+
121
+ function renderEmblaCarousels() {
122
+ // Detect image groups, make them carousels automatically
123
+ Map.groupBy(document.querySelectorAll('img'), x => x.parentNode).entries().forEach(([container, images]) => {
124
+ const outer = document.createElement('div');
125
+ outer.classList.add('embla');
126
+ container.appendChild(outer);
127
+
128
+ const inner = document.createElement('div');
129
+ inner.classList.add('embla__container');
130
+ outer.appendChild(inner);
131
+
132
+ images.forEach(img => {
133
+ container.removeChild(img);
134
+
135
+ const slide = document.createElement('div');
136
+ slide.classList.add('embla__slide');
137
+
138
+ slide.appendChild(img);
139
+ inner.appendChild(slide);
140
+ });
141
+
142
+ EmblaCarousel(outer, { loop: true }, [EmblaCarouselAutoplay()]);
143
+ });
144
+
145
+ }
146
+ `))));
110
147
  };
111
148
  exports.ContentPage = ContentPage;
112
149
  //# sourceMappingURL=ContentPage.js.map
@@ -30,7 +30,7 @@ const IndexPage = props => {
30
30
  react_1.default.createElement("meta", { name: "description", content: `Index page for language ${props.lang}` }),
31
31
  react_1.default.createElement("style", null, style_1.style)),
32
32
  react_1.default.createElement("body", null,
33
- react_1.default.createElement(PageLayout_1.PageLayout, { header: react_1.default.createElement(CZONHeader_1.CZONHeader, { ctx: props.ctx }), navigator: undefined, main: react_1.default.createElement("div", { className: "p-6 max-w-3xl mx-auto" },
33
+ react_1.default.createElement(PageLayout_1.PageLayout, { header: react_1.default.createElement(CZONHeader_1.CZONHeader, { ctx: props.ctx, lang: props.lang }), navigator: undefined, main: react_1.default.createElement("div", { className: "p-6 max-w-3xl mx-auto" },
34
34
  react_1.default.createElement("div", null,
35
35
  react_1.default.createElement("span", { className: "font-bold" }),
36
36
  react_1.default.createElement("div", { className: "mb-6 gap-6 flex flex-wrap" }, allCategories.map(category => {
@@ -5,10 +5,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.CZONHeader = void 0;
7
7
  const react_1 = __importDefault(require("react"));
8
+ const LanguageSwitch_1 = require("./LanguageSwitch");
8
9
  const CZONHeader = props => {
9
- return (react_1.default.createElement("header", { className: "czon-header py-4 border-b" },
10
- react_1.default.createElement("h1", { className: "text-2xl font-bold px-6" },
11
- react_1.default.createElement("a", { href: "index.html" }, "CZON"))));
10
+ return (react_1.default.createElement("header", { className: "czon-header py-4 border-b flex justify-between items-center px-6" },
11
+ react_1.default.createElement("h1", { className: "text-2xl font-bold" },
12
+ react_1.default.createElement("a", { href: "index.html" }, "CZON")),
13
+ props.lang && (react_1.default.createElement(LanguageSwitch_1.LanguageSwitch, { ctx: props.ctx, lang: props.lang, file: props.file }))));
12
14
  };
13
15
  exports.CZONHeader = CZONHeader;
14
16
  //# sourceMappingURL=CZONHeader.js.map
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.LanguageSwitch = void 0;
7
+ const node_path_1 = require("node:path");
8
+ const react_1 = __importDefault(require("react"));
9
+ const languages_1 = require("../../languages");
10
+ const LanguageSwitch = props => {
11
+ // 获取当前语言名称
12
+ const currentLangName = languages_1.LANGUAGE_NAMES[props.lang] || props.lang;
13
+ // 获取所有支持的语言
14
+ const languages = props.ctx.site.options.langs || [];
15
+ // 生成语言链接(纯静态,无状态)
16
+ const getLanguageLink = (lang) => {
17
+ const target = props.file ? `${props.file.metadata.slug}.html` : 'index.html';
18
+ return (0, node_path_1.join)('..', lang, target);
19
+ };
20
+ return (react_1.default.createElement("div", { className: "language-switch-container relative inline-block" },
21
+ react_1.default.createElement("button", { type: "button", className: "language-switch-trigger px-4 py-2 border border-gray-300 rounded-md flex items-center gap-2 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors", "aria-label": `Language switcher. Current language: ${currentLangName}`, "aria-haspopup": "true", "aria-expanded": "false", "aria-controls": "language-dropdown" },
22
+ react_1.default.createElement("span", { className: "text-sm font-medium" }, currentLangName),
23
+ react_1.default.createElement("svg", { className: "language-switch-icon w-4 h-4 transition-transform", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true" },
24
+ react_1.default.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 9l-7 7-7-7" }))),
25
+ react_1.default.createElement("div", { id: "language-dropdown", className: "language-switch-dropdown absolute top-full mt-1 w-96 md:w-2xl lg:w-3xl max-h-96 overflow-y-auto bg-white border border-gray-300 rounded-md shadow-lg z-50", role: "menu", "aria-label": "Available languages" },
26
+ react_1.default.createElement("div", { className: "p-4" },
27
+ react_1.default.createElement("div", { className: "language-switch-grid grid grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-2" }, languages.map(lang => {
28
+ const langName = languages_1.LANGUAGE_NAMES[lang] || lang;
29
+ const isActive = lang === props.lang;
30
+ return (react_1.default.createElement("a", { key: lang, href: getLanguageLink(lang), className: `
31
+ language-switch-option block px-3 py-2 rounded text-sm text-center
32
+ ${isActive
33
+ ? 'bg-blue-100 text-blue-700 font-semibold'
34
+ : 'text-gray-700 hover:bg-gray-100'}
35
+ transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500
36
+ `, role: "menuitem", "aria-current": isActive ? 'page' : undefined, lang: lang }, langName));
37
+ })),
38
+ react_1.default.createElement("div", { className: "language-switch-info mt-3 pt-3 border-t border-gray-200" },
39
+ react_1.default.createElement("p", { className: "language-switch-count text-xs text-gray-500 text-center" },
40
+ languages.length,
41
+ " languages available"))))));
42
+ };
43
+ exports.LanguageSwitch = LanguageSwitch;
44
+ //# sourceMappingURL=LanguageSwitch.js.map
package/dist/ssg/style.js CHANGED
@@ -300,5 +300,150 @@ exports.style = `
300
300
  display: block;
301
301
  margin: 0 auto;
302
302
  }
303
+
304
+ /* LanguageSwitch dropdown styles - Pure CSS version */
305
+ .language-switch-container {
306
+ position: relative;
307
+ display: inline-block;
308
+ }
309
+
310
+ .language-switch-trigger {
311
+ transition: all 0.2s ease;
312
+ }
313
+
314
+ .language-switch-trigger:hover {
315
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
316
+ }
317
+
318
+ .language-switch-trigger:focus {
319
+ outline: 2px solid #3b82f6;
320
+ outline-offset: 2px;
321
+ }
322
+
323
+ .language-switch-icon {
324
+ transition: transform 0.2s ease;
325
+ }
326
+
327
+ .language-switch-option {
328
+ transition: all 0.15s ease;
329
+ }
330
+
331
+ .language-switch-option:hover {
332
+ transform: translateY(-1px);
333
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
334
+ }
335
+
336
+ .language-switch-option:focus {
337
+ outline: 2px solid #3b82f6;
338
+ outline-offset: 2px;
339
+ }
340
+
341
+ /* Pure CSS dropdown using :focus-within */
342
+ .language-switch-dropdown {
343
+ opacity: 0;
344
+ visibility: hidden;
345
+ transform: translateY(-10px);
346
+ transition: opacity 0.2s ease, visibility 0.2s ease, transform 0.2s ease;
347
+ }
348
+
349
+ .language-switch-container:focus-within .language-switch-dropdown {
350
+ opacity: 1;
351
+ visibility: visible;
352
+ transform: translateY(0);
353
+ }
354
+
355
+ .language-switch-container:focus-within .language-switch-icon {
356
+ transform: rotate(180deg);
357
+ }
358
+
359
+ /* Ensure dropdown stays visible when focusing inside it */
360
+ .language-switch-dropdown:focus-within {
361
+ opacity: 1;
362
+ visibility: visible;
363
+ transform: translateY(0);
364
+ }
365
+
366
+ .language-switch-dropdown {
367
+ max-width: 80vw;
368
+ inset-inline-start: auto;
369
+ inset-inline-end: 0;
370
+ margin-inline-start: 0;
371
+ margin-inline-end: 0;
372
+ }
373
+
374
+
375
+ /* Mobile responsiveness for language dropdown */
376
+ @media (max-width: 768px) {
377
+ .language-switch-trigger {
378
+ padding: 0.5rem 0.75rem;
379
+ font-size: 0.875rem;
380
+ }
381
+
382
+
383
+ .language-switch-option {
384
+ padding: 0.5rem 0.75rem;
385
+ font-size: 0.8125rem;
386
+ }
387
+
388
+ .language-switch-grid {
389
+ grid-template-columns: repeat(2, 1fr);
390
+ }
391
+ }
392
+
393
+ @media (max-width: 480px) {
394
+ .language-switch-grid {
395
+ grid-template-columns: 1fr;
396
+ }
397
+ }
398
+
399
+ /* Scrollbar styling for dropdown */
400
+ .language-switch-dropdown::-webkit-scrollbar {
401
+ width: 8px;
402
+ }
403
+
404
+ .language-switch-dropdown::-webkit-scrollbar-track {
405
+ background: #f1f1f1;
406
+ border-radius: 4px;
407
+ }
408
+
409
+ .language-switch-dropdown::-webkit-scrollbar-thumb {
410
+ background: #c1c1c1;
411
+ border-radius: 4px;
412
+ }
413
+
414
+ .language-switch-dropdown::-webkit-scrollbar-thumb:hover {
415
+ background: #a8a8a8;
416
+ }
417
+
418
+ /* Browser compatibility fallback for :focus-within */
419
+ @supports not selector(:focus-within) {
420
+ .language-switch-dropdown {
421
+ display: none;
422
+ }
423
+
424
+ .language-switch-container:hover .language-switch-dropdown,
425
+ .language-switch-container:focus .language-switch-dropdown {
426
+ display: block;
427
+ opacity: 1;
428
+ visibility: visible;
429
+ transform: translateY(0);
430
+ }
431
+
432
+ .language-switch-container:hover .language-switch-icon,
433
+ .language-switch-container:focus .language-switch-icon {
434
+ transform: rotate(180deg);
435
+ }
436
+ }
437
+
438
+ .embla {
439
+ overflow: hidden;
440
+ }
441
+ .embla__container {
442
+ display: flex;
443
+ }
444
+ .embla__slide {
445
+ flex: 0 0 100%;
446
+ min-width: 0;
447
+ }
303
448
  `;
304
449
  //# sourceMappingURL=style.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "czon",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
4
4
  "description": "CZone - AI enhanced Markdown content engine",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",