create-evikit 2.2.3 → 2.3.1

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.
@@ -23,10 +23,12 @@ for (const file of [...template, "eslint.config.js", "tsconfig.json"]) {
23
23
  mkdirSync(dirname(path), { recursive: true });
24
24
  writeFileSync(
25
25
  path,
26
- readFileSync(join(ownRoot, file), "utf8").replace(
27
- /<link\s+href="\/node_modules\/[^"]+.css"\s+media="[^"]+"\s+rel="stylesheet"\s+\/>\s+/g,
28
- "",
29
- ),
26
+ readFileSync(join(ownRoot, file), "utf8")
27
+ .replace(
28
+ /<link\s+href="[^"]+"(\s+media="[^"]+")?\s+rel="[a-z]+"\s+\/>\s+/g,
29
+ "",
30
+ )
31
+ .replace(', "uk"', ""),
30
32
  );
31
33
  }
32
34
 
@@ -47,19 +49,6 @@ package-lock.json
47
49
 
48
50
  // ---
49
51
 
50
- writeFileSync(
51
- join(target, "src/lib/index.js"),
52
- `
53
- import acceptLanguage from "accept-language";
54
-
55
- export function acceptLanguages() {
56
- acceptLanguage.languages(["en"]);
57
- }
58
- `.trimStart(),
59
- );
60
-
61
- // ---
62
-
63
52
  writeFileSync(
64
53
  join(target, "src/routes/api/types.js"),
65
54
  `
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/index.html CHANGED
@@ -2,6 +2,7 @@
2
2
  <html lang="en">
3
3
  <head>
4
4
  <meta charset="utf-8" />
5
+ <link href="/static/favicon.svg" rel="icon" />
5
6
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
7
  <meta name="color-scheme" content="dark light" />
7
8
  <link href="/index.css" rel="stylesheet" />
package/index.js CHANGED
@@ -7,6 +7,7 @@ import { api } from "./src/api.js";
7
7
  import { prerender } from "./src/app.js";
8
8
 
9
9
  const app = express();
10
+ app.use("/static", express.static("./static"));
10
11
  app.use(api().onRequest);
11
12
  app.use("/openapi", swaggerUi.serve, swaggerUi.setup(api().openapi));
12
13
  app.use("/", express.static("./dist", { index: false, redirect: false }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-evikit",
3
- "version": "2.2.3",
3
+ "version": "2.3.1",
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.2",
41
41
  "express": "^5.2.1",
42
42
  "file-type": "^22.0.1",
43
43
  "hoofd": "^1.7.3",
@@ -8,7 +8,7 @@ msgid ""
8
8
  msgstr ""
9
9
  "Project-Id-Version: create-evikit\n"
10
10
  "Report-Msgid-Bugs-To: \n"
11
- "POT-Creation-Date: 2026-07-07 23:06+0300\n"
11
+ "POT-Creation-Date: 2026-07-08 12:51+0300\n"
12
12
  "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13
13
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
14
  "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -21,12 +21,6 @@ msgstr ""
21
21
  msgid "EviKit: Preact SSR framework for small web apps"
22
22
  msgstr ""
23
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
24
  msgid ""
31
25
  "Standard modern <b>JavaScript</b>, no JSX or TS. <b>Forms</b> accessible "
32
26
  "when JS doesn't load. Automatic <b>Swagger</b> specification. First-class "
@@ -34,6 +28,12 @@ msgid ""
34
28
  "for UI <b>translation</b> through .po files."
35
29
  msgstr ""
36
30
 
31
+ msgid "Preact SSR framework."
32
+ msgstr ""
33
+
34
+ msgid "For small web apps hosted on a <b>VPS</b> or in local network."
35
+ msgstr ""
36
+
37
37
  msgid "Documentation"
38
38
  msgstr ""
39
39
 
@@ -45,6 +45,11 @@ msgid ""
45
45
  "are deleted every 10 minutes."
46
46
  msgstr ""
47
47
 
48
+ msgid ""
49
+ "<a href=\"{openapi}\" target=\"_top\">OpenAPI demo</a> (Swagger) is "
50
+ "generated from API route schema."
51
+ msgstr ""
52
+
48
53
  msgid "Quick start"
49
54
  msgstr ""
50
55
 
@@ -55,6 +60,9 @@ msgid ""
55
60
  "using all the primary features of the EviKit framework."
56
61
  msgstr ""
57
62
 
63
+ msgid "Favicon by <a href=\"{artist}\">Solar Icons</a>"
64
+ msgstr ""
65
+
58
66
  msgid ""
59
67
  "<a href=\"{evikit}\"><b>Source code</b> at Codeberg</a> under <b>MIT</b> "
60
68
  "license"
package/po/uk.po CHANGED
@@ -7,8 +7,8 @@ msgid ""
7
7
  msgstr ""
8
8
  "Project-Id-Version: create-evikit\n"
9
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"
10
+ "POT-Creation-Date: 2026-07-08 12:51+0300\n"
11
+ "PO-Revision-Date: 2026-07-08 03:11+0300\n"
12
12
  "Last-Translator: EMAIL@ADDRESS\n"
13
13
  "Language-Team: Ukrainian\n"
14
14
  "Language: uk\n"
@@ -22,13 +22,6 @@ msgstr ""
22
22
  msgid "EviKit: Preact SSR framework for small web apps"
23
23
  msgstr "EviKit: Preact SSR-фреймворк для невеликих вебзастосунків"
24
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
25
  msgid ""
33
26
  "Standard modern <b>JavaScript</b>, no JSX or TS. <b>Forms</b> accessible "
34
27
  "when JS doesn't load. Automatic <b>Swagger</b> specification. First-class "
@@ -41,6 +34,13 @@ msgstr ""
41
34
  "коробки. Вбудована <b>ORM</b> на основі <b>node:sqlite</b>. Підтримується "
42
35
  "<b>переклад</b> інтерфейсу через .po-файли."
43
36
 
37
+ msgid "Preact SSR framework."
38
+ msgstr "Preact SSR-фреймворк."
39
+
40
+ msgid "For small web apps hosted on a <b>VPS</b> or in local network."
41
+ msgstr ""
42
+ "Для невеликих вебзастосунків, розміщених на <b>VPS</b> чи в локальній мережі."
43
+
44
44
  msgid "Documentation"
45
45
  msgstr "Документація"
46
46
 
@@ -54,6 +54,13 @@ msgstr ""
54
54
  "Перегляньте <a href=\"{todo}\">живе демо</a> коду зразків, наведених унизу. "
55
55
  "Завдання видаляються кожні 10 хвилин."
56
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
+
57
64
  msgid "Quick start"
58
65
  msgstr "Швидкий початок"
59
66
 
@@ -68,6 +75,9 @@ msgstr ""
68
75
  "покрокові інструкції з <b><a href=\"{documentation}\">написання застосунка</"
69
76
  "a></b>, що використовує всі основні функції фреймворка EviKit."
70
77
 
78
+ msgid "Favicon by <a href=\"{artist}\">Solar Icons</a>"
79
+ msgstr "Піктограма за авторством <a href=\"{artist}\">Solar Icons</a>"
80
+
71
81
  msgid ""
72
82
  "<a href=\"{evikit}\"><b>Source code</b> at Codeberg</a> under <b>MIT</b> "
73
83
  "license"
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,31 +1,79 @@
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
+ details,
9
+ div,
10
+ figcaption,
11
+ figure,
12
+ footer,
13
+ h1,
14
+ hr,
15
+ main,
16
+ nav,
17
+ p,
18
+ pre,
19
+ span,
20
+ summary,
21
+ useApi,
22
+ useI18n,
23
+ } from "evikit";
3
24
  import { useMeta, useTitle } from "hoofd/preact";
4
25
  import { h } from "preact";
5
26
  import { useEffect } from "preact/hooks";
6
27
 
7
28
  import { HomeGet } from "./api/types.js";
8
29
 
30
+ const darkBg = "dark:bg-[#161b22]";
31
+ const blockquoteStyle = `bg-gray-50 ${darkBg} border border-gray-100 dark:border-black mx-auto p-4 rounded-xl md:text-center`;
32
+
9
33
  export function HomePage() {
10
34
  const i18n = useI18n();
11
35
  const { data } = useApi(HomeGet);
12
36
  useTitle(i18n.gettext("EviKit: Preact SSR framework for small web apps"));
37
+ const description = i18n.gettext(
38
+ "Standard modern <b>JavaScript</b>, no JSX or TS. <b>Forms</b> accessible when JS doesn't load. Automatic <b>Swagger</b> specification. First-class API input/output <b>validation</b>. Built-in <b>node:sqlite ORM</b>. Support for UI <b>translation</b> through .po files.",
39
+ );
13
40
  useMeta({
14
- content:
15
- "Standard modern JavaScript, no JSX or TS. Forms accessible when JS doesn't load. Automatic Swagger specification. First-class API input/output validation. Built-in node:sqlite ORM. Support for UI translation through .po files.",
41
+ content: description.replace(/<[^>]+>/g, ""),
16
42
  name: "description",
17
43
  });
44
+ useMeta({ content: "/static/preact-ssr-framework.png", name: "og:image" });
45
+ useMeta({ content: "summary_large_image", name: "twitter:card" });
18
46
  useEffect(() => {
19
47
  highlightAll();
20
48
  }, [JSON.stringify(data?.File)]);
21
49
 
22
50
  return main(
23
51
  { class: "max-w-5xl mx-auto p-8" },
52
+ p(
53
+ { class: "mb-4 sm:text-center" },
54
+ a(
55
+ {
56
+ class: "badge badge-info badge-soft",
57
+ href: "https://npmx.dev/package/evikit",
58
+ },
59
+ "npm i evikit",
60
+ data
61
+ ? [
62
+ "@^" + data.npm.version + " ",
63
+ span(
64
+ { class: "opacity-50" },
65
+ `# ${data.npm.publishedAt.replace(/T.*/, "")}`,
66
+ ),
67
+ ]
68
+ : undefined,
69
+ ),
70
+ ),
71
+
24
72
  h1(
25
- { class: "mb-4 text-xl lg:text-3xl lg:text-center" },
73
+ { class: "mb-4 text-xl lg:text-3xl sm:text-center" },
26
74
  "EviKit: ",
27
75
  b(null, i18n.gettext("Preact SSR framework.")),
28
- h("br", null),
76
+ br(null),
29
77
  span({
30
78
  class: "mt-0 text-base lg:text-sm",
31
79
  ...sanitize(
@@ -35,18 +83,9 @@ export function HomePage() {
35
83
  ),
36
84
  }),
37
85
  ),
86
+ p({ class: "lg:text-center", ...sanitize(description) }),
38
87
 
39
- p({
40
- class: "lg:text-center",
41
- ...sanitize(
42
- i18n.gettext(
43
- "Standard modern <b>JavaScript</b>, no JSX or TS. <b>Forms</b> accessible when JS doesn't load. Automatic <b>Swagger</b> specification. First-class API input/output <b>validation</b>. Built-in <b>node:sqlite ORM</b>. Support for UI <b>translation</b> through .po files.",
44
- ),
45
- ),
46
- }),
47
-
48
- h(
49
- "nav",
88
+ nav(
50
89
  { class: "gap-4 grid grid-cols-2 max-w-lg mx-auto mt-6 text-center" },
51
90
 
52
91
  a(
@@ -61,32 +100,39 @@ export function HomePage() {
61
100
  }),
62
101
  ),
63
102
 
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
- }),
103
+ p(
104
+ { class: "mb-6 lg:text-center", id: "demo" },
105
+ span({
106
+ ...sanitize(
107
+ i18n
108
+ .gettext(
109
+ 'See <a href="{todo}">live demo</a> of the example code from below. Todos are deleted every 10 minutes.',
110
+ )
111
+ .replace("{todo}", "/todo"),
112
+ ),
113
+ }),
114
+ " ",
115
+ span({
116
+ ...sanitize(
117
+ i18n
118
+ .gettext(
119
+ '<a href="{openapi}" target="_top">OpenAPI demo</a> (Swagger) is generated from API route schema.',
120
+ )
121
+ .replace(
122
+ "{openapi}",
123
+ /localhost/.test(location.href) ? "/openapi.json" : "/openapi",
124
+ ),
125
+ ),
126
+ class: "lg:block",
127
+ }),
128
+ ),
74
129
 
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
- },
130
+ blockquote(
131
+ { class: `${blockquoteStyle} max-w-sm mb-8` },
84
132
  p(null, b(null, i18n.gettext("Quick start"))),
85
- h(
86
- "pre",
133
+ pre(
87
134
  null,
88
- h(
89
- "code",
135
+ code(
90
136
  { class: "!bg-transparent shj-lang-bash" },
91
137
  `
92
138
  npm init evikit my-app
@@ -127,6 +173,20 @@ npm run dev
127
173
  b(null, "todo/page.js"),
128
174
  ),
129
175
 
176
+ h(
177
+ Code,
178
+ { file: data?.File["src/routes/error.js"], lang: "js" },
179
+ span(null, "src/routes/"),
180
+ b(null, "error.js"),
181
+ ),
182
+
183
+ h(
184
+ Code,
185
+ { file: data?.File["src/lib/index.js"], lang: "js" },
186
+ span(null, "src/lib/"),
187
+ b(null, "index.js"),
188
+ ),
189
+
130
190
  h(
131
191
  Code,
132
192
  { file: data?.File["src/api.js"], lang: "js", tag: "details" },
@@ -167,16 +227,10 @@ npm run dev
167
227
  ),
168
228
  ),
169
229
 
170
- h("hr", { class: "opacity-0" }),
230
+ hr({ class: "opacity-0" }),
171
231
  h(
172
232
  "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
- },
233
+ { class: `${blockquoteStyle} max-w-2xl my-8` },
180
234
  p({
181
235
  ...sanitize(
182
236
  i18n
@@ -191,15 +245,25 @@ npm run dev
191
245
  }),
192
246
  ),
193
247
 
194
- h("hr", { class: "opacity-0" }),
195
- h(
196
- "footer",
197
- { class: "text-center" },
248
+ hr({ class: "opacity-0" }),
249
+ footer(
250
+ { class: "md:text-center" },
198
251
  p(
199
252
  { class: "mb-4" },
200
253
  "Copyright © 2026 ",
201
254
  a({ href: "https://libre.net.ua" }, "Denys Nykula"),
202
255
  ),
256
+ p({
257
+ class: "mb-4",
258
+ ...sanitize(
259
+ i18n
260
+ .gettext('Favicon by <a href="{artist}">Solar Icons</a>')
261
+ .replace(
262
+ "{artist}",
263
+ "https://www.svgrepo.com/author/Solar%20Icons/",
264
+ ),
265
+ ),
266
+ }),
203
267
  p({
204
268
  class: "mb-4",
205
269
  ...sanitize(
@@ -223,27 +287,27 @@ npm run dev
223
287
 
224
288
  /** @param {{ children?: any, lang: string, file: string | undefined, tag?: string }} props */
225
289
  function Code({ children, file, lang, tag }) {
226
- const inner = h(
227
- "pre",
290
+ const inner = pre(
228
291
  { class: "mt-4 overflow-auto text-sm" },
229
- h(
230
- "code",
292
+ code(
231
293
  { class: `w-full shj-lang-${lang}` },
232
294
  file
233
295
  ?.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, "")
296
+ .replace(/\{[0-9a-z,\s]*?\} from/gi, (match) =>
297
+ match.replace(/\s+/g, " "),
298
+ )
299
+ .replace(/.*HomeServer.*\r?\n/g, "")
300
+ .replace(/.*HomePage.*\r?\n/g, "")
237
301
  .replace(
238
- /<link\s+href="\/node_modules\/[^"]+.css"\s+media="[^"]+"\s+rel="stylesheet"\s+\/>\s+/g,
302
+ /<link\s+href="[^"]+"(\s+media="[^"]+")?\s+rel="[a-z]+"\s+\/>\s+/g,
239
303
  "",
240
304
  ),
241
305
  ),
242
306
  );
243
- const style = "border-lg mb-4 mx-0 overflow-hidden p-4";
307
+ const style = `${darkBg} mb-4 mx-0 overflow-hidden p-4 rounded-xl`;
244
308
  return tag == "details"
245
- ? h("details", { class: style }, h("summary", null, children), inner)
246
- : figure({ class: style }, h("figcaption", null, children), inner);
309
+ ? details({ class: style }, summary(null, children), inner)
310
+ : figure({ class: style }, figcaption(null, children), inner);
247
311
  }
248
312
 
249
313
  /** @param {string} html */
@@ -251,7 +315,8 @@ function sanitize(html) {
251
315
  return {
252
316
  dangerouslySetInnerHTML: {
253
317
  __html: html.replace(/<([^>]*)>/g, (match) =>
254
- /^<\/?[a-z]+>$/.test(match) || /^<a href="[a-z:/.#]*">$/.test(match)
318
+ /^<\/?[a-z]+>$/.test(match) ||
319
+ /^<a href="[0-9A-Za-z:/.#%]*"( target="_top")?>$/.test(match)
255
320
  ? match
256
321
  : "",
257
322
  ),
@@ -0,0 +1,15 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
3
+ <style>
4
+ #app { fill: #84f }
5
+ #api { fill: #7d8 }
6
+ @media (prefers-color-scheme: dark) {
7
+ #app { fill: #c678dd }
8
+ #api { fill: #61afef }
9
+ }
10
+ </style>
11
+ <rect fill="#fff" width="18" height="6" x="3" y="4" />
12
+ <rect fill="#fff" width="18" height="6" x="3" y="14" />
13
+ <path id="app" fill-rule="evenodd" clip-rule="evenodd" d="M2.58579 3.58579C2 4.17157 2 5.11438 2 7C2 8.88562 2 9.82843 2.58579 10.4142C3.17157 11 4.11438 11 6 11H18C19.8856 11 20.8284 11 21.4142 10.4142C22 9.82843 22 8.88562 22 7C22 5.11438 22 4.17157 21.4142 3.58579C20.8284 3 19.8856 3 18 3H6C4.11438 3 3.17157 3 2.58579 3.58579ZM9 8.75C8.58579 8.75 8.25 8.41421 8.25 8V6C8.25 5.58579 8.58579 5.25 9 5.25C9.41421 5.25 9.75 5.58579 9.75 6V8C9.75 8.41421 9.41421 8.75 9 8.75ZM13.5 6.25C13.0858 6.25 12.75 6.58579 12.75 7C12.75 7.41421 13.0858 7.75 13.5 7.75H18C18.4142 7.75 18.75 7.41421 18.75 7C18.75 6.58579 18.4142 6.25 18 6.25H13.5ZM6 8.75C5.58579 8.75 5.25 8.41421 5.25 8L5.25 6C5.25 5.58579 5.58579 5.25 6 5.25C6.41421 5.25 6.75 5.58579 6.75 6V8C6.75 8.41421 6.41421 8.75 6 8.75Z"/>
14
+ <path id="api" fill-rule="evenodd" clip-rule="evenodd" d="M2.58579 13.5858C2 14.1716 2 15.1144 2 17C2 18.8856 2 19.8284 2.58579 20.4142C3.17157 21 4.11438 21 6 21H18C19.8856 21 20.8284 21 21.4142 20.4142C22 19.8284 22 18.8856 22 17C22 15.1144 22 14.1716 21.4142 13.5858C20.8284 13 19.8856 13 18 13H6C4.11438 13 3.17157 13 2.58579 13.5858ZM12.75 17C12.75 16.5858 13.0858 16.25 13.5 16.25H18C18.4142 16.25 18.75 16.5858 18.75 17C18.75 17.4142 18.4142 17.75 18 17.75H13.5C13.0858 17.75 12.75 17.4142 12.75 17ZM5.25 18C5.25 18.4142 5.58579 18.75 6 18.75C6.41421 18.75 6.75 18.4142 6.75 18V16C6.75 15.5858 6.41421 15.25 6 15.25C5.58579 15.25 5.25 15.5858 5.25 16L5.25 18ZM9 18.75C8.58579 18.75 8.25 18.4142 8.25 18V16C8.25 15.5858 8.58579 15.25 9 15.25C9.41421 15.25 9.75 15.5858 9.75 16V18C9.75 18.4142 9.41421 18.75 9 18.75Z"/>
15
+ </svg>
Binary file
Binary file