@anglefeint/astro-theme 0.2.0 → 0.2.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.
- package/package.json +1 -1
- package/src/cli-new-post.mjs +1 -1
- package/src/components/BaseHead.astro +2 -1
- package/src/components/Giscus.astro +5 -1
- package/src/components/shared/CommonHeader.astro +6 -1
- package/src/i18n/config.ts +2 -1
- package/src/i18n/messages.ts +10 -10
- package/src/layouts/BlogPost.astro +15 -6
- package/src/scripts/about/modals.js +56 -2
- package/src/scripts/about/reading-ui.js +7 -6
- package/src/scripts/blogpost/read-progress.js +3 -3
package/package.json
CHANGED
package/src/cli-new-post.mjs
CHANGED
|
@@ -135,7 +135,7 @@ function extractThemeConfigObject(configSource) {
|
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
function localeKeyFromToken(key) {
|
|
138
|
-
return /^[a-z]{2,3}(?:-[a-z0-9]+)?$/i.test(key) ? key
|
|
138
|
+
return /^[a-z]{2,3}(?:-[a-z0-9]+)?$/i.test(key) ? key : '';
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
function localeObjectIsEnabled(localeObjectSource) {
|
|
@@ -107,6 +107,7 @@ const articleSchema = isArticle
|
|
|
107
107
|
|
|
108
108
|
const extraSchemas = schema ? (Array.isArray(schema) ? schema : [schema]) : [];
|
|
109
109
|
const jsonLdSchemas = [websiteSchema, personSchema, articleSchema, ...extraSchemas].filter(Boolean);
|
|
110
|
+
const serializeJsonLd = (value: unknown) => JSON.stringify(value).replace(/</g, '\\u003c');
|
|
110
111
|
---
|
|
111
112
|
|
|
112
113
|
<!-- Global Metadata -->
|
|
@@ -178,6 +179,6 @@ const jsonLdSchemas = [websiteSchema, personSchema, articleSchema, ...extraSchem
|
|
|
178
179
|
|
|
179
180
|
{
|
|
180
181
|
jsonLdSchemas.map((schemaItem) => (
|
|
181
|
-
<script is:inline type="application/ld+json" set:html={
|
|
182
|
+
<script is:inline type="application/ld+json" set:html={serializeJsonLd(schemaItem)} />
|
|
182
183
|
))
|
|
183
184
|
}
|
|
@@ -25,6 +25,10 @@ type Props = {
|
|
|
25
25
|
const { comments, resolvedLocale } = Astro.props;
|
|
26
26
|
const normalizedCommentTerm = comments.TERM.trim();
|
|
27
27
|
const normalizedCommentNumber = comments.NUMBER.trim();
|
|
28
|
+
const GISCUS_LOCALE_MAP: Record<string, string> = {
|
|
29
|
+
zh: 'zh-CN',
|
|
30
|
+
};
|
|
31
|
+
const giscusLocale = comments.LANG || GISCUS_LOCALE_MAP[resolvedLocale] || resolvedLocale;
|
|
28
32
|
---
|
|
29
33
|
|
|
30
34
|
<script
|
|
@@ -42,7 +46,7 @@ const normalizedCommentNumber = comments.NUMBER.trim();
|
|
|
42
46
|
data-emit-metadata={comments.EMIT_METADATA}
|
|
43
47
|
data-input-position={comments.INPUT_POSITION}
|
|
44
48
|
data-theme={comments.THEME}
|
|
45
|
-
data-lang={
|
|
49
|
+
data-lang={giscusLocale}
|
|
46
50
|
data-loading={comments.LOADING}
|
|
47
51
|
crossorigin={comments.CROSSORIGIN}
|
|
48
52
|
async></script>
|
|
@@ -80,7 +80,9 @@ const localeOptions = SUPPORTED_LOCALES.map((targetLocale) => ({
|
|
|
80
80
|
<span class="nav-status-dot" aria-hidden="true"></span>
|
|
81
81
|
<span class="nav-status-text">{labels.status}</span>
|
|
82
82
|
</div>
|
|
83
|
-
<
|
|
83
|
+
<div class="social-menu-wrap">
|
|
84
|
+
<SocialMenu links={SOCIAL_LINKS} />
|
|
85
|
+
</div>
|
|
84
86
|
</div>
|
|
85
87
|
</div>
|
|
86
88
|
</nav>
|
|
@@ -244,6 +246,9 @@ const localeOptions = SUPPORTED_LOCALES.map((targetLocale) => ({
|
|
|
244
246
|
display: none !important;
|
|
245
247
|
}
|
|
246
248
|
.social-links {
|
|
249
|
+
display: flex;
|
|
250
|
+
}
|
|
251
|
+
.social-menu-wrap {
|
|
247
252
|
display: none;
|
|
248
253
|
}
|
|
249
254
|
.lang-switcher {
|
package/src/i18n/config.ts
CHANGED
|
@@ -32,7 +32,8 @@ export function localePath(locale: string, path = '/'): string {
|
|
|
32
32
|
|
|
33
33
|
export function stripLocaleFromPath(pathname: string, locale: string): string {
|
|
34
34
|
const prefix = `/${locale}`;
|
|
35
|
-
if (
|
|
35
|
+
if (pathname === prefix) return '/';
|
|
36
|
+
if (!pathname.startsWith(`${prefix}/`)) return pathname;
|
|
36
37
|
const withoutLocale = pathname.slice(prefix.length);
|
|
37
38
|
return withoutLocale || '/';
|
|
38
39
|
}
|
package/src/i18n/messages.ts
CHANGED
|
@@ -96,7 +96,7 @@ export const DEFAULT_MESSAGES: Record<string, Messages> = {
|
|
|
96
96
|
ethos: 'Hacker Ethos',
|
|
97
97
|
now: 'Now',
|
|
98
98
|
contact: 'Contact',
|
|
99
|
-
regenerate: '
|
|
99
|
+
regenerate: 'Replay scan',
|
|
100
100
|
},
|
|
101
101
|
blog: {
|
|
102
102
|
title: 'Blog',
|
|
@@ -130,7 +130,7 @@ export const DEFAULT_MESSAGES: Record<string, Messages> = {
|
|
|
130
130
|
heroMonitor: 'neural monitor',
|
|
131
131
|
heroSignalSync: 'signal sync active',
|
|
132
132
|
heroModelOnline: 'model online',
|
|
133
|
-
regenerate: '
|
|
133
|
+
regenerate: 'Replay scan',
|
|
134
134
|
relatedAria: 'Related posts',
|
|
135
135
|
backToBlogAria: 'Back to blog',
|
|
136
136
|
paginationAria: 'Pagination',
|
|
@@ -165,7 +165,7 @@ export const DEFAULT_MESSAGES: Record<string, Messages> = {
|
|
|
165
165
|
ethos: 'ハッカー精神',
|
|
166
166
|
now: '現在',
|
|
167
167
|
contact: '連絡先',
|
|
168
|
-
regenerate: '
|
|
168
|
+
regenerate: 'スキャン再生',
|
|
169
169
|
},
|
|
170
170
|
blog: {
|
|
171
171
|
title: 'ブログ',
|
|
@@ -199,7 +199,7 @@ export const DEFAULT_MESSAGES: Record<string, Messages> = {
|
|
|
199
199
|
heroMonitor: 'ニューラルモニター',
|
|
200
200
|
heroSignalSync: 'シグナル同期中',
|
|
201
201
|
heroModelOnline: 'モデルオンライン',
|
|
202
|
-
regenerate: '
|
|
202
|
+
regenerate: 'スキャン再生',
|
|
203
203
|
relatedAria: '関連記事',
|
|
204
204
|
backToBlogAria: 'ブログへ戻る',
|
|
205
205
|
paginationAria: 'ページネーション',
|
|
@@ -234,7 +234,7 @@ export const DEFAULT_MESSAGES: Record<string, Messages> = {
|
|
|
234
234
|
ethos: '해커 정신',
|
|
235
235
|
now: '지금',
|
|
236
236
|
contact: '연락처',
|
|
237
|
-
regenerate: '
|
|
237
|
+
regenerate: '스캔 재생',
|
|
238
238
|
},
|
|
239
239
|
blog: {
|
|
240
240
|
title: '블로그',
|
|
@@ -268,7 +268,7 @@ export const DEFAULT_MESSAGES: Record<string, Messages> = {
|
|
|
268
268
|
heroMonitor: '뉴럴 모니터',
|
|
269
269
|
heroSignalSync: '신호 동기화 활성',
|
|
270
270
|
heroModelOnline: '모델 온라인',
|
|
271
|
-
regenerate: '
|
|
271
|
+
regenerate: '스캔 재생',
|
|
272
272
|
relatedAria: '관련 글',
|
|
273
273
|
backToBlogAria: '블로그로 돌아가기',
|
|
274
274
|
paginationAria: '페이지네이션',
|
|
@@ -303,7 +303,7 @@ export const DEFAULT_MESSAGES: Record<string, Messages> = {
|
|
|
303
303
|
ethos: 'Ethos hacker',
|
|
304
304
|
now: 'Ahora',
|
|
305
305
|
contact: 'Contacto',
|
|
306
|
-
regenerate: '
|
|
306
|
+
regenerate: 'Repetir escaneo',
|
|
307
307
|
},
|
|
308
308
|
blog: {
|
|
309
309
|
title: 'Blog',
|
|
@@ -338,7 +338,7 @@ export const DEFAULT_MESSAGES: Record<string, Messages> = {
|
|
|
338
338
|
heroMonitor: 'monitor neural',
|
|
339
339
|
heroSignalSync: 'sincronización de señal activa',
|
|
340
340
|
heroModelOnline: 'modelo en línea',
|
|
341
|
-
regenerate: '
|
|
341
|
+
regenerate: 'Repetir escaneo',
|
|
342
342
|
relatedAria: 'Publicaciones relacionadas',
|
|
343
343
|
backToBlogAria: 'Volver al blog',
|
|
344
344
|
paginationAria: 'Paginación',
|
|
@@ -373,7 +373,7 @@ export const DEFAULT_MESSAGES: Record<string, Messages> = {
|
|
|
373
373
|
ethos: '黑客精神',
|
|
374
374
|
now: '现在',
|
|
375
375
|
contact: '联系',
|
|
376
|
-
regenerate: '
|
|
376
|
+
regenerate: '重播扫描',
|
|
377
377
|
},
|
|
378
378
|
blog: {
|
|
379
379
|
title: '博客',
|
|
@@ -407,7 +407,7 @@ export const DEFAULT_MESSAGES: Record<string, Messages> = {
|
|
|
407
407
|
heroMonitor: '神经监视器',
|
|
408
408
|
heroSignalSync: '信号同步中',
|
|
409
409
|
heroModelOnline: '模型在线',
|
|
410
|
-
regenerate: '
|
|
410
|
+
regenerate: '重播扫描',
|
|
411
411
|
relatedAria: '相关文章',
|
|
412
412
|
backToBlogAria: '返回博客',
|
|
413
413
|
paginationAria: '分页导航',
|
|
@@ -22,6 +22,8 @@ type Props = CollectionEntry<'blog'>['data'] & {
|
|
|
22
22
|
locale?: string;
|
|
23
23
|
related?: CollectionEntry<'blog'>[];
|
|
24
24
|
localeHrefs?: Partial<Record<Locale, string>>;
|
|
25
|
+
aiLatencyEstimated?: boolean;
|
|
26
|
+
aiConfidenceEstimated?: boolean;
|
|
25
27
|
};
|
|
26
28
|
|
|
27
29
|
const {
|
|
@@ -37,7 +39,9 @@ const {
|
|
|
37
39
|
aiMode,
|
|
38
40
|
aiState,
|
|
39
41
|
aiLatencyMs,
|
|
42
|
+
aiLatencyEstimated = false,
|
|
40
43
|
aiConfidence,
|
|
44
|
+
aiConfidenceEstimated = false,
|
|
41
45
|
wordCount,
|
|
42
46
|
tokenCount,
|
|
43
47
|
author,
|
|
@@ -169,7 +173,7 @@ const hasCommentsConfig = Boolean(
|
|
|
169
173
|
<Image
|
|
170
174
|
class="hero-base-image"
|
|
171
175
|
src={heroImage}
|
|
172
|
-
alt=
|
|
176
|
+
alt=""
|
|
173
177
|
loading="eager"
|
|
174
178
|
decoding="async"
|
|
175
179
|
fetchpriority="high"
|
|
@@ -257,12 +261,14 @@ const hasCommentsConfig = Boolean(
|
|
|
257
261
|
<div class="ai-response-meta">
|
|
258
262
|
{aiLatencyMs !== undefined && (
|
|
259
263
|
<span>
|
|
260
|
-
{messages.blog.latencyLabel}
|
|
264
|
+
{messages.blog.latencyLabel}{' '}
|
|
265
|
+
<strong>{aiLatencyEstimated ? `~${aiLatencyMs}` : aiLatencyMs}</strong> ms
|
|
261
266
|
</span>
|
|
262
267
|
)}
|
|
263
268
|
{confidenceText !== undefined && (
|
|
264
269
|
<span>
|
|
265
|
-
{messages.blog.confidenceLabel}
|
|
270
|
+
{messages.blog.confidenceLabel}{' '}
|
|
271
|
+
<strong>{aiConfidenceEstimated ? `~${confidenceText}` : confidenceText}</strong>
|
|
266
272
|
</span>
|
|
267
273
|
)}
|
|
268
274
|
</div>
|
|
@@ -293,8 +299,11 @@ const hasCommentsConfig = Boolean(
|
|
|
293
299
|
</div>
|
|
294
300
|
)
|
|
295
301
|
}
|
|
296
|
-
<button
|
|
297
|
-
|
|
302
|
+
<button
|
|
303
|
+
type="button"
|
|
304
|
+
class="ai-regenerate"
|
|
305
|
+
aria-label={messages.blog.regenerate}
|
|
306
|
+
title={messages.blog.regenerate}>{messages.blog.regenerate}</button
|
|
298
307
|
>
|
|
299
308
|
</div>
|
|
300
309
|
</article>
|
|
@@ -310,7 +319,7 @@ const hasCommentsConfig = Boolean(
|
|
|
310
319
|
>
|
|
311
320
|
{p.data.heroImage ? (
|
|
312
321
|
<div class="ai-related-img">
|
|
313
|
-
<Image width={320} height={180} src={p.data.heroImage} alt=
|
|
322
|
+
<Image width={320} height={180} src={p.data.heroImage} alt="" />
|
|
314
323
|
</div>
|
|
315
324
|
) : (
|
|
316
325
|
<div class="ai-related-placeholder">
|
|
@@ -10,7 +10,19 @@ export function initAboutModals(runtimeConfig, prefersReducedMotion) {
|
|
|
10
10
|
const modalOverlay = document.getElementById('hacker-modal');
|
|
11
11
|
const modalBody = document.getElementById('hacker-modal-body');
|
|
12
12
|
const modalTitle = document.querySelector('.hacker-modal-title');
|
|
13
|
+
const closeButton = document.querySelector('.hacker-modal-close');
|
|
13
14
|
if (!modalOverlay || !modalBody || !modalTitle) return;
|
|
15
|
+
let lastFocusedElement = null;
|
|
16
|
+
|
|
17
|
+
function getFocusableElements() {
|
|
18
|
+
return Array.from(
|
|
19
|
+
modalOverlay.querySelectorAll(
|
|
20
|
+
'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
|
|
21
|
+
)
|
|
22
|
+
).filter(
|
|
23
|
+
(element) => !element.hasAttribute('hidden') && element.getAttribute('aria-hidden') !== 'true'
|
|
24
|
+
);
|
|
25
|
+
}
|
|
14
26
|
|
|
15
27
|
const decryptorKeysLabel =
|
|
16
28
|
typeof runtimeConfig.decryptorKeysLabel === 'string' ? runtimeConfig.decryptorKeysLabel : '';
|
|
@@ -36,10 +48,15 @@ export function initAboutModals(runtimeConfig, prefersReducedMotion) {
|
|
|
36
48
|
if (modalEl) modalEl.classList.remove('hacker-modal-wide');
|
|
37
49
|
modalOverlay.classList.remove('open');
|
|
38
50
|
modalOverlay.setAttribute('aria-hidden', 'true');
|
|
51
|
+
if (lastFocusedElement && typeof lastFocusedElement.focus === 'function') {
|
|
52
|
+
lastFocusedElement.focus();
|
|
53
|
+
}
|
|
39
54
|
};
|
|
40
55
|
|
|
41
56
|
const openModal = (data) => {
|
|
42
57
|
if (!data) return;
|
|
58
|
+
lastFocusedElement =
|
|
59
|
+
document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
43
60
|
|
|
44
61
|
decryptor.stop();
|
|
45
62
|
if (cleanupKeyboard) {
|
|
@@ -84,6 +101,13 @@ export function initAboutModals(runtimeConfig, prefersReducedMotion) {
|
|
|
84
101
|
|
|
85
102
|
modalOverlay.classList.add('open');
|
|
86
103
|
modalOverlay.setAttribute('aria-hidden', 'false');
|
|
104
|
+
window.requestAnimationFrame(() => {
|
|
105
|
+
const focusables = getFocusableElements();
|
|
106
|
+
const nextFocus = focusables[0] || closeButton || modalOverlay;
|
|
107
|
+
if (nextFocus && typeof nextFocus.focus === 'function') {
|
|
108
|
+
nextFocus.focus();
|
|
109
|
+
}
|
|
110
|
+
});
|
|
87
111
|
};
|
|
88
112
|
|
|
89
113
|
const folderButtons = Array.from(document.querySelectorAll('.hacker-folder[data-modal]'));
|
|
@@ -97,7 +121,6 @@ export function initAboutModals(runtimeConfig, prefersReducedMotion) {
|
|
|
97
121
|
return [button, onClick];
|
|
98
122
|
});
|
|
99
123
|
|
|
100
|
-
const closeButton = document.querySelector('.hacker-modal-close');
|
|
101
124
|
if (closeButton) closeButton.addEventListener('click', closeModal);
|
|
102
125
|
|
|
103
126
|
const onOverlayClick = (event) => {
|
|
@@ -106,7 +129,38 @@ export function initAboutModals(runtimeConfig, prefersReducedMotion) {
|
|
|
106
129
|
modalOverlay.addEventListener('click', onOverlayClick);
|
|
107
130
|
|
|
108
131
|
const onDocumentKeydown = (event) => {
|
|
109
|
-
if (
|
|
132
|
+
if (!modalOverlay.classList.contains('open')) return;
|
|
133
|
+
if (event.key === 'Escape') {
|
|
134
|
+
closeModal();
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (event.key !== 'Tab') return;
|
|
138
|
+
|
|
139
|
+
const focusables = getFocusableElements();
|
|
140
|
+
if (focusables.length === 0) {
|
|
141
|
+
event.preventDefault();
|
|
142
|
+
if (closeButton && typeof closeButton.focus === 'function') {
|
|
143
|
+
closeButton.focus();
|
|
144
|
+
}
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const currentIndex = focusables.indexOf(document.activeElement);
|
|
149
|
+
const first = focusables[0];
|
|
150
|
+
const last = focusables[focusables.length - 1];
|
|
151
|
+
|
|
152
|
+
if (event.shiftKey) {
|
|
153
|
+
if (document.activeElement === first || currentIndex === -1) {
|
|
154
|
+
event.preventDefault();
|
|
155
|
+
last.focus();
|
|
156
|
+
}
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (document.activeElement === last) {
|
|
161
|
+
event.preventDefault();
|
|
162
|
+
first.focus();
|
|
163
|
+
}
|
|
110
164
|
};
|
|
111
165
|
document.addEventListener('keydown', onDocumentKeydown);
|
|
112
166
|
|
|
@@ -6,9 +6,10 @@ export function initAboutReadingUi(runtimeConfig, prefersReducedMotion) {
|
|
|
6
6
|
p60: 'inference stable',
|
|
7
7
|
p90: 'output finalized',
|
|
8
8
|
};
|
|
9
|
-
var toastMessages =
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
var toastMessages =
|
|
10
|
+
runtimeConfig.effects && runtimeConfig.effects.scrollToasts
|
|
11
|
+
? runtimeConfig.effects.scrollToasts
|
|
12
|
+
: fallbackToasts;
|
|
12
13
|
var stageSeen = { p30: false, p60: false, p90: false };
|
|
13
14
|
var toastTimer = 0;
|
|
14
15
|
var hasScrolled = false;
|
|
@@ -18,9 +19,9 @@ export function initAboutReadingUi(runtimeConfig, prefersReducedMotion) {
|
|
|
18
19
|
toast.textContent = '> ' + msg;
|
|
19
20
|
toast.classList.add('visible');
|
|
20
21
|
clearTimeout(toastTimer);
|
|
21
|
-
toastTimer = setTimeout(function() {
|
|
22
|
+
toastTimer = setTimeout(function () {
|
|
22
23
|
toast.classList.remove('visible');
|
|
23
|
-
},
|
|
24
|
+
}, 1800);
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
if (progress) {
|
|
@@ -53,7 +54,7 @@ export function initAboutReadingUi(runtimeConfig, prefersReducedMotion) {
|
|
|
53
54
|
|
|
54
55
|
var backTop = document.querySelector('.hacker-back-to-top');
|
|
55
56
|
if (backTop) {
|
|
56
|
-
backTop.addEventListener('click', function() {
|
|
57
|
+
backTop.addEventListener('click', function () {
|
|
57
58
|
window.scrollTo({ top: 0, behavior: prefersReducedMotion ? 'auto' : 'smooth' });
|
|
58
59
|
});
|
|
59
60
|
}
|
|
@@ -23,9 +23,9 @@ export function initReadProgressAndBackToTop(prefersReducedMotion) {
|
|
|
23
23
|
toast.textContent = msg;
|
|
24
24
|
toast.classList.add('visible');
|
|
25
25
|
clearTimeout(toastTimer);
|
|
26
|
-
toastTimer = setTimeout(function() {
|
|
26
|
+
toastTimer = setTimeout(function () {
|
|
27
27
|
toast.classList.remove('visible');
|
|
28
|
-
},
|
|
28
|
+
}, 1800);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
if (progress) {
|
|
@@ -62,7 +62,7 @@ export function initReadProgressAndBackToTop(prefersReducedMotion) {
|
|
|
62
62
|
|
|
63
63
|
var backTop = document.querySelector('.ai-back-to-top');
|
|
64
64
|
if (backTop) {
|
|
65
|
-
backTop.addEventListener('click', function() {
|
|
65
|
+
backTop.addEventListener('click', function () {
|
|
66
66
|
window.scrollTo({ top: 0, behavior: prefersReducedMotion ? 'auto' : 'smooth' });
|
|
67
67
|
});
|
|
68
68
|
}
|