@anglefeint/astro-theme 0.1.39 → 0.1.40
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
CHANGED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
type GiscusComments = {
|
|
3
|
+
REPO: string;
|
|
4
|
+
REPO_ID: string;
|
|
5
|
+
CATEGORY: string;
|
|
6
|
+
CATEGORY_ID: string;
|
|
7
|
+
MAPPING: string;
|
|
8
|
+
TERM: string;
|
|
9
|
+
NUMBER: string;
|
|
10
|
+
STRICT: string;
|
|
11
|
+
REACTIONS_ENABLED: string;
|
|
12
|
+
EMIT_METADATA: string;
|
|
13
|
+
INPUT_POSITION: string;
|
|
14
|
+
THEME: string;
|
|
15
|
+
LANG: string;
|
|
16
|
+
LOADING: string;
|
|
17
|
+
CROSSORIGIN: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type Props = {
|
|
21
|
+
comments: GiscusComments;
|
|
22
|
+
resolvedLocale: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const { comments, resolvedLocale } = Astro.props;
|
|
26
|
+
const normalizedCommentTerm = comments.TERM.trim();
|
|
27
|
+
const normalizedCommentNumber = comments.NUMBER.trim();
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
<script
|
|
31
|
+
is:inline
|
|
32
|
+
src="https://giscus.app/client.js"
|
|
33
|
+
data-repo={comments.REPO}
|
|
34
|
+
data-repo-id={comments.REPO_ID}
|
|
35
|
+
data-category={comments.CATEGORY}
|
|
36
|
+
data-category-id={comments.CATEGORY_ID}
|
|
37
|
+
data-mapping={comments.MAPPING}
|
|
38
|
+
data-term={comments.MAPPING === 'specific' ? normalizedCommentTerm : undefined}
|
|
39
|
+
data-number={comments.MAPPING === 'number' ? normalizedCommentNumber : undefined}
|
|
40
|
+
data-strict={comments.STRICT}
|
|
41
|
+
data-reactions-enabled={comments.REACTIONS_ENABLED}
|
|
42
|
+
data-emit-metadata={comments.EMIT_METADATA}
|
|
43
|
+
data-input-position={comments.INPUT_POSITION}
|
|
44
|
+
data-theme={comments.THEME}
|
|
45
|
+
data-lang={comments.LANG || resolvedLocale}
|
|
46
|
+
data-loading={comments.LOADING}
|
|
47
|
+
crossorigin={comments.CROSSORIGIN}
|
|
48
|
+
async></script>
|
package/src/content-schema.ts
CHANGED
|
@@ -2,33 +2,58 @@ import { defineCollection } from 'astro:content';
|
|
|
2
2
|
import { glob } from 'astro/loaders';
|
|
3
3
|
import { z } from 'astro/zod';
|
|
4
4
|
|
|
5
|
+
const ABSOLUTE_URL_SCHEME_REGEX = /^[a-z][a-z\d+.-]*:/i;
|
|
6
|
+
|
|
7
|
+
function normalizeSourceLink(value: string): string {
|
|
8
|
+
const trimmed = value.trim();
|
|
9
|
+
return ABSOLUTE_URL_SCHEME_REGEX.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function isValidSourceLink(value: string): boolean {
|
|
13
|
+
try {
|
|
14
|
+
const url = new URL(value);
|
|
15
|
+
return url.protocol === 'http:' || url.protocol === 'https:';
|
|
16
|
+
} catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const sourceLinkSchema = z
|
|
22
|
+
.string()
|
|
23
|
+
.trim()
|
|
24
|
+
.min(1)
|
|
25
|
+
.transform(normalizeSourceLink)
|
|
26
|
+
.refine(isValidSourceLink, {
|
|
27
|
+
message: 'sourceLinks entries must be valid HTTP(S) URLs or bare domains.',
|
|
28
|
+
});
|
|
29
|
+
|
|
5
30
|
const blog = defineCollection({
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
// Load Markdown and MDX files in the `src/content/blog/` directory.
|
|
32
|
+
loader: glob({ base: './src/content/blog', pattern: '**/*.{md,mdx}' }),
|
|
33
|
+
// Type-check frontmatter using a schema
|
|
34
|
+
schema: ({ image }) =>
|
|
35
|
+
z.object({
|
|
36
|
+
title: z.string(),
|
|
37
|
+
subtitle: z.string().optional(),
|
|
38
|
+
description: z.string(),
|
|
39
|
+
// Transform string to Date object
|
|
40
|
+
pubDate: z.coerce.date(),
|
|
41
|
+
updatedDate: z.coerce.date().optional(),
|
|
42
|
+
heroImage: image().optional(),
|
|
43
|
+
context: z.string().optional(),
|
|
44
|
+
readMinutes: z.number().int().positive().optional(),
|
|
45
|
+
aiModel: z.string().optional(),
|
|
46
|
+
aiMode: z.string().optional(),
|
|
47
|
+
aiState: z.string().optional(),
|
|
48
|
+
aiLatencyMs: z.number().int().nonnegative().optional(),
|
|
49
|
+
aiConfidence: z.number().min(0).max(1).optional(),
|
|
50
|
+
wordCount: z.number().int().nonnegative().optional(),
|
|
51
|
+
tokenCount: z.number().int().nonnegative().optional(),
|
|
52
|
+
author: z.string().optional(),
|
|
53
|
+
tags: z.array(z.string()).optional(),
|
|
54
|
+
canonicalTopic: z.string().optional(),
|
|
55
|
+
sourceLinks: z.array(sourceLinkSchema).optional(),
|
|
56
|
+
}),
|
|
32
57
|
});
|
|
33
58
|
|
|
34
59
|
export const collections = { blog };
|
|
@@ -4,6 +4,7 @@ import type { CollectionEntry } from 'astro:content';
|
|
|
4
4
|
import themeRedqueen1 from '../assets/theme/red-queen/theme-redqueen1.webp';
|
|
5
5
|
import themeRedqueen2 from '../assets/theme/red-queen/theme-redqueen2.gif';
|
|
6
6
|
import FormattedDate from '../components/FormattedDate.astro';
|
|
7
|
+
import Giscus from '../components/Giscus.astro';
|
|
7
8
|
import AiShell from './shells/AiShell.astro';
|
|
8
9
|
import blogPostCssUrl from '../styles/blog-post.css?url';
|
|
9
10
|
import { SITE_AUTHOR } from '@anglefeint/site-config/site';
|
|
@@ -335,25 +336,7 @@ const hasCommentsConfig = Boolean(
|
|
|
335
336
|
{
|
|
336
337
|
hasCommentsConfig && (
|
|
337
338
|
<section class="prose ai-comments" aria-label={messages.blog.comments}>
|
|
338
|
-
<
|
|
339
|
-
src="https://giscus.app/client.js"
|
|
340
|
-
data-repo={comments.REPO}
|
|
341
|
-
data-repo-id={comments.REPO_ID}
|
|
342
|
-
data-category={comments.CATEGORY}
|
|
343
|
-
data-category-id={comments.CATEGORY_ID}
|
|
344
|
-
data-mapping={comments.MAPPING}
|
|
345
|
-
data-term={comments.MAPPING === 'specific' ? normalizedCommentTerm : undefined}
|
|
346
|
-
data-number={comments.MAPPING === 'number' ? normalizedCommentNumber : undefined}
|
|
347
|
-
data-strict={comments.STRICT}
|
|
348
|
-
data-reactions-enabled={comments.REACTIONS_ENABLED}
|
|
349
|
-
data-emit-metadata={comments.EMIT_METADATA}
|
|
350
|
-
data-input-position={comments.INPUT_POSITION}
|
|
351
|
-
data-theme={comments.THEME}
|
|
352
|
-
data-lang={comments.LANG || resolvedLocale}
|
|
353
|
-
data-loading={comments.LOADING}
|
|
354
|
-
crossorigin={comments.CROSSORIGIN}
|
|
355
|
-
async
|
|
356
|
-
/>
|
|
339
|
+
<Giscus comments={comments} resolvedLocale={resolvedLocale} />
|
|
357
340
|
</section>
|
|
358
341
|
)
|
|
359
342
|
}
|
|
@@ -2,13 +2,18 @@ import { createDecryptorController } from './modal-decryptor.js';
|
|
|
2
2
|
import { mountHelpKeyboard } from './modal-keyboard.js';
|
|
3
3
|
import { renderProgressModal } from './modal-progress.js';
|
|
4
4
|
|
|
5
|
+
let cleanupAboutModals = null;
|
|
6
|
+
|
|
5
7
|
export function initAboutModals(runtimeConfig, prefersReducedMotion) {
|
|
8
|
+
if (cleanupAboutModals) cleanupAboutModals();
|
|
9
|
+
|
|
6
10
|
const modalOverlay = document.getElementById('hacker-modal');
|
|
7
11
|
const modalBody = document.getElementById('hacker-modal-body');
|
|
8
12
|
const modalTitle = document.querySelector('.hacker-modal-title');
|
|
9
13
|
if (!modalOverlay || !modalBody || !modalTitle) return;
|
|
10
14
|
|
|
11
|
-
const decryptorKeysLabel =
|
|
15
|
+
const decryptorKeysLabel =
|
|
16
|
+
typeof runtimeConfig.decryptorKeysLabel === 'string' ? runtimeConfig.decryptorKeysLabel : '';
|
|
12
17
|
const decryptor = createDecryptorController(
|
|
13
18
|
modalOverlay,
|
|
14
19
|
prefersReducedMotion,
|
|
@@ -17,26 +22,8 @@ export function initAboutModals(runtimeConfig, prefersReducedMotion) {
|
|
|
17
22
|
let cleanupKeyboard = null;
|
|
18
23
|
|
|
19
24
|
const scriptsTpl = document.getElementById('hacker-scripts-folders-tpl');
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
title: 'Downloading...',
|
|
23
|
-
body: '<div class="hacker-modal-download"><div class="modal-subtitle">Critical Data</div><div class="hacker-modal-progress" id="dl-progress"></div></div>',
|
|
24
|
-
type: 'progress',
|
|
25
|
-
},
|
|
26
|
-
ai: {
|
|
27
|
-
title: 'AI',
|
|
28
|
-
body: '<pre>~ $ ai --status --verbose\n\nmodel: runtime-default\nmode: standard\ncontext window: 32k\nlatency: 100-250ms\nsafety: enabled\n\n>> system online\n>> ready</pre>',
|
|
29
|
-
type: 'plain',
|
|
30
|
-
},
|
|
31
|
-
decryptor: {
|
|
32
|
-
title: 'Password Decryptor',
|
|
33
|
-
body: '<pre class="hacker-decryptor-pre">Calculating Hashes\n\n<span id="dec-keys">[00:00:01] 0 keys tested</span>\n\nCurrent passphrase: <span id="dec-pass">********</span>\n\nMaster key\n<span id="dec-master1"></span>\n<span id="dec-master2"></span>\n\nTransient key\n<span id="dec-trans1"></span>\n<span id="dec-trans2"></span>\n<span id="dec-trans3"></span>\n<span id="dec-trans4"></span></pre>',
|
|
34
|
-
type: 'decryptor',
|
|
35
|
-
},
|
|
36
|
-
help: { title: 'Help', body: '', type: 'keyboard' },
|
|
37
|
-
'all-scripts': { title: '/root/bash/scripts', body: '', type: 'scripts' },
|
|
38
|
-
};
|
|
39
|
-
const modalContent = runtimeConfig.modalContent || fallbackModalContent;
|
|
25
|
+
const modalContent = runtimeConfig.modalContent;
|
|
26
|
+
if (!modalContent || typeof modalContent !== 'object') return;
|
|
40
27
|
|
|
41
28
|
const closeModal = () => {
|
|
42
29
|
decryptor.stop();
|
|
@@ -99,22 +86,43 @@ export function initAboutModals(runtimeConfig, prefersReducedMotion) {
|
|
|
99
86
|
modalOverlay.setAttribute('aria-hidden', 'false');
|
|
100
87
|
};
|
|
101
88
|
|
|
102
|
-
document.querySelectorAll('.hacker-folder[data-modal]')
|
|
103
|
-
|
|
89
|
+
const folderButtons = Array.from(document.querySelectorAll('.hacker-folder[data-modal]'));
|
|
90
|
+
const buttonHandlers = folderButtons.map((button) => {
|
|
91
|
+
const onClick = () => {
|
|
104
92
|
const id = button.getAttribute('data-modal');
|
|
105
93
|
if (!id) return;
|
|
106
94
|
openModal(modalContent[id]);
|
|
107
|
-
}
|
|
95
|
+
};
|
|
96
|
+
button.addEventListener('click', onClick);
|
|
97
|
+
return [button, onClick];
|
|
108
98
|
});
|
|
109
99
|
|
|
110
100
|
const closeButton = document.querySelector('.hacker-modal-close');
|
|
111
101
|
if (closeButton) closeButton.addEventListener('click', closeModal);
|
|
112
102
|
|
|
113
|
-
|
|
103
|
+
const onOverlayClick = (event) => {
|
|
114
104
|
if (event.target === modalOverlay) closeModal();
|
|
115
|
-
}
|
|
105
|
+
};
|
|
106
|
+
modalOverlay.addEventListener('click', onOverlayClick);
|
|
116
107
|
|
|
117
|
-
|
|
108
|
+
const onDocumentKeydown = (event) => {
|
|
118
109
|
if (event.key === 'Escape' && modalOverlay.classList.contains('open')) closeModal();
|
|
119
|
-
}
|
|
110
|
+
};
|
|
111
|
+
document.addEventListener('keydown', onDocumentKeydown);
|
|
112
|
+
|
|
113
|
+
cleanupAboutModals = () => {
|
|
114
|
+
decryptor.stop();
|
|
115
|
+
if (cleanupKeyboard) {
|
|
116
|
+
cleanupKeyboard();
|
|
117
|
+
cleanupKeyboard = null;
|
|
118
|
+
}
|
|
119
|
+
if (closeButton) closeButton.removeEventListener('click', closeModal);
|
|
120
|
+
modalOverlay.removeEventListener('click', onOverlayClick);
|
|
121
|
+
document.removeEventListener('keydown', onDocumentKeydown);
|
|
122
|
+
buttonHandlers.forEach(([button, handler]) => {
|
|
123
|
+
button.removeEventListener('click', handler);
|
|
124
|
+
});
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
return cleanupAboutModals;
|
|
120
128
|
}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
export function initPostInteractions(prefersReducedMotion) {
|
|
2
|
-
|
|
3
|
-
if (glow) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
const glow = document.querySelector('.ai-mouse-glow');
|
|
3
|
+
if (glow && window.__anglefeintPostGlowBound__ !== true) {
|
|
4
|
+
window.__anglefeintPostGlowBound__ = true;
|
|
5
|
+
let raf = 0;
|
|
6
|
+
let x = 0;
|
|
7
|
+
let y = 0;
|
|
8
|
+
document.addEventListener('mousemove', function (e) {
|
|
8
9
|
x = e.clientX;
|
|
9
10
|
y = e.clientY;
|
|
10
11
|
if (!raf) {
|
|
11
|
-
raf = requestAnimationFrame(function() {
|
|
12
|
+
raf = requestAnimationFrame(function () {
|
|
12
13
|
glow.style.setProperty('--mouse-x', x + 'px');
|
|
13
14
|
glow.style.setProperty('--mouse-y', y + 'px');
|
|
14
15
|
raf = 0;
|
|
@@ -17,42 +18,56 @@ export function initPostInteractions(prefersReducedMotion) {
|
|
|
17
18
|
});
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
document.querySelectorAll('.ai-prose-body a[href]').forEach(function(a) {
|
|
21
|
+
document.querySelectorAll('.ai-prose-body a[href]').forEach(function (a) {
|
|
22
|
+
if (a.dataset.aiLinkPreviewBound === 'true') return;
|
|
21
23
|
var href = a.getAttribute('href') || '';
|
|
22
24
|
if (!href || href.startsWith('#')) return;
|
|
25
|
+
a.dataset.aiLinkPreviewBound = 'true';
|
|
23
26
|
a.classList.add('ai-link-preview');
|
|
24
27
|
try {
|
|
25
|
-
a.setAttribute(
|
|
28
|
+
a.setAttribute(
|
|
29
|
+
'data-preview',
|
|
30
|
+
href.startsWith('http') ? new URL(href, location.origin).hostname : href
|
|
31
|
+
);
|
|
26
32
|
} catch (_err) {
|
|
27
33
|
a.setAttribute('data-preview', href);
|
|
28
34
|
}
|
|
29
35
|
});
|
|
30
36
|
|
|
31
|
-
|
|
37
|
+
const paras = document.querySelectorAll(
|
|
38
|
+
'.ai-prose-body p, .ai-prose-body h2, .ai-prose-body h3, .ai-prose-body pre, .ai-prose-body blockquote, .ai-prose-body ul, .ai-prose-body ol'
|
|
39
|
+
);
|
|
32
40
|
if (window.IntersectionObserver) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
entry.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
const io = new IntersectionObserver(
|
|
42
|
+
function (entries) {
|
|
43
|
+
entries.forEach(function (entry) {
|
|
44
|
+
if (entry.isIntersecting) {
|
|
45
|
+
entry.target.classList.add('ai-para-visible');
|
|
46
|
+
io.unobserve(entry.target);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
{ rootMargin: '0px 0px -60px 0px', threshold: 0.1 }
|
|
51
|
+
);
|
|
41
52
|
|
|
42
|
-
paras.forEach(function(p) {
|
|
53
|
+
paras.forEach(function (p) {
|
|
54
|
+
if (p.dataset.aiRevealObserved === 'true') return;
|
|
55
|
+
p.dataset.aiRevealObserved = 'true';
|
|
43
56
|
io.observe(p);
|
|
44
57
|
});
|
|
45
58
|
} else {
|
|
46
|
-
paras.forEach(function(p) {
|
|
59
|
+
paras.forEach(function (p) {
|
|
47
60
|
p.classList.add('ai-para-visible');
|
|
48
61
|
});
|
|
49
62
|
}
|
|
50
63
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
64
|
+
const regen = document.querySelector('.ai-regenerate');
|
|
65
|
+
const article = document.querySelector('.ai-article');
|
|
66
|
+
const scan = document.querySelector('.ai-load-scan');
|
|
54
67
|
if (regen && article) {
|
|
55
|
-
regen.
|
|
68
|
+
if (regen.dataset.aiRegenerateBound === 'true') return;
|
|
69
|
+
regen.dataset.aiRegenerateBound = 'true';
|
|
70
|
+
regen.addEventListener('click', function () {
|
|
56
71
|
regen.disabled = true;
|
|
57
72
|
regen.classList.add('ai-regenerating');
|
|
58
73
|
article.classList.add('ai-regenerate-flash');
|
|
@@ -63,11 +78,14 @@ export function initPostInteractions(prefersReducedMotion) {
|
|
|
63
78
|
scan.style.top = '0';
|
|
64
79
|
scan.style.opacity = '1';
|
|
65
80
|
}
|
|
66
|
-
setTimeout(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
81
|
+
setTimeout(
|
|
82
|
+
function () {
|
|
83
|
+
article.classList.remove('ai-regenerate-flash');
|
|
84
|
+
regen.classList.remove('ai-regenerating');
|
|
85
|
+
regen.disabled = false;
|
|
86
|
+
},
|
|
87
|
+
prefersReducedMotion ? 120 : 1200
|
|
88
|
+
);
|
|
71
89
|
});
|
|
72
90
|
}
|
|
73
91
|
}
|