@lettercamp/cli 0.1.12 → 0.1.13
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
|
@@ -6,6 +6,7 @@ const { posts } = Astro.props;
|
|
|
6
6
|
// inert JSON script: article text may contain a literal closing script tag.
|
|
7
7
|
const index = JSON.stringify(posts.map(post => ({
|
|
8
8
|
title: String(post.title), href: post.href,
|
|
9
|
+
image: post.image ?? post.ogImage,
|
|
9
10
|
summary: typeof post.description === 'string' ? post.description : '',
|
|
10
11
|
text: typeof post.searchText === 'string' ? post.searchText : '',
|
|
11
12
|
category: post.category, date: post.date.label,
|
|
@@ -14,7 +15,7 @@ const index = JSON.stringify(posts.map(post => ({
|
|
|
14
15
|
|
|
15
16
|
<div class="post-search-trigger" hidden>
|
|
16
17
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true"><circle cx="10.5" cy="10.5" r="6.5" /><path d="m16 16 4.5 4.5" /></svg>
|
|
17
|
-
<
|
|
18
|
+
<button type="button" aria-label="Search articles" aria-haspopup="dialog" aria-controls="post-search-dialog">Search articles…</button>
|
|
18
19
|
</div>
|
|
19
20
|
<dialog id="post-search-dialog" class="post-search-dialog" aria-label="Search articles">
|
|
20
21
|
<div class="post-search-header">
|
|
@@ -3,7 +3,7 @@ export const postSearchScript = String.raw`(() => {
|
|
|
3
3
|
const dialog = document.querySelector('#post-search-dialog');
|
|
4
4
|
const data = document.querySelector('[data-post-search-index]');
|
|
5
5
|
if (!trigger || !dialog || !data || typeof dialog.showModal !== 'function') return;
|
|
6
|
-
const opener = trigger.querySelector('
|
|
6
|
+
const opener = trigger.querySelector('button');
|
|
7
7
|
const input = dialog.querySelector('input');
|
|
8
8
|
const results = dialog.querySelector('.post-search-results');
|
|
9
9
|
const status = dialog.querySelector('.post-search-status');
|
|
@@ -13,7 +13,6 @@ export const postSearchScript = String.raw`(() => {
|
|
|
13
13
|
const normalized = [];
|
|
14
14
|
let generation = 0;
|
|
15
15
|
let timer;
|
|
16
|
-
let restoringFocus = false;
|
|
17
16
|
const normalize = text => text.normalize('NFKD').replace(/\p{M}/gu, '').toLowerCase().replace(/\s+/g, ' ').trim();
|
|
18
17
|
const element = (tag, className, text) => {
|
|
19
18
|
const node = document.createElement(tag);
|
|
@@ -80,11 +79,24 @@ export const postSearchScript = String.raw`(() => {
|
|
|
80
79
|
const row = document.createElement('li');
|
|
81
80
|
const link = element('a', 'post-search-result', '');
|
|
82
81
|
link.href = post.href;
|
|
83
|
-
|
|
82
|
+
if (post.image) {
|
|
83
|
+
const image = element('img', 'post-search-image', '');
|
|
84
|
+
image.src = post.image;
|
|
85
|
+
image.alt = '';
|
|
86
|
+
image.width = 1200;
|
|
87
|
+
image.height = 630;
|
|
88
|
+
image.loading = 'lazy';
|
|
89
|
+
image.decoding = 'async';
|
|
90
|
+
image.addEventListener('error', () => image.remove(), { once: true });
|
|
91
|
+
link.append(image);
|
|
92
|
+
}
|
|
93
|
+
const content = element('span', 'post-search-content', '');
|
|
94
|
+
content.append(highlighted('post-search-title', post.title, terms));
|
|
84
95
|
const meta = [post.category, post.date].filter(Boolean).join(' · ');
|
|
85
|
-
if (meta)
|
|
96
|
+
if (meta) content.append(element('span', 'post-search-meta', meta));
|
|
86
97
|
const excerpt = terms.length ? snippet(post, terms, normalized[index]) : post.summary;
|
|
87
|
-
if (excerpt)
|
|
98
|
+
if (excerpt) content.append(highlighted('post-search-excerpt', excerpt, terms));
|
|
99
|
+
link.append(content);
|
|
88
100
|
row.append(link);
|
|
89
101
|
fragment.append(row);
|
|
90
102
|
}
|
|
@@ -93,7 +105,7 @@ export const postSearchScript = String.raw`(() => {
|
|
|
93
105
|
results.removeAttribute('aria-busy');
|
|
94
106
|
empty.hidden = count > 0;
|
|
95
107
|
empty.textContent = posts.length ? 'No articles found. Try a different word or title.' : 'No articles here yet.';
|
|
96
|
-
status.textContent = !terms.length ? '
|
|
108
|
+
status.textContent = !terms.length ? '' : count > limit
|
|
97
109
|
? 'Showing ' + limit + ' of ' + count + ' matching articles. Keep typing to narrow your search.'
|
|
98
110
|
: count + (count === 1 ? ' article found' : ' articles found');
|
|
99
111
|
}
|
|
@@ -143,7 +155,7 @@ export const postSearchScript = String.raw`(() => {
|
|
|
143
155
|
timer = setTimeout(step, 0);
|
|
144
156
|
}
|
|
145
157
|
function open() {
|
|
146
|
-
if (
|
|
158
|
+
if (dialog.open) return;
|
|
147
159
|
if (!posts) {
|
|
148
160
|
posts = JSON.parse(data.textContent);
|
|
149
161
|
data.textContent = '';
|
|
@@ -153,19 +165,12 @@ export const postSearchScript = String.raw`(() => {
|
|
|
153
165
|
search();
|
|
154
166
|
}
|
|
155
167
|
function close() {
|
|
156
|
-
restoringFocus = true;
|
|
157
168
|
++generation;
|
|
158
169
|
clearTimeout(timer);
|
|
159
170
|
dialog.close();
|
|
160
171
|
opener.focus({ preventScroll: true });
|
|
161
|
-
// Native dialog focus restoration must not immediately reopen the search.
|
|
162
|
-
queueMicrotask(() => { restoringFocus = false; });
|
|
163
172
|
}
|
|
164
|
-
opener.addEventListener('focus', open);
|
|
165
173
|
opener.addEventListener('click', open);
|
|
166
|
-
opener.addEventListener('keydown', event => {
|
|
167
|
-
if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); open(); }
|
|
168
|
-
});
|
|
169
174
|
input.addEventListener('input', event => { if (!event.isComposing) search(); });
|
|
170
175
|
input.addEventListener('compositionend', search);
|
|
171
176
|
dialog.querySelector('.post-search-close').addEventListener('click', close);
|
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
.post-search-trigger { position: relative; flex: 0 1 240px; min-width: 180px; margin-left: auto; }
|
|
2
2
|
.post-search-trigger svg { position: absolute; left: 13px; top: 50%; width: 16px; height: 16px; transform: translateY(-50%); color: var(--site-text-muted); pointer-events: none; }
|
|
3
|
-
.post-search-trigger
|
|
4
|
-
.post-search-trigger
|
|
5
|
-
.post-search-
|
|
3
|
+
.post-search-trigger button { width: 100%; height: 36px; padding: 0 14px 0 38px; border: 0; border-radius: 999px; background: var(--site-surface-hover); color: var(--site-text-secondary); font: 400 var(--text-label)/1.5 var(--font-family); text-align: left; cursor: pointer; }
|
|
4
|
+
.post-search-trigger button:hover { background: var(--site-surface-active); color: var(--site-foreground); }
|
|
5
|
+
.post-search-header input::placeholder { color: var(--site-text-secondary); opacity: 1; }
|
|
6
|
+
.post-search-trigger button:focus-visible { outline: 1px solid var(--site-border-strong); outline-offset: 3px; border-radius: 999px; background: var(--site-surface-active); }
|
|
6
7
|
html:has(.post-search-dialog[open]) { overflow: hidden; }
|
|
7
8
|
.post-search-dialog { position: fixed; inset: 0; width: min(620px, calc(100vw - 32px)); max-width: none; max-height: min(440px, calc(100dvh - 48px)); margin: auto; padding: 0; overflow: hidden; border: 1px solid var(--site-border); border-radius: 14px; background: var(--site-background); color: var(--site-foreground); box-shadow: 0 20px 80px #0003; font: 400 var(--text-label)/1.5 var(--font-family); }
|
|
8
9
|
.post-search-dialog[open] { display: flex; flex-direction: column; }
|
|
9
10
|
.post-search-dialog::backdrop { background: color-mix(in srgb, var(--site-background) 65%, transparent); backdrop-filter: blur(3px); }
|
|
10
|
-
.post-search-header { display: flex; flex-shrink: 0; align-items: center; gap: 10px; padding: 10px 14px; border-bottom: 1px solid var(--site-
|
|
11
|
+
.post-search-header { display: flex; flex-shrink: 0; align-items: center; gap: 10px; padding: 10px 14px; border-bottom: 1px solid var(--site-surface, #fafafa); }
|
|
11
12
|
.post-search-header > svg { width: 20px; height: 20px; flex-shrink: 0; color: var(--site-text-secondary); }
|
|
12
13
|
.post-search-header input { min-width: 0; width: 100%; padding: 2px 0; border: 0; outline: none; background: transparent; color: inherit; font: 400 max(16px, var(--text-small))/1.5 var(--font-family); }
|
|
13
|
-
.post-search-header:focus-within { border-bottom-color: var(--site-border-strong); }
|
|
14
14
|
.post-search-close { display: grid; flex-shrink: 0; place-items: center; width: 28px; height: 28px; padding: 4px; border: 0; background: transparent; color: var(--site-text-secondary); }
|
|
15
15
|
.post-search-close:hover { color: var(--site-foreground); }
|
|
16
16
|
.post-search-close svg { width: 20px; height: 20px; }
|
|
17
17
|
.post-search-status { flex-shrink: 0; margin: 0; padding: 8px 16px 4px; color: var(--site-text-secondary); font-size: var(--text-meta); }
|
|
18
|
+
.post-search-status:empty { padding: 0; }
|
|
18
19
|
.post-search-results { flex: 0 1 auto; min-height: 0; margin: 0; padding: 0 6px 6px; overflow: auto; overscroll-behavior: contain; list-style: none; }
|
|
19
|
-
.post-search-result { display: flex;
|
|
20
|
+
.post-search-result { display: flex; align-items: flex-start; gap: 12px; padding: 8px 10px; border-radius: 8px; color: inherit; text-decoration: none; overflow-wrap: anywhere; }
|
|
21
|
+
.post-search-image { flex: 0 0 64px; width: 64px; height: auto; aspect-ratio: 1200 / 630; object-fit: cover; border-radius: 4px; background: var(--site-surface); }
|
|
22
|
+
.post-search-content { display: flex; flex: 1; min-width: 0; flex-direction: column; gap: 2px; }
|
|
20
23
|
.post-search-result:is(:hover, :focus-visible) { outline: none; background: var(--site-surface-active); border-radius: 8px; }
|
|
21
24
|
.post-search-title { font-size: var(--text-label); font-weight: 500; }
|
|
22
25
|
.post-search-meta { color: var(--site-text-secondary); font-size: var(--text-meta); }
|
|
@@ -27,6 +30,8 @@ html:has(.post-search-dialog[open]) { overflow: hidden; }
|
|
|
27
30
|
.post-search-trigger { flex-basis: 100%; min-width: 0; }
|
|
28
31
|
.post-search-dialog { width: calc(100vw - 24px); max-height: min(440px, calc(100dvh - 24px)); }
|
|
29
32
|
.post-search-header { padding: 10px 12px; }
|
|
33
|
+
.post-search-result { gap: 10px; }
|
|
34
|
+
.post-search-image { flex-basis: 56px; width: 56px; }
|
|
30
35
|
}
|
|
31
36
|
@media (prefers-reduced-motion: no-preference) {
|
|
32
37
|
.post-search-dialog[open] { animation: post-search-enter 140ms ease-out; }
|