sequoia-cli 0.5.6 → 0.5.7
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/dist/components/sequoia-subscribe.js +401 -104
- package/dist/index.js +210 -89
- package/package.json +4 -4
|
@@ -1,24 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Sequoia
|
|
3
|
-
*
|
|
4
|
-
* A self-contained Web Component that lets users subscribe to a publication
|
|
5
|
-
* via the AT Protocol by creating a site.standard.graph.subscription record.
|
|
2
|
+
* Sequoia Web Components — AT Protocol-powered engagement components
|
|
6
3
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* Self-contained Web Components for subscribing to publications and
|
|
5
|
+
* recommending documents via the AT Protocol.
|
|
9
6
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
7
|
+
* Both components share:
|
|
8
|
+
* - OAuth redirect flow via a hosted callback endpoint
|
|
9
|
+
* - DID caching in a cookie (primary) and localStorage (fallback)
|
|
10
|
+
* - A common visual style driven by CSS custom properties
|
|
12
11
|
*
|
|
13
|
-
*
|
|
14
|
-
* - publication-uri: Override the publication AT URI (optional)
|
|
15
|
-
* - callback-uri: Redirect URI after OAuth authentication (default: "https://sequoia.pub/subscribe")
|
|
16
|
-
* - button-type: Branding style — "sequoia" (default), "bluesky", "blacksky", "atmosphere", or "plain"
|
|
17
|
-
* - label: Override the subscribe button label text
|
|
18
|
-
* - unsubscribe-label: Override the unsubscribe button label text
|
|
19
|
-
* - hide: Set to "auto" to hide if no publication URI is detected
|
|
20
|
-
*
|
|
21
|
-
* CSS Custom Properties:
|
|
12
|
+
* CSS Custom Properties (apply to both components):
|
|
22
13
|
* - --sequoia-fg-color: Text color (default: #1f2937)
|
|
23
14
|
* - --sequoia-bg-color: Background color (default: #ffffff)
|
|
24
15
|
* - --sequoia-border-color: Border color (default: #e5e7eb)
|
|
@@ -26,12 +17,6 @@
|
|
|
26
17
|
* - --sequoia-secondary-color: Secondary text color (default: #6b7280)
|
|
27
18
|
* - --sequoia-border-radius: Border radius (default: 8px)
|
|
28
19
|
* - --sequoia-icon-display: Icon display mode (default: inline-block) — set to "none" to hide
|
|
29
|
-
*
|
|
30
|
-
* Events:
|
|
31
|
-
* - sequoia-subscribed: Fired when the subscription is created successfully.
|
|
32
|
-
* detail: { publicationUri: string, recordUri: string }
|
|
33
|
-
* - sequoia-subscribe-error: Fired when the subscription fails.
|
|
34
|
-
* detail: { message: string }
|
|
35
20
|
*/
|
|
36
21
|
|
|
37
22
|
// ============================================================================
|
|
@@ -50,7 +35,7 @@ const styles = `
|
|
|
50
35
|
box-sizing: border-box;
|
|
51
36
|
}
|
|
52
37
|
|
|
53
|
-
.sequoia-
|
|
38
|
+
.sequoia-button {
|
|
54
39
|
display: inline-flex;
|
|
55
40
|
align-items: center;
|
|
56
41
|
gap: 0.375rem;
|
|
@@ -67,16 +52,16 @@ const styles = `
|
|
|
67
52
|
font-family: inherit;
|
|
68
53
|
}
|
|
69
54
|
|
|
70
|
-
.sequoia-
|
|
55
|
+
.sequoia-button:hover:not(:disabled) {
|
|
71
56
|
background: color-mix(in srgb, var(--sequoia-accent-color, #2563eb) 85%, black);
|
|
72
57
|
}
|
|
73
58
|
|
|
74
|
-
.sequoia-
|
|
59
|
+
.sequoia-button:disabled {
|
|
75
60
|
opacity: 0.6;
|
|
76
61
|
cursor: not-allowed;
|
|
77
62
|
}
|
|
78
63
|
|
|
79
|
-
.sequoia-
|
|
64
|
+
.sequoia-button svg {
|
|
80
65
|
display: var(--sequoia-icon-display, inline-block);
|
|
81
66
|
width: 1rem;
|
|
82
67
|
height: 1rem;
|
|
@@ -148,6 +133,47 @@ const BUTTON_TYPES = {
|
|
|
148
133
|
plain: { icon: "", subscribe: "Subscribe", unsubscribe: "Unsubscribe" },
|
|
149
134
|
};
|
|
150
135
|
|
|
136
|
+
// ============================================================================
|
|
137
|
+
// Recommend Icon Configuration
|
|
138
|
+
// ============================================================================
|
|
139
|
+
|
|
140
|
+
const HEART_PATH =
|
|
141
|
+
"M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z";
|
|
142
|
+
const HEART_ICON_OUTLINED = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="${HEART_PATH}"/></svg>`;
|
|
143
|
+
const HEART_ICON_FILLED = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="${HEART_PATH}"/></svg>`;
|
|
144
|
+
|
|
145
|
+
const STAR_PATH =
|
|
146
|
+
"M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z";
|
|
147
|
+
const STAR_ICON_OUTLINED = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"><path d="${STAR_PATH}"/></svg>`;
|
|
148
|
+
const STAR_ICON_FILLED = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor"><path d="${STAR_PATH}"/></svg>`;
|
|
149
|
+
|
|
150
|
+
const THUMBS_UP_RECT_PATH = "M1 21h4V9H1v12z";
|
|
151
|
+
const THUMBS_UP_HAND_PATH =
|
|
152
|
+
"M23 10c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z";
|
|
153
|
+
const THUMBS_UP_ICON_OUTLINED = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="${THUMBS_UP_RECT_PATH}" fill="currentColor"/><path d="${THUMBS_UP_HAND_PATH}" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/></svg>`;
|
|
154
|
+
const THUMBS_UP_ICON_FILLED = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="${THUMBS_UP_RECT_PATH}"/><path d="${THUMBS_UP_HAND_PATH}"/></svg>`;
|
|
155
|
+
|
|
156
|
+
const RECOMMEND_ICON_TYPES = {
|
|
157
|
+
heart: {
|
|
158
|
+
icon: HEART_ICON_OUTLINED,
|
|
159
|
+
iconActioned: HEART_ICON_FILLED,
|
|
160
|
+
action: "Recommend",
|
|
161
|
+
unaction: "Unrecommend",
|
|
162
|
+
},
|
|
163
|
+
star: {
|
|
164
|
+
icon: STAR_ICON_OUTLINED,
|
|
165
|
+
iconActioned: STAR_ICON_FILLED,
|
|
166
|
+
action: "Recommend",
|
|
167
|
+
unaction: "Unrecommend",
|
|
168
|
+
},
|
|
169
|
+
"thumbs-up": {
|
|
170
|
+
icon: THUMBS_UP_ICON_OUTLINED,
|
|
171
|
+
iconActioned: THUMBS_UP_ICON_FILLED,
|
|
172
|
+
action: "Recommend",
|
|
173
|
+
unaction: "Unrecommend",
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
|
|
151
177
|
// ============================================================================
|
|
152
178
|
// DID Storage
|
|
153
179
|
// ============================================================================
|
|
@@ -161,6 +187,7 @@ function storeSubscriberDid(did) {
|
|
|
161
187
|
const expires = new Date(
|
|
162
188
|
Date.now() + 365 * 24 * 60 * 60 * 1000,
|
|
163
189
|
).toUTCString();
|
|
190
|
+
// biome-ignore lint/suspicious/noDocumentCookie: back-compat with older browsers
|
|
164
191
|
document.cookie = `sequoia_did=${encodeURIComponent(did)}; Expires=${expires}; Path=/; SameSite=Lax; Secure`;
|
|
165
192
|
} catch {
|
|
166
193
|
// Cookie write may fail in some embedded contexts
|
|
@@ -200,6 +227,7 @@ function getStoredSubscriberDid() {
|
|
|
200
227
|
*/
|
|
201
228
|
function clearSubscriberDid() {
|
|
202
229
|
try {
|
|
230
|
+
// biome-ignore lint/suspicious/noDocumentCookie: back-compat with older browsers
|
|
203
231
|
document.cookie =
|
|
204
232
|
"sequoia_did=; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Path=/; SameSite=Lax; Secure";
|
|
205
233
|
} catch {
|
|
@@ -229,7 +257,7 @@ function consumeReturnParams() {
|
|
|
229
257
|
changed = true;
|
|
230
258
|
}
|
|
231
259
|
|
|
232
|
-
if (did
|
|
260
|
+
if (did?.startsWith("did:")) {
|
|
233
261
|
storeSubscriberDid(did);
|
|
234
262
|
url.searchParams.delete("sequoia_did");
|
|
235
263
|
changed = true;
|
|
@@ -287,7 +315,13 @@ async function fetchPublicationUri(origin) {
|
|
|
287
315
|
// SSR-safe base class - use HTMLElement in browser, empty class in Node.js
|
|
288
316
|
const BaseElement = typeof HTMLElement !== "undefined" ? HTMLElement : class {};
|
|
289
317
|
|
|
290
|
-
|
|
318
|
+
/**
|
|
319
|
+
* Abstract base class shared by SequoiaSubscribe and SequoiaRecommend.
|
|
320
|
+
* Handles shadow DOM setup, state management, the OAuth redirect flow,
|
|
321
|
+
* DID storage, and button rendering. Subclasses implement template methods
|
|
322
|
+
* to provide resource-specific behaviour.
|
|
323
|
+
*/
|
|
324
|
+
class SequoiaActionBase extends BaseElement {
|
|
291
325
|
constructor() {
|
|
292
326
|
super();
|
|
293
327
|
const shadow = this.attachShadow({ mode: "open" });
|
|
@@ -301,82 +335,104 @@ class SequoiaSubscribe extends BaseElement {
|
|
|
301
335
|
wrapper.part = "container";
|
|
302
336
|
|
|
303
337
|
this.wrapper = wrapper;
|
|
304
|
-
this.
|
|
338
|
+
this.actioned = false;
|
|
305
339
|
this.state = { type: "idle" };
|
|
306
340
|
this.abortController = null;
|
|
307
341
|
this.render();
|
|
308
342
|
}
|
|
309
343
|
|
|
310
|
-
static get observedAttributes() {
|
|
311
|
-
return [
|
|
312
|
-
"publication-uri",
|
|
313
|
-
"callback-uri",
|
|
314
|
-
"label",
|
|
315
|
-
"unsubscribe-label",
|
|
316
|
-
"button-type",
|
|
317
|
-
"hide",
|
|
318
|
-
];
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
connectedCallback() {
|
|
322
|
-
consumeReturnParams();
|
|
323
|
-
this.checkPublication();
|
|
324
|
-
}
|
|
325
|
-
|
|
326
344
|
disconnectedCallback() {
|
|
327
345
|
this.abortController?.abort();
|
|
328
346
|
}
|
|
329
347
|
|
|
330
348
|
attributeChangedCallback() {
|
|
331
|
-
if (this.state.type === "error" || this.state.type === "no-
|
|
349
|
+
if (this.state.type === "error" || this.state.type === "no-resource") {
|
|
332
350
|
this.state = { type: "idle" };
|
|
333
351
|
}
|
|
334
352
|
this.render();
|
|
335
353
|
}
|
|
336
354
|
|
|
337
|
-
|
|
338
|
-
return this.getAttribute("publication-uri") ?? null;
|
|
339
|
-
}
|
|
355
|
+
// ── Shared getters ───────────────────────────────────────────────────────
|
|
340
356
|
|
|
341
357
|
get callbackUri() {
|
|
342
|
-
return this.getAttribute("callback-uri") ??
|
|
358
|
+
return this.getAttribute("callback-uri") ?? this.defaultCallbackUri;
|
|
343
359
|
}
|
|
344
360
|
|
|
345
|
-
get
|
|
346
|
-
return this.getAttribute("
|
|
361
|
+
get hide() {
|
|
362
|
+
return this.getAttribute("hide") === "auto";
|
|
347
363
|
}
|
|
348
364
|
|
|
349
|
-
|
|
350
|
-
|
|
365
|
+
// ── Template methods (override in subclasses) ────────────────────────────
|
|
366
|
+
|
|
367
|
+
/** @returns {string} Default callback URI when the attribute is absent */
|
|
368
|
+
get defaultCallbackUri() {
|
|
369
|
+
return "";
|
|
351
370
|
}
|
|
352
371
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
return
|
|
372
|
+
/** @returns {string} Query-parameter name for the resource URI */
|
|
373
|
+
get resourceParam() {
|
|
374
|
+
return "resourceUri";
|
|
356
375
|
}
|
|
357
376
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
377
|
+
/**
|
|
378
|
+
* Value of the `action` query-parameter used in the unaction redirect.
|
|
379
|
+
* @returns {string}
|
|
380
|
+
*/
|
|
381
|
+
get unactionValue() {
|
|
382
|
+
return "unaction";
|
|
361
383
|
}
|
|
362
384
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
385
|
+
/** @returns {string} Key in the /check response that signals the action was taken */
|
|
386
|
+
get actionedKey() {
|
|
387
|
+
return "actioned";
|
|
388
|
+
}
|
|
366
389
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
390
|
+
/** @returns {string} CustomEvent name dispatched on success */
|
|
391
|
+
get actionedEventName() {
|
|
392
|
+
return "sequoia-actioned";
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/** @returns {string} CustomEvent name dispatched on error */
|
|
396
|
+
get errorEventName() {
|
|
397
|
+
return "sequoia-action-error";
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/** @returns {string} Fallback error message when the thrown value has no message */
|
|
401
|
+
get defaultErrorMessage() {
|
|
402
|
+
return "Action failed";
|
|
374
403
|
}
|
|
375
404
|
|
|
376
|
-
|
|
405
|
+
/** @returns {string} SVG string for the button icon */
|
|
406
|
+
getIcon() {
|
|
407
|
+
return "";
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/** @returns {string} Accessible label for the button (defaults to the visible label) */
|
|
411
|
+
getAriaLabel() {
|
|
412
|
+
return this.actioned
|
|
413
|
+
? (this.getUnactionLabel?.() ?? this.getDefaultUnactionLabel?.() ?? "")
|
|
414
|
+
: (this.label ?? this.getDefaultActionLabel?.() ?? "");
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Resolve the resource URI for this action. May perform async network calls.
|
|
419
|
+
* @returns {Promise<string>}
|
|
420
|
+
*/
|
|
421
|
+
async resolveResourceUri() {
|
|
422
|
+
throw new Error("resolveResourceUri() must be implemented by subclass");
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// ── Shared logic ─────────────────────────────────────────────────────────
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Check whether the current user has already taken this action for the
|
|
429
|
+
* given resource URI. Updates this.actioned and re-renders on success.
|
|
430
|
+
* @param {string} resourceUri
|
|
431
|
+
*/
|
|
432
|
+
async checkStatusFor(resourceUri) {
|
|
377
433
|
try {
|
|
378
434
|
const checkUrl = new URL(`${this.callbackUri}/check`);
|
|
379
|
-
checkUrl.searchParams.set(
|
|
435
|
+
checkUrl.searchParams.set(this.resourceParam, resourceUri);
|
|
380
436
|
|
|
381
437
|
// Pass the stored DID so the server can check without a session cookie
|
|
382
438
|
const storedDid = getStoredSubscriberDid();
|
|
@@ -389,12 +445,12 @@ class SequoiaSubscribe extends BaseElement {
|
|
|
389
445
|
});
|
|
390
446
|
if (!res.ok) return;
|
|
391
447
|
const data = await res.json();
|
|
392
|
-
if (data.
|
|
393
|
-
this.
|
|
448
|
+
if (data[this.actionedKey]) {
|
|
449
|
+
this.actioned = true;
|
|
394
450
|
this.render();
|
|
395
451
|
}
|
|
396
452
|
} catch {
|
|
397
|
-
// Ignore errors — show default
|
|
453
|
+
// Ignore errors — show default action button
|
|
398
454
|
}
|
|
399
455
|
}
|
|
400
456
|
|
|
@@ -403,11 +459,10 @@ class SequoiaSubscribe extends BaseElement {
|
|
|
403
459
|
return;
|
|
404
460
|
}
|
|
405
461
|
|
|
406
|
-
//
|
|
407
|
-
if (this.
|
|
408
|
-
const
|
|
409
|
-
|
|
410
|
-
window.location.href = `${this.callbackUri}?publicationUri=${encodeURIComponent(publicationUri)}&action=unsubscribe`;
|
|
462
|
+
// Unaction: redirect to the full-page unaction flow
|
|
463
|
+
if (this.actioned) {
|
|
464
|
+
const resourceUri = await this.resolveResourceUri();
|
|
465
|
+
window.location.href = `${this.callbackUri}?${this.resourceParam}=${encodeURIComponent(resourceUri)}&action=${this.unactionValue}`;
|
|
411
466
|
return;
|
|
412
467
|
}
|
|
413
468
|
|
|
@@ -415,28 +470,27 @@ class SequoiaSubscribe extends BaseElement {
|
|
|
415
470
|
this.render();
|
|
416
471
|
|
|
417
472
|
try {
|
|
418
|
-
const
|
|
419
|
-
this.publicationUri ?? (await fetchPublicationUri());
|
|
473
|
+
const resourceUri = await this.resolveResourceUri();
|
|
420
474
|
|
|
421
475
|
const response = await fetch(this.callbackUri, {
|
|
422
476
|
method: "POST",
|
|
423
477
|
headers: { "Content-Type": "application/json" },
|
|
424
478
|
credentials: "include",
|
|
425
479
|
referrerPolicy: "no-referrer-when-downgrade",
|
|
426
|
-
body: JSON.stringify({
|
|
480
|
+
body: JSON.stringify({ [this.resourceParam]: resourceUri }),
|
|
427
481
|
});
|
|
428
482
|
|
|
429
483
|
const data = await response.json();
|
|
430
484
|
|
|
431
485
|
if (response.status === 401 && data.authenticated === false) {
|
|
432
|
-
// Redirect to the hosted
|
|
486
|
+
// Redirect to the hosted action page to complete OAuth,
|
|
433
487
|
// passing the current page URL (without credentials) as returnTo.
|
|
434
|
-
const
|
|
488
|
+
const actionUrl = new URL(data.subscribeUrl);
|
|
435
489
|
const pageUrl = new URL(window.location.href);
|
|
436
490
|
pageUrl.username = "";
|
|
437
491
|
pageUrl.password = "";
|
|
438
|
-
|
|
439
|
-
window.location.href =
|
|
492
|
+
actionUrl.searchParams.set("returnTo", pageUrl.toString());
|
|
493
|
+
window.location.href = actionUrl.toString();
|
|
440
494
|
return;
|
|
441
495
|
}
|
|
442
496
|
|
|
@@ -454,27 +508,27 @@ class SequoiaSubscribe extends BaseElement {
|
|
|
454
508
|
}
|
|
455
509
|
}
|
|
456
510
|
|
|
457
|
-
this.
|
|
511
|
+
this.actioned = true;
|
|
458
512
|
this.state = { type: "idle" };
|
|
459
513
|
this.render();
|
|
460
514
|
|
|
461
515
|
this.dispatchEvent(
|
|
462
|
-
new CustomEvent(
|
|
516
|
+
new CustomEvent(this.actionedEventName, {
|
|
463
517
|
bubbles: true,
|
|
464
518
|
composed: true,
|
|
465
|
-
detail: {
|
|
519
|
+
detail: { [this.resourceParam]: resourceUri, recordUri },
|
|
466
520
|
}),
|
|
467
521
|
);
|
|
468
522
|
} catch (error) {
|
|
469
523
|
if (this.state.type !== "loading") return;
|
|
470
524
|
|
|
471
525
|
const message =
|
|
472
|
-
error instanceof Error ? error.message :
|
|
526
|
+
error instanceof Error ? error.message : this.defaultErrorMessage;
|
|
473
527
|
this.state = { type: "error", message };
|
|
474
528
|
this.render();
|
|
475
529
|
|
|
476
530
|
this.dispatchEvent(
|
|
477
|
-
new CustomEvent(
|
|
531
|
+
new CustomEvent(this.errorEventName, {
|
|
478
532
|
bubbles: true,
|
|
479
533
|
composed: true,
|
|
480
534
|
detail: { message },
|
|
@@ -486,7 +540,7 @@ class SequoiaSubscribe extends BaseElement {
|
|
|
486
540
|
render() {
|
|
487
541
|
const { type } = this.state;
|
|
488
542
|
|
|
489
|
-
if (type === "no-
|
|
543
|
+
if (type === "no-resource") {
|
|
490
544
|
if (this.hide) {
|
|
491
545
|
this.wrapper.innerHTML = "";
|
|
492
546
|
this.wrapper.style.display = "none";
|
|
@@ -495,15 +549,15 @@ class SequoiaSubscribe extends BaseElement {
|
|
|
495
549
|
}
|
|
496
550
|
|
|
497
551
|
const isLoading = type === "loading";
|
|
498
|
-
const config = BUTTON_TYPES[this.buttonType] ?? BUTTON_TYPES.sequoia;
|
|
499
|
-
|
|
500
552
|
const icon = isLoading
|
|
501
553
|
? `<span class="sequoia-loading-spinner"></span>`
|
|
502
|
-
:
|
|
554
|
+
: this.getIcon();
|
|
555
|
+
|
|
556
|
+
const label = this.actioned
|
|
557
|
+
? (this.getUnactionLabel?.() ?? this.getDefaultUnactionLabel?.() ?? "")
|
|
558
|
+
: (this.label ?? this.getDefaultActionLabel?.() ?? "");
|
|
503
559
|
|
|
504
|
-
const
|
|
505
|
-
? (this.unsubscribeLabel ?? config.unsubscribe)
|
|
506
|
-
: (this.label ?? config.subscribe);
|
|
560
|
+
const ariaLabel = this.getAriaLabel();
|
|
507
561
|
|
|
508
562
|
const errorHtml =
|
|
509
563
|
type === "error"
|
|
@@ -512,11 +566,11 @@ class SequoiaSubscribe extends BaseElement {
|
|
|
512
566
|
|
|
513
567
|
this.wrapper.innerHTML = `
|
|
514
568
|
<button
|
|
515
|
-
class="sequoia-
|
|
569
|
+
class="sequoia-button"
|
|
516
570
|
type="button"
|
|
517
571
|
part="button"
|
|
518
572
|
${isLoading ? "disabled" : ""}
|
|
519
|
-
aria-label="${
|
|
573
|
+
aria-label="${ariaLabel}"
|
|
520
574
|
>
|
|
521
575
|
${icon}
|
|
522
576
|
${label}
|
|
@@ -529,6 +583,197 @@ class SequoiaSubscribe extends BaseElement {
|
|
|
529
583
|
}
|
|
530
584
|
}
|
|
531
585
|
|
|
586
|
+
class SequoiaSubscribe extends SequoiaActionBase {
|
|
587
|
+
static get observedAttributes() {
|
|
588
|
+
return [
|
|
589
|
+
"publication-uri",
|
|
590
|
+
"callback-uri",
|
|
591
|
+
"label",
|
|
592
|
+
"unsubscribe-label",
|
|
593
|
+
"button-type",
|
|
594
|
+
"hide",
|
|
595
|
+
];
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
connectedCallback() {
|
|
599
|
+
consumeReturnParams();
|
|
600
|
+
this.checkPublication();
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
get publicationUri() {
|
|
604
|
+
return this.getAttribute("publication-uri") ?? null;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
get label() {
|
|
608
|
+
return this.getAttribute("label") ?? null;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
get buttonType() {
|
|
612
|
+
const val = this.getAttribute("button-type");
|
|
613
|
+
return val && val in BUTTON_TYPES ? val : "sequoia";
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
get unsubscribeLabel() {
|
|
617
|
+
return this.getAttribute("unsubscribe-label") ?? null;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// ── Template method overrides ────────────────────────────────────────────
|
|
621
|
+
|
|
622
|
+
get defaultCallbackUri() {
|
|
623
|
+
return "https://sequoia.pub/subscribe";
|
|
624
|
+
}
|
|
625
|
+
get resourceParam() {
|
|
626
|
+
return "publicationUri";
|
|
627
|
+
}
|
|
628
|
+
get unactionValue() {
|
|
629
|
+
return "unsubscribe";
|
|
630
|
+
}
|
|
631
|
+
get actionedKey() {
|
|
632
|
+
return "subscribed";
|
|
633
|
+
}
|
|
634
|
+
get actionedEventName() {
|
|
635
|
+
return "sequoia-subscribed";
|
|
636
|
+
}
|
|
637
|
+
get errorEventName() {
|
|
638
|
+
return "sequoia-subscribe-error";
|
|
639
|
+
}
|
|
640
|
+
get defaultErrorMessage() {
|
|
641
|
+
return "Failed to subscribe";
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
getDefaultActionLabel() {
|
|
645
|
+
return (BUTTON_TYPES[this.buttonType] ?? BUTTON_TYPES.sequoia).subscribe;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
getDefaultUnactionLabel() {
|
|
649
|
+
return (BUTTON_TYPES[this.buttonType] ?? BUTTON_TYPES.sequoia).unsubscribe;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
getUnactionLabel() {
|
|
653
|
+
return this.unsubscribeLabel;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
getIcon() {
|
|
657
|
+
return (BUTTON_TYPES[this.buttonType] ?? BUTTON_TYPES.sequoia).icon;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
async resolveResourceUri() {
|
|
661
|
+
return this.publicationUri ?? (await fetchPublicationUri());
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// ── SequoiaSubscribe-specific logic ──────────────────────────────────────
|
|
665
|
+
|
|
666
|
+
/** @returns {boolean} Whether the user is currently subscribed. Alias for this.actioned. */
|
|
667
|
+
get subscribed() {
|
|
668
|
+
return this.actioned;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* Check whether the current user is subscribed to the given publication URI.
|
|
673
|
+
* Forwards to the shared checkStatusFor() method.
|
|
674
|
+
* @param {string} publicationUri
|
|
675
|
+
*/
|
|
676
|
+
checkSubscription(publicationUri) {
|
|
677
|
+
return this.checkStatusFor(publicationUri);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
async checkPublication() {
|
|
681
|
+
this.abortController?.abort();
|
|
682
|
+
this.abortController = new AbortController();
|
|
683
|
+
|
|
684
|
+
try {
|
|
685
|
+
const uri = await this.resolveResourceUri();
|
|
686
|
+
this.checkStatusFor(uri);
|
|
687
|
+
} catch {
|
|
688
|
+
this.state = { type: "no-resource" };
|
|
689
|
+
this.render();
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
class SequoiaRecommend extends SequoiaActionBase {
|
|
695
|
+
static get observedAttributes() {
|
|
696
|
+
return ["document-uri", "callback-uri", "button-type", "hide"];
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
connectedCallback() {
|
|
700
|
+
consumeReturnParams();
|
|
701
|
+
this.checkDocument();
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
get documentUri() {
|
|
705
|
+
const attrUri = this.getAttribute("document-uri");
|
|
706
|
+
if (attrUri) return attrUri;
|
|
707
|
+
const linkTag = document.querySelector(
|
|
708
|
+
'link[rel="site.standard.document"]',
|
|
709
|
+
);
|
|
710
|
+
return linkTag?.href ?? null;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
get buttonType() {
|
|
714
|
+
const val = this.getAttribute("button-type");
|
|
715
|
+
return val && val in RECOMMEND_ICON_TYPES ? val : "heart";
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// ── Template method overrides ────────────────────────────────────────────
|
|
719
|
+
|
|
720
|
+
get defaultCallbackUri() {
|
|
721
|
+
return "https://sequoia.pub/recommend";
|
|
722
|
+
}
|
|
723
|
+
get resourceParam() {
|
|
724
|
+
return "documentUri";
|
|
725
|
+
}
|
|
726
|
+
get unactionValue() {
|
|
727
|
+
return "remove";
|
|
728
|
+
}
|
|
729
|
+
get actionedKey() {
|
|
730
|
+
return "recommended";
|
|
731
|
+
}
|
|
732
|
+
get actionedEventName() {
|
|
733
|
+
return "sequoia-recommended";
|
|
734
|
+
}
|
|
735
|
+
get errorEventName() {
|
|
736
|
+
return "sequoia-recommend-error";
|
|
737
|
+
}
|
|
738
|
+
get defaultErrorMessage() {
|
|
739
|
+
return "Failed to recommend";
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
getAriaLabel() {
|
|
743
|
+
const config =
|
|
744
|
+
RECOMMEND_ICON_TYPES[this.buttonType] ?? RECOMMEND_ICON_TYPES.heart;
|
|
745
|
+
return this.actioned ? config.unaction : config.action;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
getIcon() {
|
|
749
|
+
const config =
|
|
750
|
+
RECOMMEND_ICON_TYPES[this.buttonType] ?? RECOMMEND_ICON_TYPES.heart;
|
|
751
|
+
return this.actioned ? config.iconActioned : config.icon;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
async resolveResourceUri() {
|
|
755
|
+
const uri = this.documentUri;
|
|
756
|
+
if (!uri) throw new Error("No document URI found");
|
|
757
|
+
return uri;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// ── SequoiaRecommend-specific logic ──────────────────────────────────────
|
|
761
|
+
|
|
762
|
+
async checkDocument() {
|
|
763
|
+
this.abortController?.abort();
|
|
764
|
+
this.abortController = new AbortController();
|
|
765
|
+
|
|
766
|
+
const uri = this.documentUri;
|
|
767
|
+
if (!uri) {
|
|
768
|
+
this.state = { type: "no-resource" };
|
|
769
|
+
this.render();
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
this.checkStatusFor(uri);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
532
777
|
/**
|
|
533
778
|
* Escape HTML special characters (no DOM dependency for SSR).
|
|
534
779
|
* @param {string} text
|
|
@@ -542,10 +787,62 @@ function escapeHtml(text) {
|
|
|
542
787
|
.replace(/"/g, """);
|
|
543
788
|
}
|
|
544
789
|
|
|
545
|
-
// Register the custom
|
|
790
|
+
// Register the custom elements
|
|
546
791
|
if (typeof customElements !== "undefined") {
|
|
547
792
|
customElements.define("sequoia-subscribe", SequoiaSubscribe);
|
|
793
|
+
customElements.define("sequoia-recommend", SequoiaRecommend);
|
|
548
794
|
}
|
|
549
795
|
|
|
550
|
-
|
|
796
|
+
/**
|
|
797
|
+
* Sequoia Subscribe - An AT Protocol-powered subscribe component
|
|
798
|
+
*
|
|
799
|
+
* A self-contained Web Component that lets users subscribe to a publication
|
|
800
|
+
* via the AT Protocol by creating a site.standard.graph.subscription record.
|
|
801
|
+
*
|
|
802
|
+
* Usage:
|
|
803
|
+
* <sequoia-subscribe></sequoia-subscribe>
|
|
804
|
+
*
|
|
805
|
+
* The component resolves the publication AT URI from the host site's
|
|
806
|
+
* /.well-known/site.standard.publication endpoint.
|
|
807
|
+
*
|
|
808
|
+
* Attributes:
|
|
809
|
+
* - publication-uri: Override the publication AT URI (optional)
|
|
810
|
+
* - callback-uri: Redirect URI after OAuth authentication (default: "https://sequoia.pub/subscribe")
|
|
811
|
+
* - button-type: Branding style — "sequoia" (default), "bluesky", "blacksky", "atmosphere", or "plain"
|
|
812
|
+
* - label: Override the subscribe button label text
|
|
813
|
+
* - unsubscribe-label: Override the unsubscribe button label text
|
|
814
|
+
* - hide: Set to "auto" to hide if no publication URI is detected
|
|
815
|
+
*
|
|
816
|
+
* Events:
|
|
817
|
+
* - sequoia-subscribed: Fired when the subscription is created successfully.
|
|
818
|
+
* detail: { publicationUri: string, recordUri: string }
|
|
819
|
+
* - sequoia-subscribe-error: Fired when the subscription fails.
|
|
820
|
+
* detail: { message: string }
|
|
821
|
+
*/
|
|
551
822
|
export { SequoiaSubscribe };
|
|
823
|
+
|
|
824
|
+
/**
|
|
825
|
+
* Sequoia Recommend - An AT Protocol-powered recommend component
|
|
826
|
+
*
|
|
827
|
+
* A self-contained Web Component that lets users recommend a document
|
|
828
|
+
* via the AT Protocol by creating a site.standard.graph.recommend record.
|
|
829
|
+
*
|
|
830
|
+
* Usage:
|
|
831
|
+
* <sequoia-recommend></sequoia-recommend>
|
|
832
|
+
*
|
|
833
|
+
* The component resolves the document AT URI from the `document-uri` attribute
|
|
834
|
+
* or a <link rel="site.standard.document" href="at://..."> tag in the page head.
|
|
835
|
+
*
|
|
836
|
+
* Attributes:
|
|
837
|
+
* - document-uri: AT Protocol URI of the document to recommend (optional if link tag present)
|
|
838
|
+
* - callback-uri: Redirect URI after OAuth authentication (default: "https://sequoia.pub/recommend")
|
|
839
|
+
* - button-type: Icon style — "heart" (default), "star", or "thumbs-up"
|
|
840
|
+
* - hide: Set to "auto" to hide if no document URI is detected
|
|
841
|
+
*
|
|
842
|
+
* Events:
|
|
843
|
+
* - sequoia-recommended: Fired when the recommendation is created successfully.
|
|
844
|
+
* detail: { documentUri: string, recordUri: string }
|
|
845
|
+
* - sequoia-recommend-error: Fired when the recommendation fails.
|
|
846
|
+
* detail: { message: string }
|
|
847
|
+
*/
|
|
848
|
+
export { SequoiaRecommend };
|
package/dist/index.js
CHANGED
|
@@ -6,39 +6,60 @@ var __defProp = Object.defineProperty;
|
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
8
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
function __accessProp(key) {
|
|
10
|
+
return this[key];
|
|
11
|
+
}
|
|
12
|
+
var __toESMCache_node;
|
|
13
|
+
var __toESMCache_esm;
|
|
9
14
|
var __toESM = (mod, isNodeMode, target) => {
|
|
15
|
+
var canCache = mod != null && typeof mod === "object";
|
|
16
|
+
if (canCache) {
|
|
17
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
18
|
+
var cached = cache.get(mod);
|
|
19
|
+
if (cached)
|
|
20
|
+
return cached;
|
|
21
|
+
}
|
|
10
22
|
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
11
23
|
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
12
24
|
for (let key of __getOwnPropNames(mod))
|
|
13
25
|
if (!__hasOwnProp.call(to, key))
|
|
14
26
|
__defProp(to, key, {
|
|
15
|
-
get: (
|
|
27
|
+
get: __accessProp.bind(mod, key),
|
|
16
28
|
enumerable: true
|
|
17
29
|
});
|
|
30
|
+
if (canCache)
|
|
31
|
+
cache.set(mod, to);
|
|
18
32
|
return to;
|
|
19
33
|
};
|
|
20
|
-
var __moduleCache = /* @__PURE__ */ new WeakMap;
|
|
21
34
|
var __toCommonJS = (from) => {
|
|
22
|
-
var entry = __moduleCache.get(from), desc;
|
|
35
|
+
var entry = (__moduleCache ??= new WeakMap).get(from), desc;
|
|
23
36
|
if (entry)
|
|
24
37
|
return entry;
|
|
25
38
|
entry = __defProp({}, "__esModule", { value: true });
|
|
26
|
-
if (from && typeof from === "object" || typeof from === "function")
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
39
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
40
|
+
for (var key of __getOwnPropNames(from))
|
|
41
|
+
if (!__hasOwnProp.call(entry, key))
|
|
42
|
+
__defProp(entry, key, {
|
|
43
|
+
get: __accessProp.bind(from, key),
|
|
44
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
45
|
+
});
|
|
46
|
+
}
|
|
31
47
|
__moduleCache.set(from, entry);
|
|
32
48
|
return entry;
|
|
33
49
|
};
|
|
50
|
+
var __moduleCache;
|
|
34
51
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
52
|
+
var __returnValue = (v) => v;
|
|
53
|
+
function __exportSetter(name, newValue) {
|
|
54
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
55
|
+
}
|
|
35
56
|
var __export = (target, all) => {
|
|
36
57
|
for (var name in all)
|
|
37
58
|
__defProp(target, name, {
|
|
38
59
|
get: all[name],
|
|
39
60
|
enumerable: true,
|
|
40
61
|
configurable: true,
|
|
41
|
-
set: (
|
|
62
|
+
set: __exportSetter.bind(all, name)
|
|
42
63
|
});
|
|
43
64
|
};
|
|
44
65
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
@@ -60551,17 +60572,6 @@ var require_db = __commonJS((exports, module) => {
|
|
|
60551
60572
|
};
|
|
60552
60573
|
});
|
|
60553
60574
|
|
|
60554
|
-
// ../../node_modules/.bun/mime-db@1.52.0/node_modules/mime-db/index.js
|
|
60555
|
-
var require_mime_db = __commonJS((exports, module) => {
|
|
60556
|
-
/*!
|
|
60557
|
-
* mime-db
|
|
60558
|
-
* Copyright(c) 2014 Jonathan Ong
|
|
60559
|
-
* Copyright(c) 2015-2022 Douglas Christopher Wilson
|
|
60560
|
-
* MIT Licensed
|
|
60561
|
-
*/
|
|
60562
|
-
module.exports = require_db();
|
|
60563
|
-
});
|
|
60564
|
-
|
|
60565
60575
|
// ../../node_modules/.bun/@atproto-labs+fetch@0.2.3/node_modules/@atproto-labs/fetch/dist/fetch-error.js
|
|
60566
60576
|
var require_fetch_error = __commonJS((exports) => {
|
|
60567
60577
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -78864,7 +78874,7 @@ var require_undici = __commonJS((exports, module) => {
|
|
|
78864
78874
|
exports.setGlobalDispatcher = setGlobalDispatcher;
|
|
78865
78875
|
exports.getGlobalDispatcher = getGlobalDispatcher;
|
|
78866
78876
|
var fetchImpl = require_fetch2().fetch;
|
|
78867
|
-
exports.fetch = async function
|
|
78877
|
+
exports.fetch = async function fetch2(init, options = undefined) {
|
|
78868
78878
|
try {
|
|
78869
78879
|
return await fetchImpl(init, options);
|
|
78870
78880
|
} catch (err) {
|
|
@@ -93396,6 +93406,15 @@ import * as fs from "node:fs/promises";
|
|
|
93396
93406
|
import * as path from "node:path";
|
|
93397
93407
|
var CONFIG_FILENAME = "sequoia.json";
|
|
93398
93408
|
var STATE_FILENAME = ".sequoia-state.json";
|
|
93409
|
+
var DEFAULT_PUBLISHER_CONFIG = {
|
|
93410
|
+
publicDir: "./public",
|
|
93411
|
+
outputDir: "./dist",
|
|
93412
|
+
pathPrefix: "/posts",
|
|
93413
|
+
publishContent: true,
|
|
93414
|
+
removeIndexFromSlug: false,
|
|
93415
|
+
stripDatePrefix: false,
|
|
93416
|
+
autoSync: true
|
|
93417
|
+
};
|
|
93399
93418
|
async function fileExists(filePath) {
|
|
93400
93419
|
try {
|
|
93401
93420
|
await fs.access(filePath);
|
|
@@ -93432,7 +93451,7 @@ async function loadConfig(configPath) {
|
|
|
93432
93451
|
throw new Error("contentDir is required in config");
|
|
93433
93452
|
if (!config.publicationUri)
|
|
93434
93453
|
throw new Error("publicationUri is required in config");
|
|
93435
|
-
return config;
|
|
93454
|
+
return { ...DEFAULT_PUBLISHER_CONFIG, ...config };
|
|
93436
93455
|
} catch (error) {
|
|
93437
93456
|
if (error instanceof Error && error.message.includes("required")) {
|
|
93438
93457
|
throw error;
|
|
@@ -93480,7 +93499,7 @@ function generateConfigTemplate(options) {
|
|
|
93480
93499
|
if (options.textContentField) {
|
|
93481
93500
|
config.textContentField = options.textContentField;
|
|
93482
93501
|
}
|
|
93483
|
-
if (options.publishContent) {
|
|
93502
|
+
if (options.publishContent !== undefined) {
|
|
93484
93503
|
config.publishContent = options.publishContent;
|
|
93485
93504
|
}
|
|
93486
93505
|
if (options.bluesky) {
|
|
@@ -93640,7 +93659,7 @@ var import_api = __toESM(require_dist8(), 1);
|
|
|
93640
93659
|
* Copyright(c) 2015 Douglas Christopher Wilson
|
|
93641
93660
|
* MIT Licensed
|
|
93642
93661
|
*/
|
|
93643
|
-
var db =
|
|
93662
|
+
var db = require_db();
|
|
93644
93663
|
var extname = __require("path").extname;
|
|
93645
93664
|
var $extensions = Object.create(null);
|
|
93646
93665
|
var $lookup = lookup;
|
|
@@ -103071,9 +103090,9 @@ function updateFrontmatterWithAtUri(rawContent, atUri) {
|
|
|
103071
103090
|
${afterEnd}`;
|
|
103072
103091
|
}
|
|
103073
103092
|
function stripMarkdownForText(markdown) {
|
|
103074
|
-
return markdown.replace(/#{1,6}\s/g, "").replace(/\*\*([^*]+)\*\*/g, "$1").replace(/\*([^*]+)\*/g, "$1").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/`{3}[\s\S]*?`{3}/g, "").replace(/`([^`]+)`/g, "$1").replace(
|
|
103093
|
+
return markdown.replace(/#{1,6}\s/g, "").replace(/\*\*([^*]+)\*\*/g, "$1").replace(/\*([^*]+)\*/g, "$1").replace(/!\[.*?\]\(.*?\)/g, "").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/`{3}[\s\S]*?`{3}/g, "").replace(/`([^`]+)`/g, "$1").replace(/\n{3,}/g, `
|
|
103075
103094
|
|
|
103076
|
-
`).trim();
|
|
103095
|
+
`).replace(/(~{1,2})([^~]+?)\1/g, "$2").replace(/<!--[\s\S]*?-->/g, "").replace(/^\s*\[\^[^\]]+\]:[^\r\n]*(?:\r?\n[ \t]+[^\r\n]*)*/gm, "").replace(/\[\^\S+\]/g, "").trim();
|
|
103077
103096
|
}
|
|
103078
103097
|
|
|
103079
103098
|
// src/lib/oauth-client.ts
|
|
@@ -103248,6 +103267,7 @@ function isAppPasswordCredentials(creds) {
|
|
|
103248
103267
|
}
|
|
103249
103268
|
|
|
103250
103269
|
// src/lib/atproto.ts
|
|
103270
|
+
var COVER_IMAGE_MAX_SIZE = 1024 * 1024 - 1;
|
|
103251
103271
|
function isDocumentRecord(value) {
|
|
103252
103272
|
if (!value || typeof value !== "object")
|
|
103253
103273
|
return false;
|
|
@@ -103337,13 +103357,14 @@ async function uploadImage(agent, imagePath) {
|
|
|
103337
103357
|
const response = await agent.com.atproto.repo.uploadBlob(new Uint8Array(imageBuffer), {
|
|
103338
103358
|
encoding: mimeType
|
|
103339
103359
|
});
|
|
103360
|
+
const blob = response.data.blob;
|
|
103340
103361
|
return {
|
|
103341
103362
|
$type: "blob",
|
|
103342
103363
|
ref: {
|
|
103343
|
-
$link:
|
|
103364
|
+
$link: blob.ref.toString()
|
|
103344
103365
|
},
|
|
103345
|
-
mimeType,
|
|
103346
|
-
size:
|
|
103366
|
+
mimeType: blob.mimeType,
|
|
103367
|
+
size: blob.size
|
|
103347
103368
|
};
|
|
103348
103369
|
} catch (error) {
|
|
103349
103370
|
console.error(`Error uploading image ${imagePath}:`, error);
|
|
@@ -103379,7 +103400,7 @@ async function resolveImagePath(ogImage, imagesDir, contentDir) {
|
|
|
103379
103400
|
return contentRelative;
|
|
103380
103401
|
}
|
|
103381
103402
|
}
|
|
103382
|
-
return
|
|
103403
|
+
return;
|
|
103383
103404
|
}
|
|
103384
103405
|
async function createDocument(agent, post, config, coverImage) {
|
|
103385
103406
|
const postPath = resolvePostPath(post, config.pathPrefix, config.pathTemplate);
|
|
@@ -103399,7 +103420,7 @@ async function createDocument(agent, post, config, coverImage) {
|
|
|
103399
103420
|
title: post.frontmatter.title,
|
|
103400
103421
|
site: config.publicationUri,
|
|
103401
103422
|
path: postPath,
|
|
103402
|
-
textContent
|
|
103423
|
+
textContent,
|
|
103403
103424
|
publishedAt: publishDate.toISOString(),
|
|
103404
103425
|
canonicalUrl: `${config.siteUrl}${postPath}`
|
|
103405
103426
|
};
|
|
@@ -103420,7 +103441,10 @@ async function createDocument(agent, post, config, coverImage) {
|
|
|
103420
103441
|
collection: "site.standard.document",
|
|
103421
103442
|
record
|
|
103422
103443
|
});
|
|
103423
|
-
return
|
|
103444
|
+
return {
|
|
103445
|
+
cid: response.data.cid,
|
|
103446
|
+
uri: response.data.uri
|
|
103447
|
+
};
|
|
103424
103448
|
}
|
|
103425
103449
|
async function updateDocument(agent, post, atUri, config, coverImage) {
|
|
103426
103450
|
const uriMatch = atUri.match(/^at:\/\/([^/]+)\/([^/]+)\/(.+)$/);
|
|
@@ -103449,7 +103473,7 @@ async function updateDocument(agent, post, atUri, config, coverImage) {
|
|
|
103449
103473
|
title: post.frontmatter.title,
|
|
103450
103474
|
site: config.publicationUri,
|
|
103451
103475
|
path: postPath,
|
|
103452
|
-
textContent
|
|
103476
|
+
textContent,
|
|
103453
103477
|
publishedAt: publishDate.toISOString(),
|
|
103454
103478
|
canonicalUrl: `${config.siteUrl}${postPath}`
|
|
103455
103479
|
};
|
|
@@ -103465,12 +103489,16 @@ async function updateDocument(agent, post, atUri, config, coverImage) {
|
|
|
103465
103489
|
if (post.frontmatter.tags && post.frontmatter.tags.length > 0) {
|
|
103466
103490
|
record.tags = post.frontmatter.tags;
|
|
103467
103491
|
}
|
|
103468
|
-
await agent.com.atproto.repo.putRecord({
|
|
103492
|
+
const response = await agent.com.atproto.repo.putRecord({
|
|
103469
103493
|
repo: agent.did,
|
|
103470
103494
|
collection,
|
|
103471
103495
|
rkey,
|
|
103472
103496
|
record
|
|
103473
103497
|
});
|
|
103498
|
+
return {
|
|
103499
|
+
cid: response.data.cid,
|
|
103500
|
+
uri: response.data.uri
|
|
103501
|
+
};
|
|
103474
103502
|
}
|
|
103475
103503
|
function parseAtUri(atUri) {
|
|
103476
103504
|
const match2 = atUri.match(/^at:\/\/([^/]+)\/([^/]+)\/(.+)$/);
|
|
@@ -103536,7 +103564,10 @@ async function createPublication(agent, options) {
|
|
|
103536
103564
|
collection: "site.standard.publication",
|
|
103537
103565
|
record
|
|
103538
103566
|
});
|
|
103539
|
-
return
|
|
103567
|
+
return {
|
|
103568
|
+
cid: response.data.cid,
|
|
103569
|
+
uri: response.data.uri
|
|
103570
|
+
};
|
|
103540
103571
|
}
|
|
103541
103572
|
async function getPublication(agent, publicationUri) {
|
|
103542
103573
|
const parsed = parseAtUri(publicationUri);
|
|
@@ -103624,6 +103655,8 @@ async function createBlueskyPost(agent, options) {
|
|
|
103624
103655
|
description,
|
|
103625
103656
|
bskyPost,
|
|
103626
103657
|
canonicalUrl,
|
|
103658
|
+
documentRef,
|
|
103659
|
+
publicationRef,
|
|
103627
103660
|
coverImage,
|
|
103628
103661
|
publishedAt
|
|
103629
103662
|
} = options;
|
|
@@ -103661,7 +103694,19 @@ ${truncatedDesc}`;
|
|
|
103661
103694
|
external: {
|
|
103662
103695
|
uri: canonicalUrl,
|
|
103663
103696
|
title: title.substring(0, 500),
|
|
103664
|
-
description: (description || "").substring(0, 1000)
|
|
103697
|
+
description: (description || "").substring(0, 1000),
|
|
103698
|
+
associatedRefs: [
|
|
103699
|
+
{
|
|
103700
|
+
$type: "com.atproto.repo.strongRef",
|
|
103701
|
+
cid: documentRef.cid,
|
|
103702
|
+
uri: documentRef.uri
|
|
103703
|
+
},
|
|
103704
|
+
{
|
|
103705
|
+
$type: "com.atproto.repo.strongRef",
|
|
103706
|
+
cid: publicationRef.cid,
|
|
103707
|
+
uri: publicationRef.uri
|
|
103708
|
+
}
|
|
103709
|
+
]
|
|
103665
103710
|
}
|
|
103666
103711
|
};
|
|
103667
103712
|
if (coverImage) {
|
|
@@ -104236,13 +104281,14 @@ var initCommand = import_cmd_ts3.command({
|
|
|
104236
104281
|
}, { onCancel });
|
|
104237
104282
|
s.start("Creating publication...");
|
|
104238
104283
|
try {
|
|
104239
|
-
|
|
104284
|
+
const publicationRef = await createPublication(agent, {
|
|
104240
104285
|
url: siteConfig.siteUrl,
|
|
104241
104286
|
name: publicationConfig.name,
|
|
104242
104287
|
description: publicationConfig.description || undefined,
|
|
104243
104288
|
iconPath: publicationConfig.iconPath || undefined,
|
|
104244
104289
|
showInDiscover: publicationConfig.showInDiscover
|
|
104245
104290
|
});
|
|
104291
|
+
publicationUri = publicationRef.uri;
|
|
104246
104292
|
s.stop(`Publication created: ${publicationUri}`);
|
|
104247
104293
|
} catch (error) {
|
|
104248
104294
|
s.stop("Failed to create publication");
|
|
@@ -104300,9 +104346,9 @@ var initCommand = import_cmd_ts3.command({
|
|
|
104300
104346
|
siteUrl: siteConfig.siteUrl,
|
|
104301
104347
|
contentDir: siteConfig.contentDir || "./content",
|
|
104302
104348
|
imagesDir: siteConfig.imagesDir || undefined,
|
|
104303
|
-
publicDir: siteConfig.publicDir ||
|
|
104304
|
-
outputDir: siteConfig.outputDir ||
|
|
104305
|
-
pathPrefix: siteConfig.pathPrefix ??
|
|
104349
|
+
publicDir: siteConfig.publicDir || DEFAULT_PUBLISHER_CONFIG.publicDir,
|
|
104350
|
+
outputDir: siteConfig.outputDir || DEFAULT_PUBLISHER_CONFIG.outputDir,
|
|
104351
|
+
pathPrefix: siteConfig.pathPrefix ?? DEFAULT_PUBLISHER_CONFIG.pathPrefix,
|
|
104306
104352
|
publicationUri,
|
|
104307
104353
|
pdsUrl,
|
|
104308
104354
|
frontmatter: frontmatterMapping,
|
|
@@ -104312,7 +104358,7 @@ var initCommand = import_cmd_ts3.command({
|
|
|
104312
104358
|
const configPath = path8.join(process.cwd(), "sequoia.json");
|
|
104313
104359
|
await fs7.writeFile(configPath, configContent);
|
|
104314
104360
|
R2.success(`Configuration saved to ${configPath}`);
|
|
104315
|
-
const publicDir = siteConfig.publicDir ||
|
|
104361
|
+
const publicDir = siteConfig.publicDir || DEFAULT_PUBLISHER_CONFIG.publicDir;
|
|
104316
104362
|
const resolvedPublicDir = path8.isAbsolute(publicDir) ? publicDir : path8.join(process.cwd(), publicDir);
|
|
104317
104363
|
const wellKnownDir = path8.join(resolvedPublicDir, ".well-known");
|
|
104318
104364
|
const wellKnownPath = path8.join(wellKnownDir, "site.standard.publication");
|
|
@@ -104370,7 +104416,7 @@ var injectCommand = import_cmd_ts4.command({
|
|
|
104370
104416
|
}
|
|
104371
104417
|
const config = await loadConfig(configPath);
|
|
104372
104418
|
const configDir = path9.dirname(configPath);
|
|
104373
|
-
const outputDir = outputDirArg || config.outputDir ||
|
|
104419
|
+
const outputDir = outputDirArg || config.outputDir || DEFAULT_PUBLISHER_CONFIG.outputDir;
|
|
104374
104420
|
const resolvedOutputDir = path9.isAbsolute(outputDir) ? outputDir : path9.join(configDir, outputDir);
|
|
104375
104421
|
R2.info(`Scanning for HTML files in: ${resolvedOutputDir}`);
|
|
104376
104422
|
const state = await loadState(configDir);
|
|
@@ -104426,26 +104472,20 @@ var injectCommand = import_cmd_ts4.command({
|
|
|
104426
104472
|
continue;
|
|
104427
104473
|
}
|
|
104428
104474
|
let content = await fs8.readFile(htmlPath, "utf-8");
|
|
104429
|
-
const
|
|
104430
|
-
|
|
104431
|
-
|
|
104432
|
-
|
|
104433
|
-
|
|
104434
|
-
|
|
104435
|
-
|
|
104436
|
-
|
|
104437
|
-
|
|
104438
|
-
|
|
104439
|
-
|
|
104440
|
-
|
|
104441
|
-
|
|
104442
|
-
R2.message(` ${linkTag}`);
|
|
104443
|
-
injectedCount++;
|
|
104444
|
-
continue;
|
|
104475
|
+
const injected = injectLinkTags(dryRun, relativePath, content, atUri, config.publicationUri);
|
|
104476
|
+
switch (injected) {
|
|
104477
|
+
case 0 /* AlreadyPresent */:
|
|
104478
|
+
alreadyHasCount++;
|
|
104479
|
+
continue;
|
|
104480
|
+
case 1 /* Skipped */:
|
|
104481
|
+
skippedCount++;
|
|
104482
|
+
continue;
|
|
104483
|
+
case 2 /* Faked */:
|
|
104484
|
+
injectedCount++;
|
|
104485
|
+
continue;
|
|
104486
|
+
default:
|
|
104487
|
+
content = injected;
|
|
104445
104488
|
}
|
|
104446
|
-
const indent = " ";
|
|
104447
|
-
content = content.slice(0, headCloseIndex) + `${indent}${linkTag}
|
|
104448
|
-
${indent}` + content.slice(headCloseIndex);
|
|
104449
104489
|
await fs8.writeFile(htmlPath, content);
|
|
104450
104490
|
R2.success(` Injected into: ${relativePath}`);
|
|
104451
104491
|
injectedCount++;
|
|
@@ -104464,6 +104504,47 @@ Tip: Skipped files had no matching published post. This is normal for non-post p
|
|
|
104464
104504
|
}
|
|
104465
104505
|
}
|
|
104466
104506
|
});
|
|
104507
|
+
function injectLinkTags(dryRun, relativePath, content, atUri, publicationUri) {
|
|
104508
|
+
let documentLinkTag = `<link rel="site.standard.document" href="${atUri}">`;
|
|
104509
|
+
let publicationLinkTag = `<link rel="site.standard.publication" href="${publicationUri}">`;
|
|
104510
|
+
if (content.includes('rel="site.standard.document"')) {
|
|
104511
|
+
documentLinkTag = undefined;
|
|
104512
|
+
}
|
|
104513
|
+
if (content.includes('rel="site.standard.publication"')) {
|
|
104514
|
+
publicationLinkTag = undefined;
|
|
104515
|
+
}
|
|
104516
|
+
if (!documentLinkTag && !publicationLinkTag) {
|
|
104517
|
+
return 0 /* AlreadyPresent */;
|
|
104518
|
+
}
|
|
104519
|
+
const headCloseIndex = content.indexOf("</head>");
|
|
104520
|
+
if (headCloseIndex === -1) {
|
|
104521
|
+
R2.warn(` No </head> found in ${relativePath}, skipping`);
|
|
104522
|
+
return 1 /* Skipped */;
|
|
104523
|
+
}
|
|
104524
|
+
if (dryRun) {
|
|
104525
|
+
R2.message(` Would inject into: ${relativePath}`);
|
|
104526
|
+
if (documentLinkTag) {
|
|
104527
|
+
R2.message(` ${documentLinkTag}`);
|
|
104528
|
+
}
|
|
104529
|
+
if (publicationLinkTag) {
|
|
104530
|
+
R2.message(` ${publicationLinkTag}`);
|
|
104531
|
+
}
|
|
104532
|
+
return 2 /* Faked */;
|
|
104533
|
+
}
|
|
104534
|
+
const indent = " ";
|
|
104535
|
+
const after = content.slice(headCloseIndex);
|
|
104536
|
+
content = content.slice(0, headCloseIndex);
|
|
104537
|
+
if (documentLinkTag) {
|
|
104538
|
+
content += `${indent}${documentLinkTag}
|
|
104539
|
+
${indent}`;
|
|
104540
|
+
}
|
|
104541
|
+
if (publicationLinkTag) {
|
|
104542
|
+
content += `${indent}${publicationLinkTag}
|
|
104543
|
+
${indent}`;
|
|
104544
|
+
}
|
|
104545
|
+
content += after;
|
|
104546
|
+
return content;
|
|
104547
|
+
}
|
|
104467
104548
|
|
|
104468
104549
|
// src/commands/login.ts
|
|
104469
104550
|
import * as http from "node:http";
|
|
@@ -104725,6 +104806,9 @@ async function syncStateFromPDS(agent, config, configDir, options = {}) {
|
|
|
104725
104806
|
postsByPath.set(postPath, post);
|
|
104726
104807
|
}
|
|
104727
104808
|
const state = await loadState(configDir);
|
|
104809
|
+
if (!state.publication) {
|
|
104810
|
+
state.publication = await syncPublication(agent, config.publicationUri, quiet);
|
|
104811
|
+
}
|
|
104728
104812
|
let matchedCount = 0;
|
|
104729
104813
|
let unmatchedCount = 0;
|
|
104730
104814
|
let frontmatterUpdatesApplied = 0;
|
|
@@ -104817,6 +104901,19 @@ Dry run complete. No changes made.`);
|
|
|
104817
104901
|
}
|
|
104818
104902
|
return { state, matchedCount, unmatchedCount, frontmatterUpdatesApplied };
|
|
104819
104903
|
}
|
|
104904
|
+
async function syncPublication(agent, publicationUri, quiet) {
|
|
104905
|
+
const publicationRef = await getPublication(agent, publicationUri);
|
|
104906
|
+
if (!publicationRef) {
|
|
104907
|
+
if (!quiet) {
|
|
104908
|
+
R2.error(`Publication ${publicationUri} not found. Update your publication record and try again.`);
|
|
104909
|
+
}
|
|
104910
|
+
process.exit(1);
|
|
104911
|
+
}
|
|
104912
|
+
return {
|
|
104913
|
+
cid: publicationRef.cid,
|
|
104914
|
+
uri: publicationRef.uri
|
|
104915
|
+
};
|
|
104916
|
+
}
|
|
104820
104917
|
|
|
104821
104918
|
// src/commands/publish.ts
|
|
104822
104919
|
var publishCommand = import_cmd_ts6.command({
|
|
@@ -104842,7 +104939,7 @@ var publishCommand = import_cmd_ts6.command({
|
|
|
104842
104939
|
handler: async ({ force, dryRun, verbose }) => {
|
|
104843
104940
|
const configPath = await findConfig();
|
|
104844
104941
|
if (!configPath) {
|
|
104845
|
-
R2.error(
|
|
104942
|
+
R2.error(`No ${CONFIG_FILENAME} found. Run 'sequoia init' first.`);
|
|
104846
104943
|
process.exit(1);
|
|
104847
104944
|
}
|
|
104848
104945
|
const config = await loadConfig(configPath);
|
|
@@ -104901,7 +104998,7 @@ var publishCommand = import_cmd_ts6.command({
|
|
|
104901
104998
|
let state = await loadState(configDir);
|
|
104902
104999
|
const s = Ie();
|
|
104903
105000
|
let agent;
|
|
104904
|
-
if (config.autoSync !== false && Object.keys(state.posts).length === 0 && !dryRun) {
|
|
105001
|
+
if (config.autoSync !== false && (!state.publication || Object.keys(state.posts).length === 0) && !dryRun) {
|
|
104905
105002
|
const connectingTo = credentials.type === "oauth" ? credentials.handle : credentials.pdsUrl;
|
|
104906
105003
|
s.start(`Connecting as ${connectingTo}...`);
|
|
104907
105004
|
try {
|
|
@@ -104979,10 +105076,14 @@ ${postsToPublish.length} posts to publish:
|
|
|
104979
105076
|
const maxAgeDays = config.bluesky?.maxAgeDays ?? 7;
|
|
104980
105077
|
const cutoffDate = new Date;
|
|
104981
105078
|
cutoffDate.setDate(cutoffDate.getDate() - maxAgeDays);
|
|
105079
|
+
let isValid = true;
|
|
104982
105080
|
for (const { post, action, reason } of postsToPublish) {
|
|
104983
105081
|
const icon = action === "create" ? "+" : "~";
|
|
104984
105082
|
const relativeFilePath = path12.relative(configDir, post.filePath);
|
|
104985
105083
|
const existingBskyPostRef = state.posts[relativeFilePath]?.bskyPostRef;
|
|
105084
|
+
if (post.frontmatter.ogImage) {
|
|
105085
|
+
post.coverImagePath = await resolveImagePath(post.frontmatter.ogImage, imagesDir, contentDir);
|
|
105086
|
+
}
|
|
104986
105087
|
let bskyNote = "";
|
|
104987
105088
|
if (blueskyEnabled) {
|
|
104988
105089
|
if (existingBskyPostRef) {
|
|
@@ -105003,6 +105104,11 @@ ${postsToPublish.length} posts to publish:
|
|
|
105003
105104
|
${config.siteUrl}${postPath}`;
|
|
105004
105105
|
}
|
|
105005
105106
|
R2.message(` ${icon} ${post.frontmatter.title} (${reason})${bskyNote}${postUrl}`);
|
|
105107
|
+
const postValid = await validatePost(post);
|
|
105108
|
+
isValid &&= postValid;
|
|
105109
|
+
}
|
|
105110
|
+
if (!isValid) {
|
|
105111
|
+
return;
|
|
105006
105112
|
}
|
|
105007
105113
|
if (dryRun) {
|
|
105008
105114
|
if (blueskyEnabled) {
|
|
@@ -105025,6 +105131,9 @@ Dry run complete. No changes made.`);
|
|
|
105025
105131
|
process.exit(1);
|
|
105026
105132
|
}
|
|
105027
105133
|
}
|
|
105134
|
+
if (!state.publication) {
|
|
105135
|
+
state.publication = await syncPublication(agent, config.publicationUri, false);
|
|
105136
|
+
}
|
|
105028
105137
|
let publishedCount = 0;
|
|
105029
105138
|
let updatedCount = 0;
|
|
105030
105139
|
let errorCount = 0;
|
|
@@ -105033,35 +105142,32 @@ Dry run complete. No changes made.`);
|
|
|
105033
105142
|
s.start(`Publishing: ${post.frontmatter.title}`);
|
|
105034
105143
|
try {
|
|
105035
105144
|
let coverImage;
|
|
105036
|
-
if (post.
|
|
105037
|
-
|
|
105038
|
-
|
|
105039
|
-
|
|
105040
|
-
|
|
105041
|
-
if (coverImage) {
|
|
105042
|
-
R2.info(` Uploaded image blob: ${coverImage.ref.$link}`);
|
|
105043
|
-
}
|
|
105044
|
-
} else {
|
|
105045
|
-
R2.warn(` Cover image not found: ${post.frontmatter.ogImage}`);
|
|
105145
|
+
if (post.coverImagePath) {
|
|
105146
|
+
R2.info(` Uploading cover image: ${path12.basename(post.coverImagePath)}`);
|
|
105147
|
+
coverImage = await uploadImage(agent, post.coverImagePath);
|
|
105148
|
+
if (coverImage) {
|
|
105149
|
+
R2.info(` Uploaded image blob: ${coverImage.ref.$link}`);
|
|
105046
105150
|
}
|
|
105151
|
+
} else {
|
|
105152
|
+
R2.warn(` Cover image not found: ${post.frontmatter.ogImage}`);
|
|
105047
105153
|
}
|
|
105048
|
-
let
|
|
105154
|
+
let documentRef;
|
|
105049
105155
|
let contentForHash;
|
|
105050
105156
|
let bskyPostRef;
|
|
105051
105157
|
const relativeFilePath = path12.relative(configDir, post.filePath);
|
|
105052
105158
|
const existingBskyPostRef = state.posts[relativeFilePath]?.bskyPostRef;
|
|
105053
105159
|
if (action === "create") {
|
|
105054
|
-
|
|
105055
|
-
s.stop(`Created: ${
|
|
105056
|
-
const updatedContent = updateFrontmatterWithAtUri(post.rawContent,
|
|
105160
|
+
documentRef = await createDocument(agent, post, config, coverImage);
|
|
105161
|
+
s.stop(`Created: ${documentRef.uri}`);
|
|
105162
|
+
const updatedContent = updateFrontmatterWithAtUri(post.rawContent, documentRef.uri);
|
|
105057
105163
|
await fs15.writeFile(post.filePath, updatedContent);
|
|
105058
105164
|
R2.info(` Updated frontmatter in ${path12.basename(post.filePath)}`);
|
|
105059
105165
|
contentForHash = updatedContent;
|
|
105060
105166
|
publishedCount++;
|
|
105061
105167
|
} else {
|
|
105062
|
-
atUri = post.frontmatter.atUri;
|
|
105063
|
-
await updateDocument(agent, post, atUri, config, coverImage);
|
|
105064
|
-
s.stop(`Updated: ${
|
|
105168
|
+
const atUri = post.frontmatter.atUri;
|
|
105169
|
+
documentRef = await updateDocument(agent, post, atUri, config, coverImage);
|
|
105170
|
+
s.stop(`Updated: ${documentRef.uri}`);
|
|
105065
105171
|
contentForHash = post.rawContent;
|
|
105066
105172
|
updatedCount++;
|
|
105067
105173
|
}
|
|
@@ -105081,10 +105187,12 @@ Dry run complete. No changes made.`);
|
|
|
105081
105187
|
description: post.frontmatter.description,
|
|
105082
105188
|
bskyPost: post.frontmatter.bskyPost,
|
|
105083
105189
|
canonicalUrl,
|
|
105190
|
+
documentRef,
|
|
105191
|
+
publicationRef: state.publication,
|
|
105084
105192
|
coverImage,
|
|
105085
105193
|
publishedAt: post.frontmatter.publishDate
|
|
105086
105194
|
});
|
|
105087
|
-
await addBskyPostRefToDocument(agent,
|
|
105195
|
+
await addBskyPostRefToDocument(agent, documentRef.uri, bskyPostRef);
|
|
105088
105196
|
R2.info(` Created Bluesky post: ${bskyPostRef.uri}`);
|
|
105089
105197
|
bskyPostCount++;
|
|
105090
105198
|
} catch (bskyError) {
|
|
@@ -105097,7 +105205,7 @@ Dry run complete. No changes made.`);
|
|
|
105097
105205
|
const contentHash = await getContentHash(contentForHash);
|
|
105098
105206
|
state.posts[relativeFilePath] = {
|
|
105099
105207
|
contentHash,
|
|
105100
|
-
atUri,
|
|
105208
|
+
atUri: documentRef.uri,
|
|
105101
105209
|
lastPublished: new Date().toISOString(),
|
|
105102
105210
|
slug: post.slug,
|
|
105103
105211
|
bskyPostRef
|
|
@@ -105122,6 +105230,16 @@ Dry run complete. No changes made.`);
|
|
|
105122
105230
|
}
|
|
105123
105231
|
}
|
|
105124
105232
|
});
|
|
105233
|
+
async function validatePost(post) {
|
|
105234
|
+
if (post.coverImagePath) {
|
|
105235
|
+
const stat3 = await fs15.stat(post.coverImagePath);
|
|
105236
|
+
if (stat3.size >= COVER_IMAGE_MAX_SIZE) {
|
|
105237
|
+
R2.error(` Cover image "${post.coverImagePath}" must be less than 1MB`);
|
|
105238
|
+
return false;
|
|
105239
|
+
}
|
|
105240
|
+
}
|
|
105241
|
+
return true;
|
|
105242
|
+
}
|
|
105125
105243
|
|
|
105126
105244
|
// src/commands/sync.ts
|
|
105127
105245
|
var import_cmd_ts7 = __toESM(require_cjs(), 1);
|
|
@@ -105261,7 +105379,7 @@ async function updateConfigFlow(config, configPath) {
|
|
|
105261
105379
|
const configSummary = [
|
|
105262
105380
|
`Site URL: ${config.siteUrl}`,
|
|
105263
105381
|
`Content Dir: ${config.contentDir}`,
|
|
105264
|
-
`Path Prefix: ${config.pathPrefix ??
|
|
105382
|
+
`Path Prefix: ${config.pathPrefix ?? DEFAULT_PUBLISHER_CONFIG.pathPrefix}`,
|
|
105265
105383
|
`Publication URI: ${config.publicationUri}`,
|
|
105266
105384
|
config.imagesDir ? `Images Dir: ${config.imagesDir}` : null,
|
|
105267
105385
|
config.outputDir ? `Output Dir: ${config.outputDir}` : null,
|
|
@@ -105345,7 +105463,7 @@ async function editSiteSettings(config) {
|
|
|
105345
105463
|
}));
|
|
105346
105464
|
const pathPrefix = exitOnCancel(await Qt({
|
|
105347
105465
|
message: "URL path prefix for posts:",
|
|
105348
|
-
initialValue: config.pathPrefix ??
|
|
105466
|
+
initialValue: config.pathPrefix ?? DEFAULT_PUBLISHER_CONFIG.pathPrefix
|
|
105349
105467
|
}));
|
|
105350
105468
|
return {
|
|
105351
105469
|
...config,
|
|
@@ -105368,11 +105486,11 @@ async function editDirectories(config) {
|
|
|
105368
105486
|
}));
|
|
105369
105487
|
const publicDir = exitOnCancel(await Qt({
|
|
105370
105488
|
message: "Public/static directory:",
|
|
105371
|
-
initialValue: config.publicDir ||
|
|
105489
|
+
initialValue: config.publicDir || DEFAULT_PUBLISHER_CONFIG.publicDir
|
|
105372
105490
|
}));
|
|
105373
105491
|
const outputDir = exitOnCancel(await Qt({
|
|
105374
105492
|
message: "Build output directory:",
|
|
105375
|
-
initialValue: config.outputDir ||
|
|
105493
|
+
initialValue: config.outputDir || DEFAULT_PUBLISHER_CONFIG.outputDir
|
|
105376
105494
|
}));
|
|
105377
105495
|
return {
|
|
105378
105496
|
...config,
|
|
@@ -105459,7 +105577,7 @@ async function editAdvanced(config) {
|
|
|
105459
105577
|
}));
|
|
105460
105578
|
const publishContent = exitOnCancel(await Mt2({
|
|
105461
105579
|
message: "Publish the post content on the standard.site document?",
|
|
105462
|
-
initialValue: config.publishContent ??
|
|
105580
|
+
initialValue: config.publishContent ?? DEFAULT_PUBLISHER_CONFIG.publishContent
|
|
105463
105581
|
}));
|
|
105464
105582
|
const textContentField = exitOnCancel(await Qt({
|
|
105465
105583
|
message: "Frontmatter field for textContent (leave empty to use markdown body):",
|
|
@@ -105474,7 +105592,7 @@ async function editAdvanced(config) {
|
|
|
105474
105592
|
removeIndexFromSlug: removeIndexFromSlug || undefined,
|
|
105475
105593
|
stripDatePrefix: stripDatePrefix || undefined,
|
|
105476
105594
|
textContentField: textContentField || undefined,
|
|
105477
|
-
publishContent: publishContent ??
|
|
105595
|
+
publishContent: publishContent ?? DEFAULT_PUBLISHER_CONFIG.publishContent
|
|
105478
105596
|
};
|
|
105479
105597
|
}
|
|
105480
105598
|
async function editBluesky(config) {
|
|
@@ -105670,7 +105788,7 @@ Publish evergreen content to the ATmosphere
|
|
|
105670
105788
|
|
|
105671
105789
|
> https://tangled.org/stevedylan.dev/sequoia
|
|
105672
105790
|
`,
|
|
105673
|
-
version: "0.5.
|
|
105791
|
+
version: "0.5.7",
|
|
105674
105792
|
cmds: {
|
|
105675
105793
|
add: addCommand,
|
|
105676
105794
|
auth: authCommand,
|
|
@@ -105683,3 +105801,6 @@ Publish evergreen content to the ATmosphere
|
|
|
105683
105801
|
}
|
|
105684
105802
|
});
|
|
105685
105803
|
import_cmd_ts9.run(app, process.argv.slice(2));
|
|
105804
|
+
|
|
105805
|
+
//# debugId=0DA3B1F9984712F664756E2164756E21
|
|
105806
|
+
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sequoia-cli",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"sequoia": "dist/index.js"
|
|
@@ -17,13 +17,13 @@
|
|
|
17
17
|
"scripts": {
|
|
18
18
|
"lint": "biome lint --write",
|
|
19
19
|
"format": "biome format --write",
|
|
20
|
-
"build": "bun build src/index.ts --target node --outdir dist && mkdir -p dist/components && cp src/components/*.js dist/components/",
|
|
20
|
+
"build": "bun build src/index.ts --target node --outdir dist --sourcemap && mkdir -p dist/components && cp src/components/*.js dist/components/",
|
|
21
21
|
"test": "bun test",
|
|
22
22
|
"dev": "bun run build && bun link",
|
|
23
|
-
"deploy": "bun run build && bun publish"
|
|
23
|
+
"deploy": "bun run build && rm -f dist/*.map && bun publish"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@biomejs/biome": "
|
|
26
|
+
"@biomejs/biome": "2.3.6",
|
|
27
27
|
"@types/js-yaml": "^4.0.9",
|
|
28
28
|
"@types/mime-types": "^3.0.1",
|
|
29
29
|
"@types/node": "^20"
|