@takuhon/api 1.0.0 → 1.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.
- package/NOTICE +35 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +287 -59
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/NOTICE
CHANGED
|
@@ -8,3 +8,38 @@ Licensed under the Apache License, Version 2.0 (see LICENSE).
|
|
|
8
8
|
|
|
9
9
|
Third-party dependencies retain their own licenses; see this package's
|
|
10
10
|
node_modules listing for details.
|
|
11
|
+
|
|
12
|
+
-------------------------------------------------------------------------------
|
|
13
|
+
Bundled brand-logo icons (src/html/brand-icons.ts)
|
|
14
|
+
|
|
15
|
+
The standard HTML renderer inlines monochrome brand-logo glyphs for social link
|
|
16
|
+
types. These path definitions are derived from:
|
|
17
|
+
|
|
18
|
+
- Simple Icons (https://simpleicons.org) — CC0-1.0 (public domain). All glyphs
|
|
19
|
+
except LinkedIn: github, gitlab, x, mastodon, bluesky, instagram, youtube,
|
|
20
|
+
threads, facebook, rss.
|
|
21
|
+
|
|
22
|
+
- Bootstrap Icons (https://icons.getbootstrap.com) — MIT License,
|
|
23
|
+
Copyright (c) 2019-2024 The Bootstrap Authors. The LinkedIn glyph only
|
|
24
|
+
(Simple Icons removed the LinkedIn mark over trademark policy).
|
|
25
|
+
|
|
26
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
27
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
28
|
+
in the Software without restriction, including without limitation the rights
|
|
29
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
30
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
31
|
+
furnished to do so, subject to the following conditions:
|
|
32
|
+
|
|
33
|
+
The above copyright notice and this permission notice shall be included in all
|
|
34
|
+
copies or substantial portions of the Software.
|
|
35
|
+
|
|
36
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
37
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
38
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
39
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
40
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
41
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
42
|
+
SOFTWARE.
|
|
43
|
+
|
|
44
|
+
Brand logos are trademarks of their respective owners; their inclusion does not
|
|
45
|
+
imply endorsement.
|
package/dist/index.d.ts
CHANGED
|
@@ -56,6 +56,16 @@ interface ProblemResponseInput {
|
|
|
56
56
|
}
|
|
57
57
|
declare function problemResponse(c: Context, input: ProblemResponseInput): Response;
|
|
58
58
|
|
|
59
|
+
declare module 'hono' {
|
|
60
|
+
interface ContextVariableMap {
|
|
61
|
+
/**
|
|
62
|
+
* Set by the `/` handler when it embeds the contact widget, so the
|
|
63
|
+
* security-headers middleware serves the Turnstile-allowing CSP variant for
|
|
64
|
+
* that one response and the strict default everywhere else.
|
|
65
|
+
*/
|
|
66
|
+
contactEnabled?: boolean;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
59
69
|
interface PublicAppDeps {
|
|
60
70
|
storage: TakuhonStorage;
|
|
61
71
|
/**
|
|
@@ -292,6 +302,18 @@ interface RenderInput {
|
|
|
292
302
|
* metric-less) snapshot omits the section entirely.
|
|
293
303
|
*/
|
|
294
304
|
activitySnapshot?: ActivitySnapshot;
|
|
305
|
+
/**
|
|
306
|
+
* When set, embed the `@takuhon/contact` widget: a `<link>` to its stylesheet
|
|
307
|
+
* in `<head>` and a deferred `<script>` whose config travels as `data-*`
|
|
308
|
+
* attributes (no inline script, so the page CSP needs no `'unsafe-inline'`).
|
|
309
|
+
* The caller gates it on `settings.contact.enabled` and a present site key;
|
|
310
|
+
* the adapter is responsible for serving `/contact-widget.{js,css}` and for
|
|
311
|
+
* relaxing its CSP to allow the Turnstile origin.
|
|
312
|
+
*/
|
|
313
|
+
contact?: {
|
|
314
|
+
siteKey: string;
|
|
315
|
+
endpoint?: string;
|
|
316
|
+
};
|
|
295
317
|
}
|
|
296
318
|
/** Render a complete static HTML document for one locale-resolved profile. */
|
|
297
319
|
declare function renderProfileHtml(input: RenderInput): string;
|
package/dist/index.js
CHANGED
|
@@ -85,35 +85,201 @@ function nonEmpty(values, separator) {
|
|
|
85
85
|
return joined.length > 0 ? joined : void 0;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
// src/html/brand-icons.ts
|
|
89
|
+
var BRAND_ICONS = {
|
|
90
|
+
github: {
|
|
91
|
+
viewBox: "0 0 24 24",
|
|
92
|
+
path: "M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"
|
|
93
|
+
},
|
|
94
|
+
gitlab: {
|
|
95
|
+
viewBox: "0 0 24 24",
|
|
96
|
+
path: "m23.6004 9.5927-.0337-.0862L20.3.9814a.851.851 0 0 0-.3362-.405.8748.8748 0 0 0-.9997.0539.8748.8748 0 0 0-.29.4399l-2.2055 6.748H7.5375l-2.2057-6.748a.8573.8573 0 0 0-.29-.4412.8748.8748 0 0 0-.9997-.0537.8585.8585 0 0 0-.3362.4049L.4332 9.5015l-.0325.0862a6.0657 6.0657 0 0 0 2.0119 7.0105l.0113.0087.03.0213 4.976 3.7264 2.462 1.8633 1.4995 1.1321a1.0085 1.0085 0 0 0 1.2197 0l1.4995-1.1321 2.4619-1.8633 5.006-3.7489.0125-.01a6.0682 6.0682 0 0 0 2.0094-7.003z"
|
|
97
|
+
},
|
|
98
|
+
linkedin: {
|
|
99
|
+
viewBox: "0 0 16 16",
|
|
100
|
+
path: "M0 1.146C0 .513.526 0 1.175 0h13.65C15.474 0 16 .513 16 1.146v13.708c0 .633-.526 1.146-1.175 1.146H1.175C.526 16 0 15.487 0 14.854zm4.943 12.248V6.169H2.542v7.225zm-1.2-8.212c.837 0 1.358-.554 1.358-1.248-.015-.709-.52-1.248-1.342-1.248S2.4 3.226 2.4 3.934c0 .694.521 1.248 1.327 1.248zm4.908 8.212V9.359c0-.216.016-.432.08-.586.173-.431.568-.878 1.232-.878.869 0 1.216.662 1.216 1.634v3.865h2.401V9.25c0-2.22-1.184-3.252-2.764-3.252-1.274 0-1.845.7-2.165 1.193v.025h-.016l.016-.025V6.169h-2.4c.03.678 0 7.225 0 7.225z"
|
|
101
|
+
},
|
|
102
|
+
x: {
|
|
103
|
+
viewBox: "0 0 24 24",
|
|
104
|
+
path: "M14.234 10.162 22.977 0h-2.072l-7.591 8.824L7.251 0H.258l9.168 13.343L.258 24H2.33l8.016-9.318L16.749 24h6.993zm-2.837 3.299-.929-1.329L3.076 1.56h3.182l5.965 8.532.929 1.329 7.754 11.09h-3.182z"
|
|
105
|
+
},
|
|
106
|
+
mastodon: {
|
|
107
|
+
viewBox: "0 0 24 24",
|
|
108
|
+
path: "M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792 0 11.813 0h-.03c-3.98 0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.074 1.874.088 3.745.26 5.611.118 1.24.325 2.47.62 3.68.55 2.237 2.777 4.098 4.96 4.857 2.336.792 4.849.923 7.256.38.265-.061.527-.132.786-.213.585-.184 1.27-.39 1.774-.753a.057.057 0 0 0 .023-.043v-1.809a.052.052 0 0 0-.02-.041.053.053 0 0 0-.046-.01 20.282 20.282 0 0 1-4.709.545c-2.73 0-3.463-1.284-3.674-1.818a5.593 5.593 0 0 1-.319-1.433.053.053 0 0 1 .066-.054c1.517.363 3.072.546 4.632.546.376 0 .75 0 1.125-.01 1.57-.044 3.224-.124 4.768-.422.038-.008.077-.015.11-.024 2.435-.464 4.753-1.92 4.989-5.604.008-.145.03-1.52.03-1.67.002-.512.167-3.63-.024-5.545zm-3.748 9.195h-2.561V8.29c0-1.309-.55-1.976-1.67-1.976-1.23 0-1.846.79-1.846 2.35v3.403h-2.546V8.663c0-1.56-.617-2.35-1.848-2.35-1.112 0-1.668.668-1.67 1.977v6.218H4.822V8.102c0-1.31.337-2.35 1.011-3.12.696-.77 1.608-1.164 2.74-1.164 1.311 0 2.302.5 2.962 1.498l.638 1.06.638-1.06c.66-.999 1.65-1.498 2.96-1.498 1.13 0 2.043.395 2.74 1.164.675.77 1.012 1.81 1.012 3.12z"
|
|
109
|
+
},
|
|
110
|
+
bluesky: {
|
|
111
|
+
viewBox: "0 0 24 24",
|
|
112
|
+
path: "M5.202 2.857C7.954 4.922 10.913 9.11 12 11.358c1.087-2.247 4.046-6.436 6.798-8.501C20.783 1.366 24 .213 24 3.883c0 .732-.42 6.156-.667 7.037-.856 3.061-3.978 3.842-6.755 3.37 4.854.826 6.089 3.562 3.422 6.299-5.065 5.196-7.28-1.304-7.847-2.97-.104-.305-.152-.448-.153-.327 0-.121-.05.022-.153.327-.568 1.666-2.782 8.166-7.847 2.97-2.667-2.737-1.432-5.473 3.422-6.3-2.777.473-5.899-.308-6.755-3.369C.42 10.04 0 4.615 0 3.883c0-3.67 3.217-2.517 5.202-1.026"
|
|
113
|
+
},
|
|
114
|
+
instagram: {
|
|
115
|
+
viewBox: "0 0 24 24",
|
|
116
|
+
path: "M7.0301.084c-1.2768.0602-2.1487.264-2.911.5634-.7888.3075-1.4575.72-2.1228 1.3877-.6652.6677-1.075 1.3368-1.3802 2.127-.2954.7638-.4956 1.6365-.552 2.914-.0564 1.2775-.0689 1.6882-.0626 4.947.0062 3.2586.0206 3.6671.0825 4.9473.061 1.2765.264 2.1482.5635 2.9107.308.7889.72 1.4573 1.388 2.1228.6679.6655 1.3365 1.0743 2.1285 1.38.7632.295 1.6361.4961 2.9134.552 1.2773.056 1.6884.069 4.9462.0627 3.2578-.0062 3.668-.0207 4.9478-.0814 1.28-.0607 2.147-.2652 2.9098-.5633.7889-.3086 1.4578-.72 2.1228-1.3881.665-.6682 1.0745-1.3378 1.3795-2.1284.2957-.7632.4966-1.636.552-2.9124.056-1.2809.0692-1.6898.063-4.948-.0063-3.2583-.021-3.6668-.0817-4.9465-.0607-1.2797-.264-2.1487-.5633-2.9117-.3084-.7889-.72-1.4568-1.3876-2.1228C21.2982 1.33 20.628.9208 19.8378.6165 19.074.321 18.2017.1197 16.9244.0645 15.6471.0093 15.236-.005 11.977.0014 8.718.0076 8.31.0215 7.0301.0839m.1402 21.6932c-1.17-.0509-1.8053-.2453-2.2287-.408-.5606-.216-.96-.4771-1.3819-.895-.422-.4178-.6811-.8186-.9-1.378-.1644-.4234-.3624-1.058-.4171-2.228-.0595-1.2645-.072-1.6442-.079-4.848-.007-3.2037.0053-3.583.0607-4.848.05-1.169.2456-1.805.408-2.2282.216-.5613.4762-.96.895-1.3816.4188-.4217.8184-.6814 1.3783-.9003.423-.1651 1.0575-.3614 2.227-.4171 1.2655-.06 1.6447-.072 4.848-.079 3.2033-.007 3.5835.005 4.8495.0608 1.169.0508 1.8053.2445 2.228.408.5608.216.96.4754 1.3816.895.4217.4194.6816.8176.9005 1.3787.1653.4217.3617 1.056.4169 2.2263.0602 1.2655.0739 1.645.0796 4.848.0058 3.203-.0055 3.5834-.061 4.848-.051 1.17-.245 1.8055-.408 2.2294-.216.5604-.4763.96-.8954 1.3814-.419.4215-.8181.6811-1.3783.9-.4224.1649-1.0577.3617-2.2262.4174-1.2656.0595-1.6448.072-4.8493.079-3.2045.007-3.5825-.006-4.848-.0608M16.953 5.5864A1.44 1.44 0 1 0 18.39 4.144a1.44 1.44 0 0 0-1.437 1.4424M5.8385 12.012c.0067 3.4032 2.7706 6.1557 6.173 6.1493 3.4026-.0065 6.157-2.7701 6.1506-6.1733-.0065-3.4032-2.771-6.1565-6.174-6.1498-3.403.0067-6.156 2.771-6.1496 6.1738M8 12.0077a4 4 0 1 1 4.008 3.9921A3.9996 3.9996 0 0 1 8 12.0077"
|
|
117
|
+
},
|
|
118
|
+
youtube: {
|
|
119
|
+
viewBox: "0 0 24 24",
|
|
120
|
+
path: "M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"
|
|
121
|
+
},
|
|
122
|
+
threads: {
|
|
123
|
+
viewBox: "0 0 24 24",
|
|
124
|
+
path: "M12.186 24h-.007c-3.581-.024-6.334-1.205-8.184-3.509C2.35 18.44 1.5 15.586 1.472 12.01v-.017c.03-3.579.879-6.43 2.525-8.482C5.845 1.205 8.6.024 12.18 0h.014c2.746.02 5.043.725 6.826 2.098 1.677 1.29 2.858 3.13 3.509 5.467l-2.04.569c-1.104-3.96-3.898-5.984-8.304-6.015-2.91.022-5.11.936-6.54 2.717C4.307 6.504 3.616 8.914 3.589 12c.027 3.086.718 5.496 2.057 7.164 1.43 1.783 3.631 2.698 6.54 2.717 2.623-.02 4.358-.631 5.8-2.045 1.647-1.613 1.618-3.593 1.09-4.798-.31-.71-.873-1.3-1.634-1.75-.192 1.352-.622 2.446-1.284 3.272-.886 1.102-2.14 1.704-3.73 1.79-1.202.065-2.361-.218-3.259-.801-1.063-.689-1.685-1.74-1.752-2.964-.065-1.19.408-2.285 1.33-3.082.88-.76 2.119-1.207 3.583-1.291a13.853 13.853 0 0 1 3.02.142c-.126-.742-.375-1.332-.75-1.757-.513-.586-1.308-.883-2.359-.89h-.029c-.844 0-1.992.232-2.721 1.32L7.734 7.847c.98-1.454 2.568-2.256 4.478-2.256h.044c3.194.02 5.097 1.975 5.287 5.388.108.046.216.094.321.142 1.49.7 2.58 1.761 3.154 3.07.797 1.82.871 4.79-1.548 7.158-1.85 1.81-4.094 2.628-7.277 2.65Zm1.003-11.69c-.242 0-.487.007-.739.021-1.836.103-2.98.946-2.916 2.143.067 1.256 1.452 1.839 2.784 1.767 1.224-.065 2.818-.543 3.086-3.71a10.5 10.5 0 0 0-2.215-.221z"
|
|
125
|
+
},
|
|
126
|
+
facebook: {
|
|
127
|
+
viewBox: "0 0 24 24",
|
|
128
|
+
path: "M9.101 23.691v-7.98H6.627v-3.667h2.474v-1.58c0-4.085 1.848-5.978 5.858-5.978.401 0 .955.042 1.468.103a8.68 8.68 0 0 1 1.141.195v3.325a8.623 8.623 0 0 0-.653-.036 26.805 26.805 0 0 0-.733-.009c-.707 0-1.259.096-1.675.309a1.686 1.686 0 0 0-.679.622c-.258.42-.374.995-.374 1.752v1.297h3.919l-.386 2.103-.287 1.564h-3.246v8.245C19.396 23.238 24 18.179 24 12.044c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.628 3.874 10.35 9.101 11.647Z"
|
|
129
|
+
},
|
|
130
|
+
rss: {
|
|
131
|
+
viewBox: "0 0 24 24",
|
|
132
|
+
path: "M19.199 24C19.199 13.467 10.533 4.8 0 4.8V0c13.165 0 24 10.835 24 24h-4.801zM3.291 17.415c1.814 0 3.293 1.479 3.293 3.295 0 1.813-1.485 3.29-3.301 3.29C1.47 24 0 22.526 0 20.71s1.475-3.294 3.291-3.295zM15.909 24h-4.665c0-6.169-5.075-11.245-11.244-11.245V8.09c8.727 0 15.909 7.184 15.909 15.91z"
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
var BRAND_ICON_ALIASES = {
|
|
136
|
+
blog: "rss"
|
|
137
|
+
};
|
|
138
|
+
function brandIconSvg(type) {
|
|
139
|
+
const icon = BRAND_ICONS[BRAND_ICON_ALIASES[type] ?? type];
|
|
140
|
+
if (!icon) return "";
|
|
141
|
+
return `<svg class="brand-icon" viewBox="${icon.viewBox}" width="18" height="18" fill="currentColor" aria-hidden="true" focusable="false"><path d="${escapeHtml(icon.path)}"/></svg>`;
|
|
142
|
+
}
|
|
143
|
+
|
|
88
144
|
// src/html/build-html.ts
|
|
89
145
|
function escapeJsonLd(json) {
|
|
90
146
|
return json.replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026");
|
|
91
147
|
}
|
|
92
|
-
var
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
148
|
+
var DEFAULT_TOKENS = {
|
|
149
|
+
"--takuhon-color-bg": "#ffffff",
|
|
150
|
+
"--takuhon-color-surface": "#f6f7f9",
|
|
151
|
+
"--takuhon-color-text": "#1f2933",
|
|
152
|
+
"--takuhon-color-text-muted": "#52606d",
|
|
153
|
+
"--takuhon-color-border": "#d8dee7",
|
|
154
|
+
"--takuhon-color-primary": "#2563eb",
|
|
155
|
+
"--takuhon-color-primary-contrast": "#ffffff",
|
|
156
|
+
"--takuhon-color-accent": "#4f46e5",
|
|
157
|
+
"--takuhon-font-family": "system-ui,-apple-system,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif"
|
|
158
|
+
};
|
|
159
|
+
var DEFAULT_TOKENS_DARK = {
|
|
160
|
+
"--takuhon-color-bg": "#0f172a",
|
|
161
|
+
"--takuhon-color-surface": "#1e293b",
|
|
162
|
+
"--takuhon-color-text": "#e2e8f0",
|
|
163
|
+
"--takuhon-color-text-muted": "#94a3b8",
|
|
164
|
+
"--takuhon-color-border": "#334155",
|
|
165
|
+
"--takuhon-color-primary": "#60a5fa",
|
|
166
|
+
"--takuhon-color-primary-contrast": "#0f172a",
|
|
167
|
+
"--takuhon-color-accent": "#818cf8"
|
|
168
|
+
};
|
|
169
|
+
var INTERNAL_TOKENS = {
|
|
170
|
+
"--takuhon-space-1": "4px",
|
|
171
|
+
"--takuhon-space-2": "8px",
|
|
172
|
+
"--takuhon-space-3": "12px",
|
|
173
|
+
"--takuhon-space-4": "16px",
|
|
174
|
+
"--takuhon-space-5": "24px",
|
|
175
|
+
"--takuhon-space-6": "32px",
|
|
176
|
+
"--takuhon-radius-sm": "6px",
|
|
177
|
+
"--takuhon-radius-md": "12px",
|
|
178
|
+
"--takuhon-radius-full": "9999px",
|
|
179
|
+
"--takuhon-tap-target": "44px",
|
|
180
|
+
"--takuhon-font-size-sm": "14px",
|
|
181
|
+
"--takuhon-font-size-base": "16px",
|
|
182
|
+
"--takuhon-font-size-lg": "18px",
|
|
183
|
+
"--takuhon-font-size-xl": "22px",
|
|
184
|
+
"--takuhon-font-size-2xl": "28px",
|
|
185
|
+
"--takuhon-line-height": "1.7",
|
|
186
|
+
"--takuhon-max-content-width": "720px"
|
|
187
|
+
};
|
|
188
|
+
var COLOR_TOKEN_VARS = {
|
|
189
|
+
bg: "--takuhon-color-bg",
|
|
190
|
+
surface: "--takuhon-color-surface",
|
|
191
|
+
text: "--takuhon-color-text",
|
|
192
|
+
textMuted: "--takuhon-color-text-muted",
|
|
193
|
+
border: "--takuhon-color-border",
|
|
194
|
+
accent: "--takuhon-color-accent",
|
|
195
|
+
primary: "--takuhon-color-primary",
|
|
196
|
+
primaryContrast: "--takuhon-color-primary-contrast"
|
|
197
|
+
};
|
|
198
|
+
var SAFE_COLOR = /^(?:#[0-9A-Fa-f]{3,8}|[A-Za-z]+|(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color)\([A-Za-z0-9.,%/\s-]*\))$/;
|
|
199
|
+
var SAFE_FONT = /^[A-Za-z0-9\s,'"._-]+$/;
|
|
200
|
+
function safeValue(value, pattern, maxLength) {
|
|
201
|
+
if (typeof value !== "string") return void 0;
|
|
202
|
+
const v = value.trim();
|
|
203
|
+
if (v === "" || v.length > maxLength || !pattern.test(v)) return void 0;
|
|
204
|
+
return v;
|
|
205
|
+
}
|
|
206
|
+
var safeColor = (value) => safeValue(value, SAFE_COLOR, 64);
|
|
207
|
+
var safeFont = (value) => safeValue(value, SAFE_FONT, 256);
|
|
208
|
+
function colorOverrides(colors) {
|
|
209
|
+
if (!colors) return [];
|
|
210
|
+
const out = [];
|
|
211
|
+
for (const key of Object.keys(COLOR_TOKEN_VARS)) {
|
|
212
|
+
const safe = safeColor(colors[key]);
|
|
213
|
+
if (safe !== void 0) out.push([COLOR_TOKEN_VARS[key], safe]);
|
|
214
|
+
}
|
|
215
|
+
return out;
|
|
216
|
+
}
|
|
217
|
+
function rootBlock(pairs) {
|
|
218
|
+
return `:root{${[...pairs].map(([k, v]) => `${k}:${v}`).join(";")}}`;
|
|
219
|
+
}
|
|
220
|
+
function buildTokenCss(appearance) {
|
|
221
|
+
const light = new Map([
|
|
222
|
+
...Object.entries(INTERNAL_TOKENS),
|
|
223
|
+
...Object.entries(DEFAULT_TOKENS)
|
|
224
|
+
]);
|
|
225
|
+
if (appearance) {
|
|
226
|
+
const font = safeFont(appearance.fontFamily);
|
|
227
|
+
if (font !== void 0) light.set("--takuhon-font-family", font);
|
|
228
|
+
for (const [cssVar, value] of colorOverrides(appearance.colors)) {
|
|
229
|
+
light.set(cssVar, value);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
const dark = new Map(Object.entries(DEFAULT_TOKENS_DARK));
|
|
233
|
+
for (const [cssVar, value] of colorOverrides(appearance?.colorsDark)) {
|
|
234
|
+
dark.set(cssVar, value);
|
|
235
|
+
}
|
|
236
|
+
return `${rootBlock(light)}
|
|
237
|
+
@media (prefers-color-scheme:dark){${rootBlock(dark)}}`;
|
|
238
|
+
}
|
|
239
|
+
var CSS = `*{box-sizing:border-box}
|
|
240
|
+
html{font-size:100%}
|
|
241
|
+
body{margin:0;color:var(--takuhon-color-text);background:var(--takuhon-color-bg);font-family:var(--takuhon-font-family);font-size:var(--takuhon-font-size-base);line-height:var(--takuhon-line-height);-webkit-text-size-adjust:100%}
|
|
242
|
+
main{max-width:var(--takuhon-max-content-width);margin:0 auto;padding:var(--takuhon-space-6) var(--takuhon-space-4)}
|
|
243
|
+
a{color:var(--takuhon-color-primary)}
|
|
244
|
+
a:focus-visible{outline:2px solid var(--takuhon-color-accent);outline-offset:2px;border-radius:var(--takuhon-radius-sm)}
|
|
245
|
+
h1{font-size:var(--takuhon-font-size-2xl);font-weight:700;line-height:1.2;margin:0 0 var(--takuhon-space-2)}
|
|
246
|
+
h2{font-size:var(--takuhon-font-size-xl);margin:0 0 var(--takuhon-space-3);padding-bottom:var(--takuhon-space-2);border-bottom:1px solid var(--takuhon-color-border)}
|
|
247
|
+
h3{font-size:var(--takuhon-font-size-lg);font-weight:600;margin:0}
|
|
248
|
+
header{margin-bottom:var(--takuhon-space-6);display:flow-root}
|
|
249
|
+
header .avatar{width:96px;height:96px;border-radius:var(--takuhon-radius-full);object-fit:cover;float:left;margin:0 var(--takuhon-space-3) var(--takuhon-space-3) 0;shape-outside:circle();border:1px solid var(--takuhon-color-border)}
|
|
250
|
+
.tagline{font-size:var(--takuhon-font-size-lg);color:var(--takuhon-color-text-muted);margin:0 0 var(--takuhon-space-2)}
|
|
251
|
+
.location{font-size:var(--takuhon-font-size-sm);color:var(--takuhon-color-text-muted);margin:0}
|
|
252
|
+
.bio{margin:var(--takuhon-space-3) 0 0}
|
|
253
|
+
section{margin:0 0 var(--takuhon-space-6)}
|
|
104
254
|
ul{padding:0;margin:0;list-style:none}
|
|
105
|
-
.entries>li{margin:0 0
|
|
106
|
-
.
|
|
107
|
-
.
|
|
108
|
-
.
|
|
109
|
-
.
|
|
110
|
-
.
|
|
111
|
-
.
|
|
112
|
-
.
|
|
113
|
-
.
|
|
114
|
-
|
|
255
|
+
.entries>li{margin:0 0 var(--takuhon-space-4)}
|
|
256
|
+
.entries--timeline>li{position:relative;display:flex;flex-direction:column;margin:0 0 0 var(--takuhon-space-2);padding:0 0 var(--takuhon-space-5) var(--takuhon-space-4);border-left:2px solid var(--takuhon-color-border)}
|
|
257
|
+
.entries--timeline>li:last-child{padding-bottom:0}
|
|
258
|
+
.entries--timeline>li::before{content:"";position:absolute;left:-7px;top:6px;width:12px;height:12px;border-radius:var(--takuhon-radius-full);background:var(--takuhon-color-primary)}
|
|
259
|
+
.entries--timeline>li.is-current::before{background:var(--takuhon-color-accent)}
|
|
260
|
+
.entries--timeline .meta{order:-1;margin:0 0 var(--takuhon-space-1)}
|
|
261
|
+
.entries--cards{display:grid;gap:var(--takuhon-space-3)}
|
|
262
|
+
.entries--cards>li{margin:0;padding:var(--takuhon-space-4);border:1px solid var(--takuhon-color-border);border-radius:var(--takuhon-radius-md);background:var(--takuhon-color-surface)}
|
|
263
|
+
.entries--cards>li.is-highlighted{border-color:var(--takuhon-color-accent)}
|
|
264
|
+
.sub{margin:var(--takuhon-space-1) 0 0;font-weight:600}
|
|
265
|
+
.meta{margin:var(--takuhon-space-1) 0 0;color:var(--takuhon-color-text-muted);font-size:var(--takuhon-font-size-sm)}
|
|
266
|
+
.featured-links,.other-links{margin:0 0 var(--takuhon-space-6)}
|
|
267
|
+
.featured-links>ul,.other-links>ul{list-style:none;padding:0;margin:0;display:grid;gap:var(--takuhon-space-2)}
|
|
268
|
+
.featured-links>ul{grid-template-columns:repeat(auto-fill,minmax(220px,1fr))}
|
|
269
|
+
.featured-links a,.other-links a{display:flex;align-items:center;gap:var(--takuhon-space-2);min-height:var(--takuhon-tap-target);padding:var(--takuhon-space-2) var(--takuhon-space-3);background:var(--takuhon-color-surface);color:var(--takuhon-color-text);text-decoration:none;border:1px solid var(--takuhon-color-border);border-radius:var(--takuhon-radius-md)}
|
|
270
|
+
.featured-links a:hover,.other-links a:hover{border-color:var(--takuhon-color-accent)}
|
|
271
|
+
.link-main{display:inline-flex;align-items:center;gap:var(--takuhon-space-2);min-width:0}
|
|
272
|
+
.brand-icon{width:1.15em;height:1.15em;flex:none;opacity:.85}
|
|
273
|
+
.skills,.tags{display:flex;flex-wrap:wrap;gap:var(--takuhon-space-2)}
|
|
274
|
+
.skills>li,.tags>li{background:var(--takuhon-color-surface);border:1px solid var(--takuhon-color-border);border-radius:var(--takuhon-radius-full);padding:var(--takuhon-space-1) var(--takuhon-space-3);font-size:var(--takuhon-font-size-sm)}
|
|
275
|
+
.skills-groups{display:grid;gap:var(--takuhon-space-4)}
|
|
276
|
+
.skill-group h3{font-size:var(--takuhon-font-size-base);margin:0 0 var(--takuhon-space-2);color:var(--takuhon-color-text-muted);text-transform:uppercase;letter-spacing:.04em}
|
|
277
|
+
.rec{margin:0 0 var(--takuhon-space-4)}
|
|
278
|
+
.rec blockquote{margin:0;padding-left:var(--takuhon-space-3);border-left:3px solid var(--takuhon-color-border)}
|
|
279
|
+
.rec figcaption{color:var(--takuhon-color-text-muted);font-size:var(--takuhon-font-size-sm);margin-top:var(--takuhon-space-2)}
|
|
280
|
+
nav.locales{display:flex;gap:var(--takuhon-space-3);margin-bottom:var(--takuhon-space-4);font-size:var(--takuhon-font-size-sm)}
|
|
115
281
|
.activity svg{max-width:100%;height:auto}
|
|
116
|
-
footer.powered{max-width:
|
|
282
|
+
footer.powered{max-width:var(--takuhon-max-content-width);margin:0 auto;padding:var(--takuhon-space-5) var(--takuhon-space-4);color:var(--takuhon-color-text-muted);font-size:var(--takuhon-font-size-sm)}`;
|
|
117
283
|
function renderEntry(entry) {
|
|
118
284
|
const href = entry.url ? safeUrl(entry.url) : void 0;
|
|
119
285
|
const heading = href ? `<a href="${escapeHtml(href)}">${escapeHtml(entry.heading)}</a>` : escapeHtml(entry.heading);
|
|
@@ -126,11 +292,13 @@ function renderEntry(entry) {
|
|
|
126
292
|
`<ul class="tags">${entry.tags.map((t) => `<li>${escapeHtml(t)}</li>`).join("")}</ul>`
|
|
127
293
|
);
|
|
128
294
|
}
|
|
129
|
-
|
|
295
|
+
const cls = [entry.current ? "is-current" : "", entry.highlighted ? "is-highlighted" : ""].filter(Boolean).join(" ");
|
|
296
|
+
return `<li${cls ? ` class="${cls}"` : ""}>${parts.join("")}</li>`;
|
|
130
297
|
}
|
|
131
|
-
function entryList(title, entries) {
|
|
298
|
+
function entryList(title, entries, variant) {
|
|
132
299
|
if (entries.length === 0) return "";
|
|
133
|
-
|
|
300
|
+
const cls = variant ? `entries entries--${variant}` : "entries";
|
|
301
|
+
return `<section><h2>${escapeHtml(title)}</h2><ul class="${cls}">${entries.map(renderEntry).join("")}</ul></section>`;
|
|
134
302
|
}
|
|
135
303
|
function renderHeader(p) {
|
|
136
304
|
const parts = [];
|
|
@@ -146,19 +314,55 @@ function renderHeader(p) {
|
|
|
146
314
|
if (p.bio) parts.push(`<p class="bio">${escapeHtml(p.bio)}</p>`);
|
|
147
315
|
return `<header>${parts.join("")}</header>`;
|
|
148
316
|
}
|
|
317
|
+
function renderLinkItem(link) {
|
|
318
|
+
const label = escapeHtml(link.label ?? link.url);
|
|
319
|
+
const main = `<span class="link-main">${brandIconSvg(link.type)}<span>${label}</span></span>`;
|
|
320
|
+
const href = safeUrl(link.url);
|
|
321
|
+
return href ? `<li><a href="${escapeHtml(href)}" rel="me noopener">${main}</a></li>` : `<li>${main}</li>`;
|
|
322
|
+
}
|
|
323
|
+
function byOrder(a, b) {
|
|
324
|
+
return (a.order ?? 0) - (b.order ?? 0);
|
|
325
|
+
}
|
|
149
326
|
function renderLinks(links) {
|
|
150
327
|
if (links.length === 0) return "";
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
328
|
+
const featured = links.filter((l) => l.featured === true).sort(byOrder);
|
|
329
|
+
const others = links.filter((l) => l.featured !== true).sort(byOrder);
|
|
330
|
+
const group = (cls, ariaLabel, items) => items.length === 0 ? "" : `<nav class="${cls}" aria-label="${ariaLabel}"><ul>${items.map(renderLinkItem).join("")}</ul></nav>`;
|
|
331
|
+
return [
|
|
332
|
+
group("featured-links", "Featured links", featured),
|
|
333
|
+
group("other-links", "Links", others)
|
|
334
|
+
].filter(Boolean).join("\n");
|
|
157
335
|
}
|
|
158
|
-
function renderSkills(skills) {
|
|
336
|
+
function renderSkills(skills, categories) {
|
|
159
337
|
if (skills.length === 0) return "";
|
|
160
|
-
const
|
|
161
|
-
|
|
338
|
+
const chips = (list) => `<ul class="skills">${list.map((s) => `<li>${escapeHtml(s.label)}</li>`).join("")}</ul>`;
|
|
339
|
+
if (!categories || categories.length === 0) {
|
|
340
|
+
return `<section><h2>Skills</h2>${chips(skills)}</section>`;
|
|
341
|
+
}
|
|
342
|
+
const UNCAT = "";
|
|
343
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
344
|
+
for (const s of skills) {
|
|
345
|
+
const key = s.category ?? UNCAT;
|
|
346
|
+
const arr = buckets.get(key) ?? [];
|
|
347
|
+
arr.push(s);
|
|
348
|
+
buckets.set(key, arr);
|
|
349
|
+
}
|
|
350
|
+
const group = (heading, list) => `<div class="skill-group">${heading !== void 0 ? `<h3>${escapeHtml(heading)}</h3>` : ""}${chips(list)}</div>`;
|
|
351
|
+
const seen = /* @__PURE__ */ new Set();
|
|
352
|
+
const groups = [];
|
|
353
|
+
for (const cat of categories) {
|
|
354
|
+
const list = buckets.get(cat.id);
|
|
355
|
+
if (!list || list.length === 0) continue;
|
|
356
|
+
seen.add(cat.id);
|
|
357
|
+
groups.push(group(cat.label, list));
|
|
358
|
+
}
|
|
359
|
+
for (const [key, list] of buckets) {
|
|
360
|
+
if (key === UNCAT || seen.has(key)) continue;
|
|
361
|
+
groups.push(group(key, list));
|
|
362
|
+
}
|
|
363
|
+
const uncategorized = buckets.get(UNCAT);
|
|
364
|
+
if (uncategorized && uncategorized.length > 0) groups.push(group(void 0, uncategorized));
|
|
365
|
+
return `<section><h2>Skills</h2><div class="skills-groups">${groups.join("")}</div></section>`;
|
|
162
366
|
}
|
|
163
367
|
function renderLanguages(languages) {
|
|
164
368
|
if (languages.length === 0) return "";
|
|
@@ -220,7 +424,9 @@ function renderProfileHtml(input) {
|
|
|
220
424
|
(a) => `<link rel="alternate" hreflang="${escapeHtml(a.hreflang)}" href="${escapeHtml(a.href)}">`
|
|
221
425
|
),
|
|
222
426
|
input.jsonLd ? renderJsonLdScript(d) : "",
|
|
223
|
-
|
|
427
|
+
input.contact ? '<link rel="stylesheet" href="/contact-widget.css">' : "",
|
|
428
|
+
`<style>${buildTokenCss(d.settings.appearance)}
|
|
429
|
+
${CSS}</style>`
|
|
224
430
|
].filter(Boolean).join("\n ");
|
|
225
431
|
const body = [
|
|
226
432
|
input.localeNav.length > 1 ? renderLocaleNav(input.localeNav) : "",
|
|
@@ -237,8 +443,10 @@ function renderProfileHtml(input) {
|
|
|
237
443
|
locale: d.resolvedLocale
|
|
238
444
|
}),
|
|
239
445
|
body: c.description,
|
|
240
|
-
url: c.url
|
|
241
|
-
|
|
446
|
+
url: c.url,
|
|
447
|
+
current: c.isCurrent
|
|
448
|
+
})),
|
|
449
|
+
"timeline"
|
|
242
450
|
),
|
|
243
451
|
entryList(
|
|
244
452
|
"Projects",
|
|
@@ -247,10 +455,12 @@ function renderProfileHtml(input) {
|
|
|
247
455
|
dates: dateRange(x.startDate, { end: x.endDate, locale: d.resolvedLocale }),
|
|
248
456
|
body: x.description,
|
|
249
457
|
url: x.url,
|
|
250
|
-
tags: x.tags
|
|
251
|
-
|
|
458
|
+
tags: x.tags,
|
|
459
|
+
highlighted: x.highlighted
|
|
460
|
+
})),
|
|
461
|
+
"cards"
|
|
252
462
|
),
|
|
253
|
-
renderSkills(d.skills),
|
|
463
|
+
renderSkills(d.skills, d.settings.skillCategories),
|
|
254
464
|
renderActivity(input.activitySnapshot),
|
|
255
465
|
entryList(
|
|
256
466
|
"Education",
|
|
@@ -360,6 +570,7 @@ function renderProfileHtml(input) {
|
|
|
360
570
|
renderContact(d.contact)
|
|
361
571
|
].filter(Boolean).join("\n");
|
|
362
572
|
const footer = d.settings.showPoweredBy === true ? '<footer class="powered">Powered by takuhon</footer>' : "";
|
|
573
|
+
const contactScript = input.contact ? `<script src="/contact-widget.js" data-site-key="${escapeHtml(input.contact.siteKey)}"` + (input.contact.endpoint ? ` data-endpoint="${escapeHtml(input.contact.endpoint)}"` : "") + " defer></script>\n" : "";
|
|
363
574
|
return `<!DOCTYPE html>
|
|
364
575
|
<html lang="${escapeHtml(d.resolvedLocale)}">
|
|
365
576
|
<head>
|
|
@@ -370,7 +581,7 @@ function renderProfileHtml(input) {
|
|
|
370
581
|
${body}
|
|
371
582
|
</main>
|
|
372
583
|
${footer ? `${footer}
|
|
373
|
-
` : ""}</body>
|
|
584
|
+
` : ""}${contactScript}</body>
|
|
374
585
|
</html>
|
|
375
586
|
`;
|
|
376
587
|
}
|
|
@@ -494,21 +705,27 @@ function pathLocaleFromUrl(url) {
|
|
|
494
705
|
|
|
495
706
|
// src/public-app.ts
|
|
496
707
|
var FALLBACK_VERSION = "bundled-fixture";
|
|
497
|
-
var
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
708
|
+
var TURNSTILE_ORIGIN = "https://challenges.cloudflare.com";
|
|
709
|
+
function buildPublicCsp(contact) {
|
|
710
|
+
return [
|
|
711
|
+
"default-src 'self'",
|
|
712
|
+
// `https:` lets the server-rendered profile page load remote avatar images
|
|
713
|
+
// (the schema permits any https avatar URL, and `safeUrl` in the renderer
|
|
714
|
+
// already blocks non-http(s) schemes); `data:` covers inline placeholders.
|
|
715
|
+
"img-src 'self' https: data:",
|
|
716
|
+
"style-src 'self' 'unsafe-inline'",
|
|
717
|
+
contact ? `script-src 'self' ${TURNSTILE_ORIGIN}` : "script-src 'self'",
|
|
718
|
+
"font-src 'self'",
|
|
719
|
+
contact ? `connect-src 'self' ${TURNSTILE_ORIGIN}` : "connect-src 'self'",
|
|
720
|
+
...contact ? [`frame-src ${TURNSTILE_ORIGIN}`] : [],
|
|
721
|
+
"frame-ancestors 'none'",
|
|
722
|
+
"base-uri 'self'",
|
|
723
|
+
"form-action 'self'",
|
|
724
|
+
"upgrade-insecure-requests"
|
|
725
|
+
].join("; ");
|
|
726
|
+
}
|
|
727
|
+
var PUBLIC_CSP = buildPublicCsp(false);
|
|
728
|
+
var PUBLIC_CSP_WITH_CONTACT = buildPublicCsp(true);
|
|
512
729
|
async function loadProfile(deps) {
|
|
513
730
|
try {
|
|
514
731
|
return await deps.storage.getProfile();
|
|
@@ -529,7 +746,10 @@ function createPublicApp(deps) {
|
|
|
529
746
|
h.set("x-frame-options", "DENY");
|
|
530
747
|
h.set("referrer-policy", "strict-origin-when-cross-origin");
|
|
531
748
|
h.set("permissions-policy", "camera=(), microphone=(), geolocation=(), interest-cohort=()");
|
|
532
|
-
h.set(
|
|
749
|
+
h.set(
|
|
750
|
+
"content-security-policy",
|
|
751
|
+
c.get("contactEnabled") ? PUBLIC_CSP_WITH_CONTACT : PUBLIC_CSP
|
|
752
|
+
);
|
|
533
753
|
h.set("access-control-allow-origin", "*");
|
|
534
754
|
h.set("access-control-expose-headers", "ETag");
|
|
535
755
|
});
|
|
@@ -565,6 +785,13 @@ function createPublicApp(deps) {
|
|
|
565
785
|
const origin = new URL(c.req.url).origin;
|
|
566
786
|
const localePath = (l) => l === defaultLocale ? "/" : `/${l}/`;
|
|
567
787
|
const snapshot = profile.settings.activity?.enabled === true && deps.activityStorage ? await deps.activityStorage.getActivitySnapshot() : null;
|
|
788
|
+
const contactSettings = profile.settings.contact;
|
|
789
|
+
const contactSiteKey = contactSettings?.turnstileSiteKey?.trim();
|
|
790
|
+
const contact = contactSettings?.enabled === true && contactSiteKey ? {
|
|
791
|
+
siteKey: contactSiteKey,
|
|
792
|
+
...contactSettings.endpoint ? { endpoint: contactSettings.endpoint } : {}
|
|
793
|
+
} : void 0;
|
|
794
|
+
if (contact) c.set("contactEnabled", true);
|
|
568
795
|
const html = renderProfileHtml({
|
|
569
796
|
localized,
|
|
570
797
|
canonicalUrl: `${origin}${localePath(current)}`,
|
|
@@ -574,7 +801,8 @@ function createPublicApp(deps) {
|
|
|
574
801
|
],
|
|
575
802
|
localeNav: locales.map((l) => ({ locale: l, href: localePath(l), current: l === current })),
|
|
576
803
|
jsonLd: profile.settings.enableJsonLd !== false,
|
|
577
|
-
activitySnapshot: snapshot ?? void 0
|
|
804
|
+
activitySnapshot: snapshot ?? void 0,
|
|
805
|
+
contact
|
|
578
806
|
});
|
|
579
807
|
c.header("etag", `"${version}"`);
|
|
580
808
|
c.header("cache-control", "public, max-age=300");
|