create-evikit 2.2.2 → 2.3.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.
@@ -18,33 +18,32 @@ const target = resolve(process.cwd(), name);
18
18
  if (existsSync(target)) {
19
19
  mkdirSync(target, { recursive: true });
20
20
  }
21
- for (const file of [
22
- ...template,
23
- ".gitignore",
24
- "eslint.config.js",
25
- "tsconfig.json",
26
- ]) {
21
+ for (const file of [...template, "eslint.config.js", "tsconfig.json"]) {
27
22
  const path = join(target, file);
28
23
  mkdirSync(dirname(path), { recursive: true });
29
24
  writeFileSync(
30
25
  path,
31
- readFileSync(join(ownRoot, file), "utf8").replace(
32
- /<link\s+href="\/node_modules\/[^"]+.css"\s+media="[^"]+"\s+rel="stylesheet"\s+\/>\s+/g,
33
- "",
34
- ),
26
+ readFileSync(join(ownRoot, file), "utf8")
27
+ .replace(
28
+ /<link\s+href="\/node_modules\/[^"]+.css"\s+media="[^"]+"\s+rel="stylesheet"\s+\/>\s+/g,
29
+ "",
30
+ )
31
+ .replace(', "uk"', ""),
35
32
  );
36
33
  }
37
34
 
38
35
  // ---
39
36
 
40
37
  writeFileSync(
41
- join(target, "src/lib/index.js"),
38
+ join(target, ".gitignore"),
42
39
  `
43
- import acceptLanguage from "accept-language";
44
-
45
- export function acceptLanguages() {
46
- acceptLanguage.languages(["en"]);
47
- }
40
+ node_modules
41
+ /dist
42
+ *~
43
+ *swp
44
+ *.mo
45
+ .env
46
+ package-lock.json
48
47
  `.trimStart(),
49
48
  );
50
49
 
package/index.css CHANGED
@@ -5,8 +5,19 @@
5
5
  text-wrap: balance;
6
6
  }
7
7
 
8
+ .badge-info.badge-soft,
8
9
  p a:not([class]) {
9
10
  color: #28f;
11
+ }
12
+
13
+ @media (prefers-color-scheme: dark) {
14
+ .badge-info.badge-soft,
15
+ p a:not([class]) {
16
+ color: #61afef;
17
+ }
18
+ }
19
+
20
+ p a:not([class]) {
10
21
  text-decoration: underline;
11
22
  }
12
23
 
@@ -32,7 +43,7 @@ details {
32
43
  nav a {
33
44
  background-color: currentColor;
34
45
  color: #fff;
35
- border-radius: 1em;
46
+ border-radius: 0.75rem;
36
47
  box-shadow: 0 0 5px rgba(0, 0, 0, 0.05);
37
48
  display: inline-block;
38
49
  margin-bottom: 1em;
@@ -53,3 +64,13 @@ nav a[href*="issues"] {
53
64
  background-color: #7d8f;
54
65
  color: #fff;
55
66
  }
67
+
68
+ @media (prefers-color-scheme: dark) {
69
+ nav a[href*="documentation"] {
70
+ background-color: #c678dd;
71
+ }
72
+
73
+ nav a[href*="issues"] {
74
+ background-color: #61afef;
75
+ }
76
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-evikit",
3
- "version": "2.2.2",
3
+ "version": "2.3.0",
4
4
  "type": "module",
5
5
  "bin": "bin/create-evikit.js",
6
6
  "scripts": {
@@ -37,7 +37,7 @@
37
37
  "@speed-highlight/core": "^1.2.17",
38
38
  "accept-language": "^3.0.20",
39
39
  "classnames": "^2.5.1",
40
- "evikit": "^2.2.2",
40
+ "evikit": "^2.3.1",
41
41
  "express": "^5.2.1",
42
42
  "file-type": "^22.0.1",
43
43
  "hoofd": "^1.7.3",
@@ -1,84 +1,89 @@
1
- # SOME DESCRIPTIVE TITLE.
2
- # Copyright (C) YEAR Denys Nykula <nykula@ukr.net>
3
- # This file is distributed under the same license as the create-evikit package.
4
- # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5
- #
6
- #, fuzzy
7
- msgid ""
8
- msgstr ""
9
- "Project-Id-Version: create-evikit\n"
10
- "Report-Msgid-Bugs-To: \n"
11
- "POT-Creation-Date: 2026-07-07 23:06+0300\n"
12
- "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13
- "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
- "Language-Team: LANGUAGE <LL@li.org>\n"
15
- "Language: \n"
16
- "MIME-Version: 1.0\n"
17
- "Content-Type: text/plain; charset=UTF-8\n"
18
- "Content-Transfer-Encoding: 8bit\n"
19
- "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
20
-
21
- msgid "EviKit: Preact SSR framework for small web apps"
22
- msgstr ""
23
-
24
- msgid "Preact SSR framework."
25
- msgstr ""
26
-
27
- msgid "For small web apps hosted on a <b>VPS</b> or in local network."
28
- msgstr ""
29
-
30
- msgid ""
31
- "Standard modern <b>JavaScript</b>, no JSX or TS. <b>Forms</b> accessible "
32
- "when JS doesn't load. Automatic <b>Swagger</b> specification. First-class "
33
- "API input/output <b>validation</b>. Built-in <b>node:sqlite ORM</b>. Support "
34
- "for UI <b>translation</b> through .po files."
35
- msgstr ""
36
-
37
- msgid "Documentation"
38
- msgstr ""
39
-
40
- msgid "Report <b>issue</b>"
41
- msgstr ""
42
-
43
- msgid ""
44
- "See <a href=\"{todo}\">live demo</a> of the example code from below. Todos "
45
- "are deleted every 10 minutes."
46
- msgstr ""
47
-
48
- msgid "Quick start"
49
- msgstr ""
50
-
51
- msgid ""
52
- "There’s intentionally <b>not much API</b>. Documentation entirely fits in a "
53
- "long but straightforward <b>README</b>. Like the Django tutorial, it guides "
54
- "you through <b><a href=\"{documentation}\">writing an application</a></b> "
55
- "using all the primary features of the EviKit framework."
56
- msgstr ""
57
-
58
- msgid ""
59
- "<a href=\"{evikit}\"><b>Source code</b> at Codeberg</a> under <b>MIT</b> "
60
- "license"
61
- msgstr ""
62
-
63
- msgid "Discuss on <b>Fediverse</b>"
64
- msgstr ""
65
-
66
- msgid "%1 todo"
67
- msgid_plural "%1 todos"
68
- msgstr[0] ""
69
- msgstr[1] ""
70
-
71
- msgid "Todos"
72
- msgstr ""
73
-
74
- msgid "Text"
75
- msgstr ""
76
-
77
- msgid "Create todo"
78
- msgstr ""
79
-
80
- msgid "Back to plan"
81
- msgstr ""
82
-
83
- msgid "Done"
84
- msgstr ""
1
+ # SOME DESCRIPTIVE TITLE.
2
+ # Copyright (C) YEAR Denys Nykula <nykula@ukr.net>
3
+ # This file is distributed under the same license as the create-evikit package.
4
+ # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5
+ #
6
+ #, fuzzy
7
+ msgid ""
8
+ msgstr ""
9
+ "Project-Id-Version: create-evikit\n"
10
+ "Report-Msgid-Bugs-To: \n"
11
+ "POT-Creation-Date: 2026-07-08 01:57+0300\n"
12
+ "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13
+ "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
+ "Language-Team: LANGUAGE <LL@li.org>\n"
15
+ "Language: \n"
16
+ "MIME-Version: 1.0\n"
17
+ "Content-Type: text/plain; charset=UTF-8\n"
18
+ "Content-Transfer-Encoding: 8bit\n"
19
+ "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
20
+
21
+ msgid "EviKit: Preact SSR framework for small web apps"
22
+ msgstr ""
23
+
24
+ msgid "Preact SSR framework."
25
+ msgstr ""
26
+
27
+ msgid "For small web apps hosted on a <b>VPS</b> or in local network."
28
+ msgstr ""
29
+
30
+ msgid ""
31
+ "Standard modern <b>JavaScript</b>, no JSX or TS. <b>Forms</b> accessible "
32
+ "when JS doesn't load. Automatic <b>Swagger</b> specification. First-class "
33
+ "API input/output <b>validation</b>. Built-in <b>node:sqlite ORM</b>. Support "
34
+ "for UI <b>translation</b> through .po files."
35
+ msgstr ""
36
+
37
+ msgid "Documentation"
38
+ msgstr ""
39
+
40
+ msgid "Report <b>issue</b>"
41
+ msgstr ""
42
+
43
+ msgid ""
44
+ "See <a href=\"{todo}\">live demo</a> of the example code from below. Todos "
45
+ "are deleted every 10 minutes."
46
+ msgstr ""
47
+
48
+ msgid ""
49
+ "<a href=\"{openapi}\" target=\"_top\">OpenAPI demo</a> (Swagger) is "
50
+ "generated from API route schema."
51
+ msgstr ""
52
+
53
+ msgid "Quick start"
54
+ msgstr ""
55
+
56
+ msgid ""
57
+ "There’s intentionally <b>not much API</b>. Documentation entirely fits in a "
58
+ "long but straightforward <b>README</b>. Like the Django tutorial, it guides "
59
+ "you through <b><a href=\"{documentation}\">writing an application</a></b> "
60
+ "using all the primary features of the EviKit framework."
61
+ msgstr ""
62
+
63
+ msgid ""
64
+ "<a href=\"{evikit}\"><b>Source code</b> at Codeberg</a> under <b>MIT</b> "
65
+ "license"
66
+ msgstr ""
67
+
68
+ msgid "Discuss on <b>Fediverse</b>"
69
+ msgstr ""
70
+
71
+ msgid "%1 todo"
72
+ msgid_plural "%1 todos"
73
+ msgstr[0] ""
74
+ msgstr[1] ""
75
+
76
+ msgid "Todos"
77
+ msgstr ""
78
+
79
+ msgid "Text"
80
+ msgstr ""
81
+
82
+ msgid "Create todo"
83
+ msgstr ""
84
+
85
+ msgid "Back to plan"
86
+ msgstr ""
87
+
88
+ msgid "Done"
89
+ msgstr ""
package/po/uk.po CHANGED
@@ -1,100 +1,107 @@
1
- # Ukrainian translations for create-evikit package.
2
- # Copyright (C) 2026 Denys Nykula <nykula@ukr.net>
3
- # This file is distributed under the same license as the create-evikit package.
4
- # Denys Nykula <nykula@ukr.net>, 2026.
5
- #
6
- msgid ""
7
- msgstr ""
8
- "Project-Id-Version: create-evikit\n"
9
- "Report-Msgid-Bugs-To: \n"
10
- "POT-Creation-Date: 2026-07-07 23:06+0300\n"
11
- "PO-Revision-Date: 2026-07-07 23:07+0300\n"
12
- "Last-Translator: EMAIL@ADDRESS\n"
13
- "Language-Team: Ukrainian\n"
14
- "Language: uk\n"
15
- "MIME-Version: 1.0\n"
16
- "Content-Type: text/plain; charset=UTF-8\n"
17
- "Content-Transfer-Encoding: 8bit\n"
18
- "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
19
- "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
20
- "X-Generator: Poedit 3.9\n"
21
-
22
- msgid "EviKit: Preact SSR framework for small web apps"
23
- msgstr "EviKit: Preact SSR-фреймворк для невеликих вебзастосунків"
24
-
25
- msgid "Preact SSR framework."
26
- msgstr "Preact SSR-фреймворк."
27
-
28
- msgid "For small web apps hosted on a <b>VPS</b> or in local network."
29
- msgstr ""
30
- "Для невеликих вебзастосунків, розміщених на <b>VPS</b> чи в локальній мережі."
31
-
32
- msgid ""
33
- "Standard modern <b>JavaScript</b>, no JSX or TS. <b>Forms</b> accessible "
34
- "when JS doesn't load. Automatic <b>Swagger</b> specification. First-class "
35
- "API input/output <b>validation</b>. Built-in <b>node:sqlite ORM</b>. Support "
36
- "for UI <b>translation</b> through .po files."
37
- msgstr ""
38
- "Стандартний сучасний <b>JavaScript</b>, без JSX чи TS. <b>Форми</b> "
39
- "доступні, навіть коли JS не запускається. <b>Swagger</b>-специфікація "
40
- "генерується автоматично. <b>Валідація</b> введення-виведення API - з "
41
- "коробки. Вбудована <b>ORM</b> на основі <b>node:sqlite</b>. Підтримується "
42
- "<b>переклад</b> інтерфейсу через .po-файли."
43
-
44
- msgid "Documentation"
45
- msgstr "Документація"
46
-
47
- msgid "Report <b>issue</b>"
48
- msgstr "Повідомити про <b>ваду</b>"
49
-
50
- msgid ""
51
- "See <a href=\"{todo}\">live demo</a> of the example code from below. Todos "
52
- "are deleted every 10 minutes."
53
- msgstr ""
54
- "Перегляньте <a href=\"{todo}\">живе демо</a> коду зразків, наведених унизу. "
55
- "Завдання видаляються кожні 10 хвилин."
56
-
57
- msgid "Quick start"
58
- msgstr "Швидкий початок"
59
-
60
- msgid ""
61
- "There’s intentionally <b>not much API</b>. Documentation entirely fits in a "
62
- "long but straightforward <b>README</b>. Like the Django tutorial, it guides "
63
- "you through <b><a href=\"{documentation}\">writing an application</a></b> "
64
- "using all the primary features of the EviKit framework."
65
- msgstr ""
66
- "<b>API навмисне небагато</b>. Документація повністю вміщається в довгий, але "
67
- "послідовний <b>README</b>-файл. Подібно до посібника Django, він містить "
68
- "покрокові інструкції з <b><a href=\"{documentation}\">написання застосунка</"
69
- "a></b>, що використовує всі основні функції фреймворка EviKit."
70
-
71
- msgid ""
72
- "<a href=\"{evikit}\"><b>Source code</b> at Codeberg</a> under <b>MIT</b> "
73
- "license"
74
- msgstr ""
75
- "<a href=\"{evikit}\"><b>Вихідний код</b> на Codeberg</a> на умовах ліцензії "
76
- "<b>MIT</b>"
77
-
78
- msgid "Discuss on <b>Fediverse</b>"
79
- msgstr "Обговорити в <b>Fediverse</b>"
80
-
81
- msgid "%1 todo"
82
- msgid_plural "%1 todos"
83
- msgstr[0] "%1 завдання"
84
- msgstr[1] "%1 завдання"
85
- msgstr[2] "%1 завдань"
86
-
87
- msgid "Todos"
88
- msgstr "Завдання"
89
-
90
- msgid "Text"
91
- msgstr "Текст"
92
-
93
- msgid "Create todo"
94
- msgstr "Створити завдання"
95
-
96
- msgid "Back to plan"
97
- msgstr "Назад до плану"
98
-
99
- msgid "Done"
100
- msgstr "Готово"
1
+ # Ukrainian translations for create-evikit package.
2
+ # Copyright (C) 2026 Denys Nykula <nykula@ukr.net>
3
+ # This file is distributed under the same license as the create-evikit package.
4
+ # Denys Nykula <nykula@ukr.net>, 2026.
5
+ #
6
+ msgid ""
7
+ msgstr ""
8
+ "Project-Id-Version: create-evikit\n"
9
+ "Report-Msgid-Bugs-To: \n"
10
+ "POT-Creation-Date: 2026-07-08 01:57+0300\n"
11
+ "PO-Revision-Date: 2026-07-08 01:57+0300\n"
12
+ "Last-Translator: EMAIL@ADDRESS\n"
13
+ "Language-Team: Ukrainian\n"
14
+ "Language: uk\n"
15
+ "MIME-Version: 1.0\n"
16
+ "Content-Type: text/plain; charset=UTF-8\n"
17
+ "Content-Transfer-Encoding: 8bit\n"
18
+ "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
19
+ "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
20
+ "X-Generator: Poedit 3.9\n"
21
+
22
+ msgid "EviKit: Preact SSR framework for small web apps"
23
+ msgstr "EviKit: Preact SSR-фреймворк для невеликих вебзастосунків"
24
+
25
+ msgid "Preact SSR framework."
26
+ msgstr "Preact SSR-фреймворк."
27
+
28
+ msgid "For small web apps hosted on a <b>VPS</b> or in local network."
29
+ msgstr ""
30
+ "Для невеликих вебзастосунків, розміщених на <b>VPS</b> чи в локальній мережі."
31
+
32
+ msgid ""
33
+ "Standard modern <b>JavaScript</b>, no JSX or TS. <b>Forms</b> accessible "
34
+ "when JS doesn't load. Automatic <b>Swagger</b> specification. First-class "
35
+ "API input/output <b>validation</b>. Built-in <b>node:sqlite ORM</b>. Support "
36
+ "for UI <b>translation</b> through .po files."
37
+ msgstr ""
38
+ "Стандартний сучасний <b>JavaScript</b>, без JSX чи TS. <b>Форми</b> "
39
+ "доступні, навіть коли JS не запускається. <b>Swagger</b>-специфікація "
40
+ "генерується автоматично. <b>Валідація</b> введення-виведення API - з "
41
+ "коробки. Вбудована <b>ORM</b> на основі <b>node:sqlite</b>. Підтримується "
42
+ "<b>переклад</b> інтерфейсу через .po-файли."
43
+
44
+ msgid "Documentation"
45
+ msgstr "Документація"
46
+
47
+ msgid "Report <b>issue</b>"
48
+ msgstr "Повідомити про <b>ваду</b>"
49
+
50
+ msgid ""
51
+ "See <a href=\"{todo}\">live demo</a> of the example code from below. Todos "
52
+ "are deleted every 10 minutes."
53
+ msgstr ""
54
+ "Перегляньте <a href=\"{todo}\">живе демо</a> коду зразків, наведених унизу. "
55
+ "Завдання видаляються кожні 10 хвилин."
56
+
57
+ msgid ""
58
+ "<a href=\"{openapi}\" target=\"_top\">OpenAPI demo</a> (Swagger) is "
59
+ "generated from API route schema."
60
+ msgstr ""
61
+ "<a href=\"{openapi}\" target=\"_top\">Демо OpenAPI</a> (Swagger) генерується "
62
+ "зі схеми API-шляхів."
63
+
64
+ msgid "Quick start"
65
+ msgstr "Швидкий початок"
66
+
67
+ msgid ""
68
+ "There’s intentionally <b>not much API</b>. Documentation entirely fits in a "
69
+ "long but straightforward <b>README</b>. Like the Django tutorial, it guides "
70
+ "you through <b><a href=\"{documentation}\">writing an application</a></b> "
71
+ "using all the primary features of the EviKit framework."
72
+ msgstr ""
73
+ "<b>API навмисне небагато</b>. Документація повністю вміщається в довгий, але "
74
+ "послідовний <b>README</b>-файл. Подібно до посібника Django, він містить "
75
+ "покрокові інструкції з <b><a href=\"{documentation}\">написання застосунка</"
76
+ "a></b>, що використовує всі основні функції фреймворка EviKit."
77
+
78
+ msgid ""
79
+ "<a href=\"{evikit}\"><b>Source code</b> at Codeberg</a> under <b>MIT</b> "
80
+ "license"
81
+ msgstr ""
82
+ "<a href=\"{evikit}\"><b>Вихідний код</b> на Codeberg</a> на умовах ліцензії "
83
+ "<b>MIT</b>"
84
+
85
+ msgid "Discuss on <b>Fediverse</b>"
86
+ msgstr "Обговорити в <b>Fediverse</b>"
87
+
88
+ msgid "%1 todo"
89
+ msgid_plural "%1 todos"
90
+ msgstr[0] "%1 завдання"
91
+ msgstr[1] "%1 завдання"
92
+ msgstr[2] "%1 завдань"
93
+
94
+ msgid "Todos"
95
+ msgstr "Завдання"
96
+
97
+ msgid "Text"
98
+ msgstr "Текст"
99
+
100
+ msgid "Create todo"
101
+ msgstr "Створити завдання"
102
+
103
+ msgid "Back to plan"
104
+ msgstr "Назад до плану"
105
+
106
+ msgid "Done"
107
+ msgstr "Готово"
package/src/app.js CHANGED
@@ -3,6 +3,7 @@ import { h } from "preact";
3
3
  import { Route } from "preact-iso";
4
4
 
5
5
  import { acceptLanguages } from "./lib/index.js";
6
+ import { ErrorPage } from "./routes/error.js";
6
7
  import { HomePage } from "./routes/page.js";
7
8
  import { TodoPage } from "./routes/todo/page.js";
8
9
 
@@ -18,6 +19,7 @@ export function App(props) {
18
19
  props,
19
20
  h(Route, { component: HomePage, path: "/" }),
20
21
  h(Route, { component: TodoPage, path: "/todo" }),
22
+ h(Route, { component: ErrorPage, default: true }),
21
23
  );
22
24
  }
23
25
 
@@ -1,5 +1,7 @@
1
1
  import { endpoint, json } from "evikit/server";
2
+ import { ok } from "node:assert/strict";
2
3
  import { readFile } from "node:fs/promises";
4
+ import { object, parse, record, string } from "valibot";
3
5
 
4
6
  import { HomeGet } from "./types.js";
5
7
 
@@ -8,6 +10,8 @@ export const template = [
8
10
  "src/routes/api/todo/types.js",
9
11
  "src/routes/api/todo/server.js",
10
12
  "src/routes/todo/page.js",
13
+ "src/routes/error.js",
14
+ "src/lib/index.js",
11
15
  "src/api.js",
12
16
  "src/app.js",
13
17
  "index.js",
@@ -17,13 +21,27 @@ export const template = [
17
21
  ];
18
22
 
19
23
  export const HomeServer = {
20
- get: endpoint(HomeGet, async () =>
21
- json({
24
+ get: endpoint(HomeGet, async () => {
25
+ const latest = Object.entries(
26
+ parse(
27
+ object({ time: record(string(), string()) }),
28
+ await (await fetch("https://registry.npmjs.org/evikit")).json(),
29
+ ).time,
30
+ )
31
+ .filter(
32
+ ([, publishedAt]) =>
33
+ // Give me 30 minutes for hotfix.
34
+ new Date(publishedAt).getTime() < Date.now() - 30 * 60e3,
35
+ )
36
+ .at(-1);
37
+ ok(latest);
38
+ return json({
22
39
  File: Object.fromEntries(
23
40
  await Promise.all(
24
41
  template.map(async (path) => [path, await readFile(path, "utf8")]),
25
42
  ),
26
43
  ),
27
- }),
28
- ),
44
+ npm: { publishedAt: latest[1], version: latest[0] },
45
+ });
46
+ }),
29
47
  };
@@ -2,5 +2,10 @@ import { object, record, string } from "valibot";
2
2
 
3
3
  export const HomeGet = {
4
4
  input: object({ lang: string() }),
5
- outputs: { 200: object({ File: record(string(), string()) }) },
5
+ outputs: {
6
+ 200: object({
7
+ File: record(string(), string()),
8
+ npm: object({ publishedAt: string(), version: string() }),
9
+ }),
10
+ },
6
11
  };
@@ -0,0 +1,16 @@
1
+ import { a, h1, main, p, useStatus } from "evikit";
2
+ import { useMeta, useTitle } from "hoofd/preact";
3
+
4
+ /** @param {{ children?: import("preact").ComponentChildren, error?: unknown }} props */
5
+ export function ErrorPage({ children, error }) {
6
+ const status = useStatus(error);
7
+ useMeta({ content: "noindex", name: "robots" });
8
+ useTitle(status);
9
+
10
+ return main(
11
+ { class: "m-8" },
12
+ p({ class: "mb-4" }, a({ class: "link", href: "/" }, "EviKit")),
13
+ h1({ class: "text-2xl" }, status),
14
+ children,
15
+ );
16
+ }
@@ -1,11 +1,33 @@
1
1
  import { highlightAll } from "@speed-highlight/core";
2
- import { a, b, div, figure, h1, main, p, span, useApi, useI18n } from "evikit";
2
+ import {
3
+ a,
4
+ b,
5
+ blockquote,
6
+ br,
7
+ code,
8
+ div,
9
+ figcaption,
10
+ figure,
11
+ footer,
12
+ h1,
13
+ hr,
14
+ main,
15
+ nav,
16
+ p,
17
+ pre,
18
+ span,
19
+ useApi,
20
+ useI18n,
21
+ } from "evikit";
3
22
  import { useMeta, useTitle } from "hoofd/preact";
4
23
  import { h } from "preact";
5
24
  import { useEffect } from "preact/hooks";
6
25
 
7
26
  import { HomeGet } from "./api/types.js";
8
27
 
28
+ const darkBg = "dark:bg-[#161b22]";
29
+ const blockquoteStyle = `bg-gray-50 ${darkBg} border border-gray-100 dark:border-black mx-auto p-4 rounded-xl md:text-center`;
30
+
9
31
  export function HomePage() {
10
32
  const i18n = useI18n();
11
33
  const { data } = useApi(HomeGet);
@@ -21,11 +43,31 @@ export function HomePage() {
21
43
 
22
44
  return main(
23
45
  { class: "max-w-5xl mx-auto p-8" },
46
+ p(
47
+ { class: "mb-4 sm:text-center" },
48
+ a(
49
+ {
50
+ class: "badge badge-info badge-soft",
51
+ href: "https://npmx.dev/package/evikit",
52
+ },
53
+ "npm i evikit",
54
+ data
55
+ ? [
56
+ "@^" + data.npm.version + " ",
57
+ span(
58
+ { class: "opacity-50" },
59
+ `# ${data.npm.publishedAt.replace(/T.*/, "")}`,
60
+ ),
61
+ ]
62
+ : undefined,
63
+ ),
64
+ ),
65
+
24
66
  h1(
25
- { class: "mb-4 text-xl lg:text-3xl lg:text-center" },
67
+ { class: "mb-4 text-xl lg:text-3xl sm:text-center" },
26
68
  "EviKit: ",
27
69
  b(null, i18n.gettext("Preact SSR framework.")),
28
- h("br", null),
70
+ br(null),
29
71
  span({
30
72
  class: "mt-0 text-base lg:text-sm",
31
73
  ...sanitize(
@@ -45,8 +87,7 @@ export function HomePage() {
45
87
  ),
46
88
  }),
47
89
 
48
- h(
49
- "nav",
90
+ nav(
50
91
  { class: "gap-4 grid grid-cols-2 max-w-lg mx-auto mt-6 text-center" },
51
92
 
52
93
  a(
@@ -61,32 +102,39 @@ export function HomePage() {
61
102
  }),
62
103
  ),
63
104
 
64
- p({
65
- class: "mb-6 lg:text-center",
66
- ...sanitize(
67
- i18n
68
- .gettext(
69
- 'See <a href="{todo}">live demo</a> of the example code from below. Todos are deleted every 10 minutes.',
70
- )
71
- .replace("{todo}", "/todo"),
72
- ),
73
- }),
105
+ p(
106
+ { class: "mb-6 lg:text-center", id: "demo" },
107
+ span({
108
+ ...sanitize(
109
+ i18n
110
+ .gettext(
111
+ 'See <a href="{todo}">live demo</a> of the example code from below. Todos are deleted every 10 minutes.',
112
+ )
113
+ .replace("{todo}", "/todo"),
114
+ ),
115
+ }),
116
+ " ",
117
+ span({
118
+ ...sanitize(
119
+ i18n
120
+ .gettext(
121
+ '<a href="{openapi}" target="_top">OpenAPI demo</a> (Swagger) is generated from API route schema.',
122
+ )
123
+ .replace(
124
+ "{openapi}",
125
+ /localhost/.test(location.href) ? "/openapi.json" : "/openapi",
126
+ ),
127
+ ),
128
+ class: "lg:block",
129
+ }),
130
+ ),
74
131
 
75
- h(
76
- "blockquote",
77
- {
78
- class: "max-w-xs mx-auto mb-8 p-4 rounded-lg text-center",
79
- style: {
80
- background: "rgba(0, 0, 0, 0.025)",
81
- border: "1px solid rgba(0, 0, 0, 0.05)",
82
- },
83
- },
132
+ blockquote(
133
+ { class: `${blockquoteStyle} max-w-sm mb-8` },
84
134
  p(null, b(null, i18n.gettext("Quick start"))),
85
- h(
86
- "pre",
135
+ pre(
87
136
  null,
88
- h(
89
- "code",
137
+ code(
90
138
  { class: "!bg-transparent shj-lang-bash" },
91
139
  `
92
140
  npm init evikit my-app
@@ -127,6 +175,20 @@ npm run dev
127
175
  b(null, "todo/page.js"),
128
176
  ),
129
177
 
178
+ h(
179
+ Code,
180
+ { file: data?.File["src/routes/error.js"], lang: "js" },
181
+ span(null, "src/routes/"),
182
+ b(null, "error.js"),
183
+ ),
184
+
185
+ h(
186
+ Code,
187
+ { file: data?.File["src/lib/index.js"], lang: "js" },
188
+ span(null, "src/lib/"),
189
+ b(null, "index.js"),
190
+ ),
191
+
130
192
  h(
131
193
  Code,
132
194
  { file: data?.File["src/api.js"], lang: "js", tag: "details" },
@@ -167,16 +229,10 @@ npm run dev
167
229
  ),
168
230
  ),
169
231
 
170
- h("hr", { class: "opacity-0" }),
232
+ hr({ class: "opacity-0" }),
171
233
  h(
172
234
  "blockquote",
173
- {
174
- class: "max-w-2xl mx-auto my-8 p-4 rounded-lg text-center",
175
- style: {
176
- background: "rgba(0, 0, 0, 0.025)",
177
- border: "1px solid rgba(0, 0, 0, 0.05)",
178
- },
179
- },
235
+ { class: `${blockquoteStyle} max-w-2xl my-8` },
180
236
  p({
181
237
  ...sanitize(
182
238
  i18n
@@ -191,10 +247,9 @@ npm run dev
191
247
  }),
192
248
  ),
193
249
 
194
- h("hr", { class: "opacity-0" }),
195
- h(
196
- "footer",
197
- { class: "text-center" },
250
+ hr({ class: "opacity-0" }),
251
+ footer(
252
+ { class: "md:text-center" },
198
253
  p(
199
254
  { class: "mb-4" },
200
255
  "Copyright © 2026 ",
@@ -223,27 +278,27 @@ npm run dev
223
278
 
224
279
  /** @param {{ children?: any, lang: string, file: string | undefined, tag?: string }} props */
225
280
  function Code({ children, file, lang, tag }) {
226
- const inner = h(
227
- "pre",
281
+ const inner = pre(
228
282
  { class: "mt-4 overflow-auto text-sm" },
229
- h(
230
- "code",
283
+ code(
231
284
  { class: `w-full shj-lang-${lang}` },
232
285
  file
233
286
  ?.replace(/.*EviKit.*\r?\n/g, "")
234
- .replace(/\{[\s\S]*?\} from/g, (match) => match.replace(/\s+/g, " "))
235
- .replace(/.*(HomeServer|acceptLanguage).*\r?\n/g, "")
236
- .replace(/.*(HomePage|acceptLanguage).*\r?\n/g, "")
287
+ .replace(/\{[0-9a-z,\s]*?\} from/gi, (match) =>
288
+ match.replace(/\s+/g, " "),
289
+ )
290
+ .replace(/.*HomeServer.*\r?\n/g, "")
291
+ .replace(/.*HomePage.*\r?\n/g, "")
237
292
  .replace(
238
293
  /<link\s+href="\/node_modules\/[^"]+.css"\s+media="[^"]+"\s+rel="stylesheet"\s+\/>\s+/g,
239
294
  "",
240
295
  ),
241
296
  ),
242
297
  );
243
- const style = "border-lg mb-4 mx-0 overflow-hidden p-4";
298
+ const style = `${darkBg} mb-4 mx-0 overflow-hidden p-4 rounded-xl`;
244
299
  return tag == "details"
245
300
  ? h("details", { class: style }, h("summary", null, children), inner)
246
- : figure({ class: style }, h("figcaption", null, children), inner);
301
+ : figure({ class: style }, figcaption(null, children), inner);
247
302
  }
248
303
 
249
304
  /** @param {string} html */
@@ -251,7 +306,8 @@ function sanitize(html) {
251
306
  return {
252
307
  dangerouslySetInnerHTML: {
253
308
  __html: html.replace(/<([^>]*)>/g, (match) =>
254
- /^<\/?[a-z]+>$/.test(match) || /^<a href="[a-z:/.#]*">$/.test(match)
309
+ /^<\/?[a-z]+>$/.test(match) ||
310
+ /^<a href="[a-z:/.#]*"( target="_top")?>$/.test(match)
255
311
  ? match
256
312
  : "",
257
313
  ),