daily-soup-widget 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -4
- package/dist/embed.cjs.js +222 -139
- package/dist/embed.cjs.js.map +4 -4
- package/dist/embed.esm.js +221 -138
- package/dist/embed.esm.js.map +4 -4
- package/dist/embed.js +69 -27
- package/dist/embed.js.map +4 -4
- package/dist/schedule-zh.json +6 -5
- package/dist/types/styles.d.ts +1 -1
- package/package.json +3 -6
package/README.md
CHANGED
|
@@ -51,7 +51,7 @@ export default function Page() {
|
|
|
51
51
|
|
|
52
52
|
The component is SSR-safe: it emits a placeholder during render and hydrates the widget inside `useEffect`. No `window` access at module top-level.
|
|
53
53
|
|
|
54
|
-
## Install —
|
|
54
|
+
## Install — imperative API (NPM)
|
|
55
55
|
|
|
56
56
|
```ts
|
|
57
57
|
import { mount } from 'daily-soup-widget';
|
|
@@ -61,6 +61,9 @@ const handle = mount(host, { lang: 'zh', theme: 'auto' });
|
|
|
61
61
|
// later: handle.destroy();
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
+
Same React peer requirement as the `<DailySoup>` component — install React + ReactDOM
|
|
65
|
+
(>=18) alongside.
|
|
66
|
+
|
|
64
67
|
---
|
|
65
68
|
|
|
66
69
|
## Configuration
|
|
@@ -130,10 +133,10 @@ scripts/
|
|
|
130
133
|
build-schedule.ts content/*.md → dist/schedule-zh.json
|
|
131
134
|
build-bundle.ts esbuild UMD + ESM + CJS bundles
|
|
132
135
|
src/
|
|
133
|
-
embed.ts UMD entry (CDN, auto-mounts)
|
|
136
|
+
embed.ts UMD entry (CDN, auto-mounts; React bundled in)
|
|
134
137
|
index.ts ESM entry (NPM)
|
|
135
|
-
component.tsx React wrapper
|
|
136
|
-
widget.
|
|
138
|
+
component.tsx React wrapper around mount()
|
|
139
|
+
widget.tsx React widget core (Shadow DOM root, render, fetch)
|
|
137
140
|
theme.ts auto/light/dark resolution + system-theme listener
|
|
138
141
|
i18n.ts UI strings (zh)
|
|
139
142
|
share.ts copy / X / LINE share builders
|
|
@@ -165,6 +168,20 @@ npm run build # full prod build (schedule + bundle + types + Next.j
|
|
|
165
168
|
2. `npm run build:schedule` — extends the schedule, preserving past dates.
|
|
166
169
|
3. Commit + PR. Merge triggers the GH Action to redeploy.
|
|
167
170
|
|
|
171
|
+
### Releasing a new npm version
|
|
172
|
+
|
|
173
|
+
1. Bump `version` in `package.json`, commit, merge to `main`.
|
|
174
|
+
2. Tag and push: `git tag v0.2.3 && git push --tags`.
|
|
175
|
+
3. `.github/workflows/publish.yml` runs `npm test` + `typecheck` + `build:lib` + `npm publish` against the `NPM_TOKEN` repo secret. No local `npm login` ever needed.
|
|
176
|
+
|
|
177
|
+
The `NPM_TOKEN` must be a granular automation token scoped to read & write on `daily-soup-widget`.
|
|
178
|
+
|
|
179
|
+
**Verifying the auth chain without a version bump:** trigger `gh workflow run publish.yml -R mshmwr/daily-soup-widget` against the current `main`. The workflow runs all pre-publish steps and then attempts `npm publish` on the already-published version, which the registry rejects with `E403 You cannot publish over the previously published versions: <version>`. Reaching that exact error confirms the `NPM_TOKEN` secret is present and has publish scope — only the uniqueness check rejected. Use this whenever you rotate the token or change the workflow, instead of minting a throwaway version. Note: `npm whoami` on a granular automation token returns 401 and is not a valid auth-chain test; use `npm publish --dry-run` (locally) or this `workflow_dispatch` trick (in CI) instead.
|
|
180
|
+
|
|
181
|
+
### Auditing source URLs
|
|
182
|
+
|
|
183
|
+
`npm run check:source-urls` HEADs every `sourceUrl` in `content/quotes/**` and flags pages whose body matches `資料已刪除|404 Not Found`. Run before each release; null any broken URLs in frontmatter (the widget renders source as plain text when `sourceUrl` is empty).
|
|
184
|
+
|
|
168
185
|
---
|
|
169
186
|
|
|
170
187
|
## Browser support
|
package/dist/embed.cjs.js
CHANGED
|
@@ -26,6 +26,11 @@ __export(index_exports, {
|
|
|
26
26
|
});
|
|
27
27
|
module.exports = __toCommonJS(index_exports);
|
|
28
28
|
|
|
29
|
+
// src/widget.tsx
|
|
30
|
+
var import_react = require("react");
|
|
31
|
+
var import_client = require("react-dom/client");
|
|
32
|
+
var import_react_dom = require("react-dom");
|
|
33
|
+
|
|
29
34
|
// src/i18n.ts
|
|
30
35
|
var STRINGS = {
|
|
31
36
|
zh: {
|
|
@@ -155,9 +160,9 @@ var WIDGET_STYLES = `
|
|
|
155
160
|
align-items: center;
|
|
156
161
|
gap: 0.25rem;
|
|
157
162
|
}
|
|
158
|
-
.ds-btn:hover {
|
|
163
|
+
.ds-btn:hover { color: var(--ds-accent); border-color: var(--ds-accent); }
|
|
159
164
|
.ds-btn:focus-visible { outline: 2px solid var(--ds-accent); outline-offset: 2px; }
|
|
160
|
-
.ds-btn.ds-toast {
|
|
165
|
+
.ds-btn.ds-toast { color: var(--ds-accent); border-color: var(--ds-accent); }
|
|
161
166
|
.ds-powered { font-size: 0.75em; color: var(--ds-muted); }
|
|
162
167
|
.ds-powered a { color: var(--ds-muted); text-decoration: none; }
|
|
163
168
|
.ds-powered a:hover { text-decoration: underline; }
|
|
@@ -194,81 +199,9 @@ function todayUtc8(now = /* @__PURE__ */ new Date()) {
|
|
|
194
199
|
return `${yy}-${mm}-${dd}`;
|
|
195
200
|
}
|
|
196
201
|
|
|
197
|
-
// src/widget.
|
|
202
|
+
// src/widget.tsx
|
|
203
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
198
204
|
var DEFAULT_SCHEDULE_BASE = "https://daily-soup-widget.vercel.app";
|
|
199
|
-
function escape(s) {
|
|
200
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
201
|
-
}
|
|
202
|
-
function attachRoot(host) {
|
|
203
|
-
if (typeof host.attachShadow === "function") {
|
|
204
|
-
if (host.shadowRoot) return host.shadowRoot;
|
|
205
|
-
return host.attachShadow({ mode: "open" });
|
|
206
|
-
}
|
|
207
|
-
console.warn("[daily-soup] attachShadow unsupported, falling back to light DOM");
|
|
208
|
-
return host;
|
|
209
|
-
}
|
|
210
|
-
function injectStyles(root) {
|
|
211
|
-
const style = document.createElement("style");
|
|
212
|
-
style.textContent = WIDGET_STYLES;
|
|
213
|
-
root.appendChild(style);
|
|
214
|
-
}
|
|
215
|
-
function buildSkeleton(theme) {
|
|
216
|
-
const card = document.createElement("div");
|
|
217
|
-
card.className = `ds-card ds-${theme} ds-skeleton`;
|
|
218
|
-
card.innerHTML = `
|
|
219
|
-
<div class="ds-quote"> </div>
|
|
220
|
-
<div class="ds-meta"><span class="ds-author"> </span><span class="ds-source"> </span></div>
|
|
221
|
-
`;
|
|
222
|
-
return card;
|
|
223
|
-
}
|
|
224
|
-
function renderQuote(card, quote, lang, theme) {
|
|
225
|
-
const s = t(lang);
|
|
226
|
-
card.className = `ds-card ds-${theme}`;
|
|
227
|
-
const sourceLabel = quote.sourceUrl ? `<a href="${escape(quote.sourceUrl)}" target="_blank" rel="noopener noreferrer">${escape(quote.source)}</a>` : escape(quote.source);
|
|
228
|
-
const flag = quote.attribution === "popular-attribution" ? `<span class="ds-flag">${escape(s.attributedPopular)}</span>` : "";
|
|
229
|
-
card.innerHTML = `
|
|
230
|
-
<p class="ds-quote">${escape(quote.text)}</p>
|
|
231
|
-
<div class="ds-meta">
|
|
232
|
-
<span class="ds-author">\u2014 ${escape(quote.author)}${flag}</span>
|
|
233
|
-
${quote.source ? `<span class="ds-source">${s.source}\uFF1A${sourceLabel}</span>` : ""}
|
|
234
|
-
</div>
|
|
235
|
-
<div class="ds-actions">
|
|
236
|
-
<div class="ds-share">
|
|
237
|
-
<button class="ds-btn" data-action="copy" type="button" aria-label="${escape(s.copy)}">
|
|
238
|
-
<span aria-hidden="true">\u29C9</span><span class="ds-share-label">${escape(s.copy)}</span>
|
|
239
|
-
</button>
|
|
240
|
-
<a class="ds-btn" data-action="x" href="${escape(buildXShareUrl({ text: quote.text, author: quote.author }))}" target="_blank" rel="noopener noreferrer" aria-label="${escape(s.shareX)}">
|
|
241
|
-
<span aria-hidden="true">\u{1D54F}</span><span class="ds-share-label">X</span>
|
|
242
|
-
</a>
|
|
243
|
-
<a class="ds-btn" data-action="line" href="${escape(buildLineShareUrl({ text: quote.text, author: quote.author }))}" target="_blank" rel="noopener noreferrer" aria-label="${escape(s.shareLine)}">
|
|
244
|
-
<span aria-hidden="true">L</span><span class="ds-share-label">LINE</span>
|
|
245
|
-
</a>
|
|
246
|
-
</div>
|
|
247
|
-
<span class="ds-powered"><a href="https://personal-site-mocha-chi.vercel.app" target="_blank" rel="noopener noreferrer">${s.poweredBy}</a></span>
|
|
248
|
-
</div>
|
|
249
|
-
`;
|
|
250
|
-
const copyBtn = card.querySelector('[data-action="copy"]');
|
|
251
|
-
if (copyBtn) {
|
|
252
|
-
copyBtn.addEventListener("click", async () => {
|
|
253
|
-
const ok = await copyToClipboard({ text: quote.text, author: quote.author });
|
|
254
|
-
if (ok) {
|
|
255
|
-
const label = copyBtn.querySelector(".ds-share-label");
|
|
256
|
-
const originalLabel = label?.textContent ?? "";
|
|
257
|
-
if (label) label.textContent = s.copied;
|
|
258
|
-
copyBtn.classList.add("ds-toast");
|
|
259
|
-
setTimeout(() => {
|
|
260
|
-
if (label) label.textContent = originalLabel;
|
|
261
|
-
copyBtn.classList.remove("ds-toast");
|
|
262
|
-
}, 2e3);
|
|
263
|
-
}
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
function renderError(card, lang, theme) {
|
|
268
|
-
card.className = `ds-card ds-${theme}`;
|
|
269
|
-
const s = t(lang);
|
|
270
|
-
card.innerHTML = `<p class="ds-error">${escape(s.loadFailed)}</p>`;
|
|
271
|
-
}
|
|
272
205
|
async function fetchSchedule(scheduleUrl, lang) {
|
|
273
206
|
const base = scheduleUrl.replace(/\/$/, "");
|
|
274
207
|
const url = `${base}/schedule-${lang}.json`;
|
|
@@ -281,8 +214,7 @@ async function fetchSchedule(scheduleUrl, lang) {
|
|
|
281
214
|
return null;
|
|
282
215
|
}
|
|
283
216
|
}
|
|
284
|
-
function pickQuote(schedule) {
|
|
285
|
-
const today = todayUtc8();
|
|
217
|
+
function pickQuote(schedule, today) {
|
|
286
218
|
const id = schedule.entries[today];
|
|
287
219
|
if (id && schedule.quotes[id]) return schedule.quotes[id];
|
|
288
220
|
const fallbackId = Object.keys(schedule.quotes).sort()[0];
|
|
@@ -292,80 +224,231 @@ function pickQuote(schedule) {
|
|
|
292
224
|
}
|
|
293
225
|
return null;
|
|
294
226
|
}
|
|
295
|
-
function
|
|
296
|
-
const
|
|
227
|
+
function buildInlineStyle(themeConfig, maxWidth) {
|
|
228
|
+
const style = {};
|
|
229
|
+
const colors = getThemeColors(themeConfig);
|
|
297
230
|
if (colors) {
|
|
298
|
-
if (colors.bg)
|
|
299
|
-
if (colors.ink)
|
|
300
|
-
if (colors.muted)
|
|
301
|
-
if (colors.border)
|
|
302
|
-
if (colors.accent)
|
|
231
|
+
if (colors.bg) style["--ds-bg"] = colors.bg;
|
|
232
|
+
if (colors.ink) style["--ds-fg"] = colors.ink;
|
|
233
|
+
if (colors.muted) style["--ds-muted"] = colors.muted;
|
|
234
|
+
if (colors.border) style["--ds-border"] = colors.border;
|
|
235
|
+
if (colors.accent) style["--ds-accent"] = colors.accent;
|
|
236
|
+
}
|
|
237
|
+
if (maxWidth) style["--ds-max-width"] = maxWidth;
|
|
238
|
+
return style;
|
|
239
|
+
}
|
|
240
|
+
function DailySoupWidget({ lang, themeConfig, scheduleUrl, maxWidth }) {
|
|
241
|
+
const [resolvedTheme, setResolvedTheme] = (0, import_react.useState)(() => resolveTheme(themeConfig));
|
|
242
|
+
const [schedule, setSchedule] = (0, import_react.useState)(null);
|
|
243
|
+
const [displayedDate, setDisplayedDate] = (0, import_react.useState)("");
|
|
244
|
+
const [quote, setQuote] = (0, import_react.useState)(null);
|
|
245
|
+
const [loadFailed, setLoadFailed] = (0, import_react.useState)(false);
|
|
246
|
+
const [copyToast, setCopyToast] = (0, import_react.useState)(false);
|
|
247
|
+
(0, import_react.useEffect)(() => {
|
|
248
|
+
if (themeConfig !== "auto") {
|
|
249
|
+
setResolvedTheme(resolveTheme(themeConfig));
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
setResolvedTheme(resolveTheme(themeConfig));
|
|
253
|
+
return watchSystemTheme(setResolvedTheme);
|
|
254
|
+
}, [themeConfig]);
|
|
255
|
+
(0, import_react.useEffect)(() => {
|
|
256
|
+
let cancelled = false;
|
|
257
|
+
let retried = false;
|
|
258
|
+
const load = async () => {
|
|
259
|
+
const sch = await fetchSchedule(scheduleUrl, lang);
|
|
260
|
+
if (cancelled) return;
|
|
261
|
+
if (!sch) {
|
|
262
|
+
if (!retried) {
|
|
263
|
+
retried = true;
|
|
264
|
+
setTimeout(load, 2e3);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
setLoadFailed(true);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
const today = todayUtc8();
|
|
271
|
+
const q = pickQuote(sch, today);
|
|
272
|
+
if (!q) {
|
|
273
|
+
setLoadFailed(true);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
(0, import_react_dom.flushSync)(() => {
|
|
277
|
+
setSchedule(sch);
|
|
278
|
+
setDisplayedDate(today);
|
|
279
|
+
setQuote(q);
|
|
280
|
+
});
|
|
281
|
+
};
|
|
282
|
+
load();
|
|
283
|
+
return () => {
|
|
284
|
+
cancelled = true;
|
|
285
|
+
};
|
|
286
|
+
}, [lang, scheduleUrl]);
|
|
287
|
+
(0, import_react.useEffect)(() => {
|
|
288
|
+
if (!schedule || typeof document === "undefined") return;
|
|
289
|
+
const onVisibility = () => {
|
|
290
|
+
if (document.visibilityState !== "visible") return;
|
|
291
|
+
const today = todayUtc8();
|
|
292
|
+
if (today === displayedDate) return;
|
|
293
|
+
const q = pickQuote(schedule, today);
|
|
294
|
+
if (!q) return;
|
|
295
|
+
(0, import_react_dom.flushSync)(() => {
|
|
296
|
+
setDisplayedDate(today);
|
|
297
|
+
setQuote(q);
|
|
298
|
+
});
|
|
299
|
+
};
|
|
300
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
301
|
+
return () => document.removeEventListener("visibilitychange", onVisibility);
|
|
302
|
+
}, [schedule, displayedDate]);
|
|
303
|
+
const inlineStyle = buildInlineStyle(themeConfig, maxWidth);
|
|
304
|
+
const s = t(lang);
|
|
305
|
+
const handleCopy = (0, import_react.useCallback)(async () => {
|
|
306
|
+
if (!quote) return;
|
|
307
|
+
const ok = await copyToClipboard({ text: quote.text, author: quote.author });
|
|
308
|
+
if (ok) {
|
|
309
|
+
setCopyToast(true);
|
|
310
|
+
setTimeout(() => setCopyToast(false), 2e3);
|
|
311
|
+
}
|
|
312
|
+
}, [quote]);
|
|
313
|
+
if (loadFailed) {
|
|
314
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
315
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: WIDGET_STYLES }),
|
|
316
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: `ds-card ds-${resolvedTheme}`, style: inlineStyle, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "ds-error", children: s.loadFailed }) })
|
|
317
|
+
] });
|
|
318
|
+
}
|
|
319
|
+
if (!quote) {
|
|
320
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
321
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: WIDGET_STYLES }),
|
|
322
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `ds-card ds-${resolvedTheme} ds-skeleton`, style: inlineStyle, children: [
|
|
323
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ds-quote", children: "\xA0".repeat(25) }),
|
|
324
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ds-meta", children: [
|
|
325
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ds-author", children: "\xA0" }),
|
|
326
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ds-source", children: "\xA0" })
|
|
327
|
+
] })
|
|
328
|
+
] })
|
|
329
|
+
] });
|
|
303
330
|
}
|
|
304
|
-
|
|
331
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
332
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: WIDGET_STYLES }),
|
|
333
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `ds-card ds-${resolvedTheme}`, style: inlineStyle, children: [
|
|
334
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "ds-quote", children: quote.text }),
|
|
335
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ds-meta", children: [
|
|
336
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "ds-author", children: [
|
|
337
|
+
"\u2014 ",
|
|
338
|
+
quote.author,
|
|
339
|
+
quote.attribution === "popular-attribution" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ds-flag", children: s.attributedPopular })
|
|
340
|
+
] }),
|
|
341
|
+
quote.source && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "ds-source", children: [
|
|
342
|
+
s.source,
|
|
343
|
+
"\uFF1A",
|
|
344
|
+
quote.sourceUrl ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href: quote.sourceUrl, target: "_blank", rel: "noopener noreferrer", children: quote.source }) : quote.source
|
|
345
|
+
] })
|
|
346
|
+
] }),
|
|
347
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ds-actions", children: [
|
|
348
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ds-share", children: [
|
|
349
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
350
|
+
"button",
|
|
351
|
+
{
|
|
352
|
+
className: `ds-btn${copyToast ? " ds-toast" : ""}`,
|
|
353
|
+
"data-action": "copy",
|
|
354
|
+
type: "button",
|
|
355
|
+
"aria-label": s.copy,
|
|
356
|
+
onClick: handleCopy,
|
|
357
|
+
children: [
|
|
358
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", children: "\u29C9" }),
|
|
359
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ds-share-label", children: copyToast ? s.copied : s.copy })
|
|
360
|
+
]
|
|
361
|
+
}
|
|
362
|
+
),
|
|
363
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
364
|
+
"a",
|
|
365
|
+
{
|
|
366
|
+
className: "ds-btn",
|
|
367
|
+
"data-action": "x",
|
|
368
|
+
href: buildXShareUrl({ text: quote.text, author: quote.author }),
|
|
369
|
+
target: "_blank",
|
|
370
|
+
rel: "noopener noreferrer",
|
|
371
|
+
"aria-label": s.shareX,
|
|
372
|
+
children: [
|
|
373
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", children: "\u{1D54F}" }),
|
|
374
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ds-share-label", children: "X" })
|
|
375
|
+
]
|
|
376
|
+
}
|
|
377
|
+
),
|
|
378
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
379
|
+
"a",
|
|
380
|
+
{
|
|
381
|
+
className: "ds-btn",
|
|
382
|
+
"data-action": "line",
|
|
383
|
+
href: buildLineShareUrl({ text: quote.text, author: quote.author }),
|
|
384
|
+
target: "_blank",
|
|
385
|
+
rel: "noopener noreferrer",
|
|
386
|
+
"aria-label": s.shareLine,
|
|
387
|
+
children: [
|
|
388
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", children: "L" }),
|
|
389
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ds-share-label", children: "LINE" })
|
|
390
|
+
]
|
|
391
|
+
}
|
|
392
|
+
)
|
|
393
|
+
] }),
|
|
394
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ds-powered", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
395
|
+
"a",
|
|
396
|
+
{
|
|
397
|
+
href: "https://personal-site-mocha-chi.vercel.app",
|
|
398
|
+
target: "_blank",
|
|
399
|
+
rel: "noopener noreferrer",
|
|
400
|
+
children: s.poweredBy
|
|
401
|
+
}
|
|
402
|
+
) })
|
|
403
|
+
] })
|
|
404
|
+
] })
|
|
405
|
+
] });
|
|
406
|
+
}
|
|
407
|
+
function attachRoot(host) {
|
|
408
|
+
if (typeof host.attachShadow === "function") {
|
|
409
|
+
if (host.shadowRoot) return host.shadowRoot;
|
|
410
|
+
return host.attachShadow({ mode: "open" });
|
|
411
|
+
}
|
|
412
|
+
console.warn("[daily-soup] attachShadow unsupported, falling back to light DOM");
|
|
413
|
+
return host;
|
|
305
414
|
}
|
|
306
415
|
function mount(host, options = {}) {
|
|
307
416
|
const lang = options.lang ?? "zh";
|
|
308
417
|
const themeConfig = options.theme ?? "auto";
|
|
309
418
|
const scheduleUrl = options.scheduleUrl === void 0 ? DEFAULT_SCHEDULE_BASE : options.scheduleUrl;
|
|
310
419
|
const maxWidth = options.maxWidth;
|
|
311
|
-
let resolvedTheme = resolveTheme(themeConfig);
|
|
312
420
|
const root = attachRoot(host);
|
|
313
421
|
if (root === host) {
|
|
314
422
|
host.textContent = "";
|
|
315
423
|
} else {
|
|
316
|
-
|
|
424
|
+
const shadow = root;
|
|
425
|
+
while (shadow.firstChild) shadow.removeChild(shadow.firstChild);
|
|
317
426
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
resolvedTheme = t2;
|
|
335
|
-
state.cardEl.classList.remove("ds-light", "ds-dark");
|
|
336
|
-
state.cardEl.classList.add(`ds-${t2}`);
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
let cancelled = false;
|
|
340
|
-
let retried = false;
|
|
341
|
-
const load = async () => {
|
|
342
|
-
const schedule = await fetchSchedule(scheduleUrl, lang);
|
|
343
|
-
if (cancelled) return;
|
|
344
|
-
if (!schedule) {
|
|
345
|
-
if (!retried) {
|
|
346
|
-
retried = true;
|
|
347
|
-
setTimeout(load, 2e3);
|
|
348
|
-
return;
|
|
349
|
-
}
|
|
350
|
-
renderError(state.cardEl, lang, resolvedTheme);
|
|
351
|
-
return;
|
|
352
|
-
}
|
|
353
|
-
const quote = pickQuote(schedule);
|
|
354
|
-
if (!quote) {
|
|
355
|
-
renderError(state.cardEl, lang, resolvedTheme);
|
|
356
|
-
return;
|
|
357
|
-
}
|
|
358
|
-
renderQuote(state.cardEl, quote, lang, resolvedTheme);
|
|
359
|
-
};
|
|
360
|
-
load();
|
|
427
|
+
const container = document.createElement("div");
|
|
428
|
+
root.appendChild(container);
|
|
429
|
+
const reactRoot = (0, import_client.createRoot)(container);
|
|
430
|
+
(0, import_react_dom.flushSync)(() => {
|
|
431
|
+
reactRoot.render(
|
|
432
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
433
|
+
DailySoupWidget,
|
|
434
|
+
{
|
|
435
|
+
lang,
|
|
436
|
+
themeConfig,
|
|
437
|
+
scheduleUrl,
|
|
438
|
+
maxWidth
|
|
439
|
+
}
|
|
440
|
+
)
|
|
441
|
+
);
|
|
442
|
+
});
|
|
361
443
|
return {
|
|
362
444
|
destroy() {
|
|
363
|
-
|
|
364
|
-
state.unwatchTheme();
|
|
445
|
+
reactRoot.unmount();
|
|
365
446
|
if (root === host) {
|
|
366
447
|
host.textContent = "";
|
|
367
448
|
} else if (host.shadowRoot) {
|
|
368
|
-
while (host.shadowRoot.firstChild)
|
|
449
|
+
while (host.shadowRoot.firstChild) {
|
|
450
|
+
host.shadowRoot.removeChild(host.shadowRoot.firstChild);
|
|
451
|
+
}
|
|
369
452
|
}
|
|
370
453
|
}
|
|
371
454
|
};
|
|
@@ -385,16 +468,16 @@ function mountAll(selector = "[data-daily-soup], #daily-soup") {
|
|
|
385
468
|
}
|
|
386
469
|
|
|
387
470
|
// src/component.tsx
|
|
388
|
-
var
|
|
389
|
-
var
|
|
471
|
+
var import_react2 = require("react");
|
|
472
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
390
473
|
function DailySoup({ lang = "zh", theme = "auto", scheduleUrl, className, maxWidth }) {
|
|
391
|
-
const hostRef = (0,
|
|
474
|
+
const hostRef = (0, import_react2.useRef)(null);
|
|
392
475
|
const themeKey = typeof theme === "string" ? theme : JSON.stringify(theme);
|
|
393
|
-
(0,
|
|
476
|
+
(0, import_react2.useEffect)(() => {
|
|
394
477
|
if (!hostRef.current) return;
|
|
395
478
|
const handle = mount(hostRef.current, { lang, theme, scheduleUrl, maxWidth });
|
|
396
479
|
return () => handle.destroy();
|
|
397
480
|
}, [lang, themeKey, scheduleUrl, maxWidth]);
|
|
398
|
-
return /* @__PURE__ */ (0,
|
|
481
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ref: hostRef, className, "data-daily-soup-host": "" });
|
|
399
482
|
}
|
|
400
483
|
//# sourceMappingURL=embed.cjs.js.map
|
package/dist/embed.cjs.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/index.ts", "../src/
|
|
4
|
-
"sourcesContent": ["export { mount, mountAll } from './widget';\nexport { DailySoup } from './component';\nexport type {\n Lang,\n ThemeConfig,\n ResolvedTheme,\n Quote,\n Schedule,\n MountOptions,\n DailySoupProps,\n} from './types';\n", "import type { Lang } from './types';\n\ninterface UiStrings {\n copy: string;\n copied: string;\n share: string;\n source: string;\n poweredBy: string;\n attributedPopular: string;\n shareX: string;\n shareLine: string;\n loadFailed: string;\n}\n\nconst STRINGS: Record<Lang, UiStrings> = {\n zh: {\n copy: '\u8907\u88FD',\n copied: '\u5DF2\u8907\u88FD',\n share: '\u5206\u4EAB',\n source: '\u51FA\u8655',\n poweredBy: '\u7531 mshmwr \u63D0\u4F9B',\n attributedPopular: '\u50B3\u7D71\u6B78\u5C6C',\n shareX: '\u5206\u4EAB\u5230 X',\n shareLine: '\u5206\u4EAB\u5230 LINE',\n loadFailed: '\u672C\u65E5\u5C0F\u8A9E\u8F09\u5165\u5931\u6557',\n },\n};\n\nexport function t(lang: Lang): UiStrings {\n return STRINGS[lang] ?? STRINGS.zh;\n}\n\nexport type { UiStrings };\n", "import type { ThemeConfig, ResolvedTheme, ThemeColors } from './types';\n\nfunction isThemeColors(config: ThemeConfig): config is ThemeColors {\n return typeof config === 'object' && config !== null;\n}\n\nexport function resolveTheme(config: ThemeConfig): ResolvedTheme {\n if (isThemeColors(config)) return config.base ?? 'light';\n if (config === 'light' || config === 'dark') return config;\n if (typeof window === 'undefined' || !window.matchMedia) return 'light';\n return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';\n}\n\nexport function getThemeColors(config: ThemeConfig): ThemeColors | null {\n return isThemeColors(config) ? config : null;\n}\n\nexport function watchSystemTheme(cb: (theme: ResolvedTheme) => void): () => void {\n if (typeof window === 'undefined' || !window.matchMedia) return () => {};\n const mql = window.matchMedia('(prefers-color-scheme: dark)');\n const handler = (e: MediaQueryListEvent) => cb(e.matches ? 'dark' : 'light');\n mql.addEventListener('change', handler);\n return () => mql.removeEventListener('change', handler);\n}\n", "const SHARE_URL = 'https://daily-soup-widget.vercel.app';\n\nexport interface ShareContent {\n text: string;\n author: string;\n}\n\nexport async function copyToClipboard(content: ShareContent): Promise<boolean> {\n const payload = `${content.text} \u2014 ${content.author}`;\n if (typeof navigator !== 'undefined' && navigator.clipboard) {\n try {\n await navigator.clipboard.writeText(payload);\n return true;\n } catch {\n return false;\n }\n }\n return false;\n}\n\nexport function buildXShareUrl(content: ShareContent): string {\n const text = encodeURIComponent(`${content.text} \u2014 ${content.author}`);\n const url = encodeURIComponent(SHARE_URL);\n return `https://twitter.com/intent/tweet?text=${text}&url=${url}`;\n}\n\nexport function buildLineShareUrl(content: ShareContent): string {\n const url = encodeURIComponent(`${SHARE_URL} \u2014 ${content.text}`);\n return `https://social-plugins.line.me/lineit/share?url=${url}`;\n}\n", "export const WIDGET_STYLES = `\n :host { all: initial; display: block; font-family: system-ui, -apple-system, \"Segoe UI\", Roboto, \"Noto Sans TC\", sans-serif; }\n * { box-sizing: border-box; }\n .ds-card {\n container-type: inline-size;\n width: 100%;\n max-width: var(--ds-max-width, 32rem);\n margin: 0 auto;\n padding: 1.25rem 1.5rem;\n border-radius: 0.75rem;\n border: 1px solid var(--ds-border);\n background: var(--ds-bg);\n color: var(--ds-fg);\n font-size: clamp(0.875rem, 2.5cqi, 1.25rem);\n line-height: 1.7;\n transition: background 0.2s ease, color 0.2s ease;\n }\n .ds-card.ds-light {\n --ds-bg: #fdfcf7;\n --ds-fg: #1f2933;\n --ds-accent: #5b6b9e;\n --ds-muted: #6b7280;\n --ds-border: #e5e7eb;\n }\n .ds-card.ds-dark {\n --ds-bg: #1a1d24;\n --ds-fg: #e5e7eb;\n --ds-accent: #9aa9d4;\n --ds-muted: #9ca3af;\n --ds-border: #2d323d;\n }\n .ds-quote {\n margin: 0 0 0.875rem;\n font-size: 1.1em;\n font-weight: 500;\n letter-spacing: 0.01em;\n white-space: pre-wrap;\n }\n .ds-quote::before { content: '\\\\201C'; margin-right: 0.15em; color: var(--ds-accent); }\n .ds-quote::after { content: '\\\\201D'; margin-left: 0.15em; color: var(--ds-accent); }\n .ds-meta { display: flex; flex-direction: column; gap: 0.15rem; margin-bottom: 0.875rem; font-size: 0.875em; color: var(--ds-muted); }\n .ds-author { font-weight: 500; color: var(--ds-fg); }\n .ds-source { font-size: 0.95em; }\n .ds-source a { color: var(--ds-accent); text-decoration: none; }\n .ds-source a:hover { text-decoration: underline; }\n .ds-flag { display: inline-block; margin-left: 0.25rem; padding: 0 0.4rem; font-size: 0.75em; border: 1px solid var(--ds-border); border-radius: 9999px; color: var(--ds-muted); }\n .ds-actions { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; margin-top: 0.875rem; padding-top: 0.875rem; border-top: 1px solid var(--ds-border); }\n .ds-share { display: flex; gap: 0.35rem; }\n .ds-btn {\n appearance: none;\n background: transparent;\n color: var(--ds-fg);\n border: 1px solid var(--ds-border);\n border-radius: 0.5rem;\n padding: 0.3rem 0.7rem;\n font-size: 0.85em;\n font-family: inherit;\n cursor: pointer;\n transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;\n text-decoration: none;\n display: inline-flex;\n align-items: center;\n gap: 0.25rem;\n }\n .ds-btn:hover { background: var(--ds-accent); color: var(--ds-bg); border-color: var(--ds-accent); }\n .ds-btn:focus-visible { outline: 2px solid var(--ds-accent); outline-offset: 2px; }\n .ds-btn.ds-toast { background: var(--ds-accent); color: var(--ds-bg); border-color: var(--ds-accent); }\n .ds-powered { font-size: 0.75em; color: var(--ds-muted); }\n .ds-powered a { color: var(--ds-muted); text-decoration: none; }\n .ds-powered a:hover { text-decoration: underline; }\n .ds-skeleton .ds-quote { background: var(--ds-border); border-radius: 0.25rem; color: transparent; }\n .ds-skeleton .ds-quote::before, .ds-skeleton .ds-quote::after { content: ''; }\n .ds-error { color: var(--ds-muted); font-size: 0.875em; }\n\n @container (max-width: 320px) {\n .ds-card { padding: 1rem 1.1rem; }\n .ds-share-label { display: none; }\n .ds-actions { flex-wrap: wrap; }\n }\n @container (min-width: 500px) {\n .ds-quote { font-size: 1.25em; }\n .ds-meta { flex-direction: row; flex-wrap: wrap; align-items: baseline; gap: 0.25rem 0.75rem; }\n }\n @container (min-width: 700px) {\n .ds-card { padding: 1.75rem 2rem; }\n .ds-quote { font-size: 1.35em; margin-bottom: 1.125rem; }\n .ds-meta { gap: 0.25rem 1rem; margin-bottom: 1.125rem; }\n .ds-actions { margin-top: 1.125rem; padding-top: 1.125rem; }\n }\n @media (prefers-reduced-motion: reduce) {\n .ds-card, .ds-btn { transition: none; }\n }\n`;\n", "export function todayUtc8(now: Date = new Date()): string {\n const utc8 = new Date(now.getTime() + 8 * 60 * 60 * 1000);\n const yy = utc8.getUTCFullYear();\n const mm = String(utc8.getUTCMonth() + 1).padStart(2, '0');\n const dd = String(utc8.getUTCDate()).padStart(2, '0');\n return `${yy}-${mm}-${dd}`;\n}\n", "import type { Lang, MountOptions, Quote, ResolvedTheme, Schedule, ThemeConfig } from './types';\nimport { t } from './i18n';\nimport { resolveTheme, watchSystemTheme, getThemeColors } from './theme';\nimport { buildLineShareUrl, buildXShareUrl, copyToClipboard } from './share';\nimport { WIDGET_STYLES } from './styles';\nimport { todayUtc8 } from './date';\n\nconst DEFAULT_SCHEDULE_BASE = 'https://daily-soup-widget.vercel.app';\n\nexport interface MountHandle {\n destroy(): void;\n}\n\ninterface WidgetState {\n lang: Lang;\n themeConfig: ThemeConfig;\n scheduleUrl: string;\n host: HTMLElement;\n root: ShadowRoot | HTMLElement;\n cardEl: HTMLElement;\n unwatchTheme: () => void;\n}\n\nfunction escape(s: string): string {\n return s\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\nfunction attachRoot(host: HTMLElement): ShadowRoot | HTMLElement {\n if (typeof host.attachShadow === 'function') {\n if (host.shadowRoot) return host.shadowRoot;\n return host.attachShadow({ mode: 'open' });\n }\n console.warn('[daily-soup] attachShadow unsupported, falling back to light DOM');\n return host;\n}\n\nfunction injectStyles(root: ShadowRoot | HTMLElement) {\n const style = document.createElement('style');\n style.textContent = WIDGET_STYLES;\n root.appendChild(style);\n}\n\nfunction buildSkeleton(theme: ResolvedTheme): HTMLElement {\n const card = document.createElement('div');\n card.className = `ds-card ds-${theme} ds-skeleton`;\n card.innerHTML = `\n <div class=\"ds-quote\"> </div>\n <div class=\"ds-meta\"><span class=\"ds-author\"> </span><span class=\"ds-source\"> </span></div>\n `;\n return card;\n}\n\nfunction renderQuote(card: HTMLElement, quote: Quote, lang: Lang, theme: ResolvedTheme) {\n const s = t(lang);\n card.className = `ds-card ds-${theme}`;\n const sourceLabel = quote.sourceUrl\n ? `<a href=\"${escape(quote.sourceUrl)}\" target=\"_blank\" rel=\"noopener noreferrer\">${escape(quote.source)}</a>`\n : escape(quote.source);\n const flag = quote.attribution === 'popular-attribution'\n ? `<span class=\"ds-flag\">${escape(s.attributedPopular)}</span>`\n : '';\n card.innerHTML = `\n <p class=\"ds-quote\">${escape(quote.text)}</p>\n <div class=\"ds-meta\">\n <span class=\"ds-author\">\u2014 ${escape(quote.author)}${flag}</span>\n ${quote.source ? `<span class=\"ds-source\">${s.source}\uFF1A${sourceLabel}</span>` : ''}\n </div>\n <div class=\"ds-actions\">\n <div class=\"ds-share\">\n <button class=\"ds-btn\" data-action=\"copy\" type=\"button\" aria-label=\"${escape(s.copy)}\">\n <span aria-hidden=\"true\">\u29C9</span><span class=\"ds-share-label\">${escape(s.copy)}</span>\n </button>\n <a class=\"ds-btn\" data-action=\"x\" href=\"${escape(buildXShareUrl({ text: quote.text, author: quote.author }))}\" target=\"_blank\" rel=\"noopener noreferrer\" aria-label=\"${escape(s.shareX)}\">\n <span aria-hidden=\"true\">\uD835\uDD4F</span><span class=\"ds-share-label\">X</span>\n </a>\n <a class=\"ds-btn\" data-action=\"line\" href=\"${escape(buildLineShareUrl({ text: quote.text, author: quote.author }))}\" target=\"_blank\" rel=\"noopener noreferrer\" aria-label=\"${escape(s.shareLine)}\">\n <span aria-hidden=\"true\">L</span><span class=\"ds-share-label\">LINE</span>\n </a>\n </div>\n <span class=\"ds-powered\"><a href=\"https://personal-site-mocha-chi.vercel.app\" target=\"_blank\" rel=\"noopener noreferrer\">${s.poweredBy}</a></span>\n </div>\n `;\n\n const copyBtn = card.querySelector<HTMLButtonElement>('[data-action=\"copy\"]');\n if (copyBtn) {\n copyBtn.addEventListener('click', async () => {\n const ok = await copyToClipboard({ text: quote.text, author: quote.author });\n if (ok) {\n const label = copyBtn.querySelector('.ds-share-label');\n const originalLabel = label?.textContent ?? '';\n if (label) label.textContent = s.copied;\n copyBtn.classList.add('ds-toast');\n setTimeout(() => {\n if (label) label.textContent = originalLabel;\n copyBtn.classList.remove('ds-toast');\n }, 2000);\n }\n });\n }\n}\n\nfunction renderError(card: HTMLElement, lang: Lang, theme: ResolvedTheme) {\n card.className = `ds-card ds-${theme}`;\n const s = t(lang);\n card.innerHTML = `<p class=\"ds-error\">${escape(s.loadFailed)}</p>`;\n}\n\nasync function fetchSchedule(scheduleUrl: string, lang: Lang): Promise<Schedule | null> {\n const base = scheduleUrl.replace(/\\/$/, '');\n const url = `${base}/schedule-${lang}.json`;\n try {\n const init: RequestInit = base === '' ? { credentials: 'omit' } : { credentials: 'omit', mode: 'cors' };\n const res = await fetch(url, init);\n if (!res.ok) return null;\n return (await res.json()) as Schedule;\n } catch {\n return null;\n }\n}\n\nfunction pickQuote(schedule: Schedule): Quote | null {\n const today = todayUtc8();\n const id = schedule.entries[today];\n if (id && schedule.quotes[id]) return schedule.quotes[id];\n const fallbackId = Object.keys(schedule.quotes).sort()[0];\n if (fallbackId && schedule.quotes[fallbackId]) {\n console.warn('[daily-soup] today entry missing or stale, falling back to first quote');\n return schedule.quotes[fallbackId];\n }\n return null;\n}\n\nfunction applyThemeOverrides(card: HTMLElement, config: ThemeConfig, maxWidth?: string) {\n const colors = getThemeColors(config);\n if (colors) {\n if (colors.bg) card.style.setProperty('--ds-bg', colors.bg);\n if (colors.ink) card.style.setProperty('--ds-fg', colors.ink);\n if (colors.muted) card.style.setProperty('--ds-muted', colors.muted);\n if (colors.border) card.style.setProperty('--ds-border', colors.border);\n if (colors.accent) card.style.setProperty('--ds-accent', colors.accent);\n }\n if (maxWidth) card.style.setProperty('--ds-max-width', maxWidth);\n}\n\nexport function mount(host: HTMLElement, options: MountOptions = {}): MountHandle {\n const lang: Lang = options.lang ?? 'zh';\n const themeConfig: ThemeConfig = options.theme ?? 'auto';\n const scheduleUrl = options.scheduleUrl === undefined ? DEFAULT_SCHEDULE_BASE : options.scheduleUrl;\n const maxWidth = options.maxWidth;\n\n let resolvedTheme = resolveTheme(themeConfig);\n const root = attachRoot(host);\n if (root === host) {\n // light DOM fallback \u2014 clear any prior content\n host.textContent = '';\n } else {\n while ((root as ShadowRoot).firstChild) (root as ShadowRoot).removeChild((root as ShadowRoot).firstChild!);\n }\n injectStyles(root);\n\n const card = buildSkeleton(resolvedTheme);\n applyThemeOverrides(card, themeConfig, maxWidth);\n root.appendChild(card);\n\n const state: WidgetState = {\n lang,\n themeConfig,\n scheduleUrl,\n host,\n root,\n cardEl: card,\n unwatchTheme: () => {},\n };\n\n if (themeConfig === 'auto') {\n state.unwatchTheme = watchSystemTheme((t) => {\n resolvedTheme = t;\n state.cardEl.classList.remove('ds-light', 'ds-dark');\n state.cardEl.classList.add(`ds-${t}`);\n });\n }\n\n let cancelled = false;\n let retried = false;\n\n const load = async () => {\n const schedule = await fetchSchedule(scheduleUrl, lang);\n if (cancelled) return;\n if (!schedule) {\n if (!retried) {\n retried = true;\n setTimeout(load, 2000);\n return;\n }\n renderError(state.cardEl, lang, resolvedTheme);\n return;\n }\n const quote = pickQuote(schedule);\n if (!quote) {\n renderError(state.cardEl, lang, resolvedTheme);\n return;\n }\n renderQuote(state.cardEl, quote, lang, resolvedTheme);\n };\n load();\n\n return {\n destroy() {\n cancelled = true;\n state.unwatchTheme();\n if (root === host) {\n host.textContent = '';\n } else if (host.shadowRoot) {\n while (host.shadowRoot.firstChild) host.shadowRoot.removeChild(host.shadowRoot.firstChild);\n }\n },\n };\n}\n\nexport function mountAll(selector = '[data-daily-soup], #daily-soup'): MountHandle[] {\n if (typeof document === 'undefined') return [];\n const nodes = document.querySelectorAll<HTMLElement>(selector);\n const handles: MountHandle[] = [];\n nodes.forEach((node) => {\n const lang = (node.dataset.lang as Lang | undefined) ?? 'zh';\n const theme = (node.dataset.theme as 'auto' | 'light' | 'dark' | undefined) ?? 'auto';\n const scheduleUrl = node.dataset.scheduleUrl;\n const maxWidth = node.dataset.maxWidth;\n handles.push(mount(node, { lang, theme, scheduleUrl, maxWidth }));\n });\n return handles;\n}\n", "import { useEffect, useRef } from 'react';\nimport { mount } from './widget';\nimport type { DailySoupProps } from './types';\n\nexport function DailySoup({ lang = 'zh', theme = 'auto', scheduleUrl, className, maxWidth }: DailySoupProps) {\n const hostRef = useRef<HTMLDivElement | null>(null);\n\n const themeKey = typeof theme === 'string' ? theme : JSON.stringify(theme);\n useEffect(() => {\n if (!hostRef.current) return;\n const handle = mount(hostRef.current, { lang, theme, scheduleUrl, maxWidth });\n return () => handle.destroy();\n }, [lang, themeKey, scheduleUrl, maxWidth]);\n\n return <div ref={hostRef} className={className} data-daily-soup-host=\"\" />;\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;
|
|
6
|
-
"names": ["
|
|
3
|
+
"sources": ["../src/index.ts", "../src/widget.tsx", "../src/i18n.ts", "../src/theme.ts", "../src/share.ts", "../src/styles.ts", "../src/date.ts", "../src/component.tsx"],
|
|
4
|
+
"sourcesContent": ["export { mount, mountAll } from './widget';\nexport { DailySoup } from './component';\nexport type {\n Lang,\n ThemeConfig,\n ResolvedTheme,\n Quote,\n Schedule,\n MountOptions,\n DailySoupProps,\n} from './types';\n", "import { useCallback, useEffect, useState } from 'react';\nimport { createRoot, type Root } from 'react-dom/client';\nimport { flushSync } from 'react-dom';\nimport type { Lang, MountOptions, Quote, ResolvedTheme, Schedule, ThemeConfig } from './types';\nimport { t } from './i18n';\nimport { resolveTheme, watchSystemTheme, getThemeColors } from './theme';\nimport { buildLineShareUrl, buildXShareUrl, copyToClipboard } from './share';\nimport { WIDGET_STYLES } from './styles';\nimport { todayUtc8 } from './date';\n\nconst DEFAULT_SCHEDULE_BASE = 'https://daily-soup-widget.vercel.app';\n\nexport interface MountHandle {\n destroy(): void;\n}\n\ninterface WidgetProps {\n lang: Lang;\n themeConfig: ThemeConfig;\n scheduleUrl: string;\n maxWidth?: string;\n}\n\nasync function fetchSchedule(scheduleUrl: string, lang: Lang): Promise<Schedule | null> {\n const base = scheduleUrl.replace(/\\/$/, '');\n const url = `${base}/schedule-${lang}.json`;\n try {\n const init: RequestInit = base === '' ? { credentials: 'omit' } : { credentials: 'omit', mode: 'cors' };\n const res = await fetch(url, init);\n if (!res.ok) return null;\n return (await res.json()) as Schedule;\n } catch {\n return null;\n }\n}\n\nfunction pickQuote(schedule: Schedule, today: string): Quote | null {\n const id = schedule.entries[today];\n if (id && schedule.quotes[id]) return schedule.quotes[id];\n const fallbackId = Object.keys(schedule.quotes).sort()[0];\n if (fallbackId && schedule.quotes[fallbackId]) {\n console.warn('[daily-soup] today entry missing or stale, falling back to first quote');\n return schedule.quotes[fallbackId];\n }\n return null;\n}\n\nfunction buildInlineStyle(themeConfig: ThemeConfig, maxWidth?: string): React.CSSProperties {\n const style: Record<string, string> = {};\n const colors = getThemeColors(themeConfig);\n if (colors) {\n if (colors.bg) style['--ds-bg'] = colors.bg;\n if (colors.ink) style['--ds-fg'] = colors.ink;\n if (colors.muted) style['--ds-muted'] = colors.muted;\n if (colors.border) style['--ds-border'] = colors.border;\n if (colors.accent) style['--ds-accent'] = colors.accent;\n }\n if (maxWidth) style['--ds-max-width'] = maxWidth;\n return style as React.CSSProperties;\n}\n\nfunction DailySoupWidget({ lang, themeConfig, scheduleUrl, maxWidth }: WidgetProps) {\n const [resolvedTheme, setResolvedTheme] = useState<ResolvedTheme>(() => resolveTheme(themeConfig));\n const [schedule, setSchedule] = useState<Schedule | null>(null);\n const [displayedDate, setDisplayedDate] = useState<string>('');\n const [quote, setQuote] = useState<Quote | null>(null);\n const [loadFailed, setLoadFailed] = useState(false);\n const [copyToast, setCopyToast] = useState(false);\n\n useEffect(() => {\n if (themeConfig !== 'auto') {\n setResolvedTheme(resolveTheme(themeConfig));\n return;\n }\n setResolvedTheme(resolveTheme(themeConfig));\n return watchSystemTheme(setResolvedTheme);\n }, [themeConfig]);\n\n useEffect(() => {\n let cancelled = false;\n let retried = false;\n\n const load = async () => {\n const sch = await fetchSchedule(scheduleUrl, lang);\n if (cancelled) return;\n if (!sch) {\n if (!retried) {\n retried = true;\n setTimeout(load, 2000);\n return;\n }\n setLoadFailed(true);\n return;\n }\n const today = todayUtc8();\n const q = pickQuote(sch, today);\n if (!q) {\n setLoadFailed(true);\n return;\n }\n flushSync(() => {\n setSchedule(sch);\n setDisplayedDate(today);\n setQuote(q);\n });\n };\n load();\n\n return () => {\n cancelled = true;\n };\n }, [lang, scheduleUrl]);\n\n useEffect(() => {\n if (!schedule || typeof document === 'undefined') return;\n const onVisibility = () => {\n if (document.visibilityState !== 'visible') return;\n const today = todayUtc8();\n if (today === displayedDate) return;\n const q = pickQuote(schedule, today);\n if (!q) return;\n flushSync(() => {\n setDisplayedDate(today);\n setQuote(q);\n });\n };\n document.addEventListener('visibilitychange', onVisibility);\n return () => document.removeEventListener('visibilitychange', onVisibility);\n }, [schedule, displayedDate]);\n\n const inlineStyle = buildInlineStyle(themeConfig, maxWidth);\n const s = t(lang);\n\n const handleCopy = useCallback(async () => {\n if (!quote) return;\n const ok = await copyToClipboard({ text: quote.text, author: quote.author });\n if (ok) {\n setCopyToast(true);\n setTimeout(() => setCopyToast(false), 2000);\n }\n }, [quote]);\n\n if (loadFailed) {\n return (\n <>\n <style>{WIDGET_STYLES}</style>\n <div className={`ds-card ds-${resolvedTheme}`} style={inlineStyle}>\n <p className=\"ds-error\">{s.loadFailed}</p>\n </div>\n </>\n );\n }\n\n if (!quote) {\n return (\n <>\n <style>{WIDGET_STYLES}</style>\n <div className={`ds-card ds-${resolvedTheme} ds-skeleton`} style={inlineStyle}>\n <div className=\"ds-quote\">{'\u00A0'.repeat(25)}</div>\n <div className=\"ds-meta\">\n <span className=\"ds-author\">{'\u00A0'}</span>\n <span className=\"ds-source\">{'\u00A0'}</span>\n </div>\n </div>\n </>\n );\n }\n\n return (\n <>\n <style>{WIDGET_STYLES}</style>\n <div className={`ds-card ds-${resolvedTheme}`} style={inlineStyle}>\n <p className=\"ds-quote\">{quote.text}</p>\n <div className=\"ds-meta\">\n <span className=\"ds-author\">\n \u2014 {quote.author}\n {quote.attribution === 'popular-attribution' && (\n <span className=\"ds-flag\">{s.attributedPopular}</span>\n )}\n </span>\n {quote.source && (\n <span className=\"ds-source\">\n {s.source}\uFF1A\n {quote.sourceUrl ? (\n <a href={quote.sourceUrl} target=\"_blank\" rel=\"noopener noreferrer\">\n {quote.source}\n </a>\n ) : (\n quote.source\n )}\n </span>\n )}\n </div>\n <div className=\"ds-actions\">\n <div className=\"ds-share\">\n <button\n className={`ds-btn${copyToast ? ' ds-toast' : ''}`}\n data-action=\"copy\"\n type=\"button\"\n aria-label={s.copy}\n onClick={handleCopy}\n >\n <span aria-hidden=\"true\">\u29C9</span>\n <span className=\"ds-share-label\">{copyToast ? s.copied : s.copy}</span>\n </button>\n <a\n className=\"ds-btn\"\n data-action=\"x\"\n href={buildXShareUrl({ text: quote.text, author: quote.author })}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n aria-label={s.shareX}\n >\n <span aria-hidden=\"true\">\uD835\uDD4F</span>\n <span className=\"ds-share-label\">X</span>\n </a>\n <a\n className=\"ds-btn\"\n data-action=\"line\"\n href={buildLineShareUrl({ text: quote.text, author: quote.author })}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n aria-label={s.shareLine}\n >\n <span aria-hidden=\"true\">L</span>\n <span className=\"ds-share-label\">LINE</span>\n </a>\n </div>\n <span className=\"ds-powered\">\n <a\n href=\"https://personal-site-mocha-chi.vercel.app\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n {s.poweredBy}\n </a>\n </span>\n </div>\n </div>\n </>\n );\n}\n\nfunction attachRoot(host: HTMLElement): ShadowRoot | HTMLElement {\n if (typeof host.attachShadow === 'function') {\n if (host.shadowRoot) return host.shadowRoot;\n return host.attachShadow({ mode: 'open' });\n }\n console.warn('[daily-soup] attachShadow unsupported, falling back to light DOM');\n return host;\n}\n\nexport function mount(host: HTMLElement, options: MountOptions = {}): MountHandle {\n const lang: Lang = options.lang ?? 'zh';\n const themeConfig: ThemeConfig = options.theme ?? 'auto';\n const scheduleUrl =\n options.scheduleUrl === undefined ? DEFAULT_SCHEDULE_BASE : options.scheduleUrl;\n const maxWidth = options.maxWidth;\n\n const root = attachRoot(host);\n if (root === host) {\n host.textContent = '';\n } else {\n const shadow = root as ShadowRoot;\n while (shadow.firstChild) shadow.removeChild(shadow.firstChild);\n }\n\n const container = document.createElement('div');\n root.appendChild(container);\n const reactRoot: Root = createRoot(container);\n flushSync(() => {\n reactRoot.render(\n <DailySoupWidget\n lang={lang}\n themeConfig={themeConfig}\n scheduleUrl={scheduleUrl}\n maxWidth={maxWidth}\n />\n );\n });\n\n return {\n destroy() {\n reactRoot.unmount();\n if (root === host) {\n host.textContent = '';\n } else if (host.shadowRoot) {\n while (host.shadowRoot.firstChild) {\n host.shadowRoot.removeChild(host.shadowRoot.firstChild);\n }\n }\n },\n };\n}\n\nexport function mountAll(selector = '[data-daily-soup], #daily-soup'): MountHandle[] {\n if (typeof document === 'undefined') return [];\n const nodes = document.querySelectorAll<HTMLElement>(selector);\n const handles: MountHandle[] = [];\n nodes.forEach((node) => {\n const lang = (node.dataset.lang as Lang | undefined) ?? 'zh';\n const theme = (node.dataset.theme as 'auto' | 'light' | 'dark' | undefined) ?? 'auto';\n const scheduleUrl = node.dataset.scheduleUrl;\n const maxWidth = node.dataset.maxWidth;\n handles.push(mount(node, { lang, theme, scheduleUrl, maxWidth }));\n });\n return handles;\n}\n", "import type { Lang } from './types';\n\ninterface UiStrings {\n copy: string;\n copied: string;\n share: string;\n source: string;\n poweredBy: string;\n attributedPopular: string;\n shareX: string;\n shareLine: string;\n loadFailed: string;\n}\n\nconst STRINGS: Record<Lang, UiStrings> = {\n zh: {\n copy: '\u8907\u88FD',\n copied: '\u5DF2\u8907\u88FD',\n share: '\u5206\u4EAB',\n source: '\u51FA\u8655',\n poweredBy: '\u7531 mshmwr \u63D0\u4F9B',\n attributedPopular: '\u50B3\u7D71\u6B78\u5C6C',\n shareX: '\u5206\u4EAB\u5230 X',\n shareLine: '\u5206\u4EAB\u5230 LINE',\n loadFailed: '\u672C\u65E5\u5C0F\u8A9E\u8F09\u5165\u5931\u6557',\n },\n};\n\nexport function t(lang: Lang): UiStrings {\n return STRINGS[lang] ?? STRINGS.zh;\n}\n\nexport type { UiStrings };\n", "import type { ThemeConfig, ResolvedTheme, ThemeColors } from './types';\n\nfunction isThemeColors(config: ThemeConfig): config is ThemeColors {\n return typeof config === 'object' && config !== null;\n}\n\nexport function resolveTheme(config: ThemeConfig): ResolvedTheme {\n if (isThemeColors(config)) return config.base ?? 'light';\n if (config === 'light' || config === 'dark') return config;\n if (typeof window === 'undefined' || !window.matchMedia) return 'light';\n return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';\n}\n\nexport function getThemeColors(config: ThemeConfig): ThemeColors | null {\n return isThemeColors(config) ? config : null;\n}\n\nexport function watchSystemTheme(cb: (theme: ResolvedTheme) => void): () => void {\n if (typeof window === 'undefined' || !window.matchMedia) return () => {};\n const mql = window.matchMedia('(prefers-color-scheme: dark)');\n const handler = (e: MediaQueryListEvent) => cb(e.matches ? 'dark' : 'light');\n mql.addEventListener('change', handler);\n return () => mql.removeEventListener('change', handler);\n}\n", "const SHARE_URL = 'https://daily-soup-widget.vercel.app';\n\nexport interface ShareContent {\n text: string;\n author: string;\n}\n\nexport async function copyToClipboard(content: ShareContent): Promise<boolean> {\n const payload = `${content.text} \u2014 ${content.author}`;\n if (typeof navigator !== 'undefined' && navigator.clipboard) {\n try {\n await navigator.clipboard.writeText(payload);\n return true;\n } catch {\n return false;\n }\n }\n return false;\n}\n\nexport function buildXShareUrl(content: ShareContent): string {\n const text = encodeURIComponent(`${content.text} \u2014 ${content.author}`);\n const url = encodeURIComponent(SHARE_URL);\n return `https://twitter.com/intent/tweet?text=${text}&url=${url}`;\n}\n\nexport function buildLineShareUrl(content: ShareContent): string {\n const url = encodeURIComponent(`${SHARE_URL} \u2014 ${content.text}`);\n return `https://social-plugins.line.me/lineit/share?url=${url}`;\n}\n", "export const WIDGET_STYLES = `\n :host { all: initial; display: block; font-family: system-ui, -apple-system, \"Segoe UI\", Roboto, \"Noto Sans TC\", sans-serif; }\n * { box-sizing: border-box; }\n .ds-card {\n container-type: inline-size;\n width: 100%;\n max-width: var(--ds-max-width, 32rem);\n margin: 0 auto;\n padding: 1.25rem 1.5rem;\n border-radius: 0.75rem;\n border: 1px solid var(--ds-border);\n background: var(--ds-bg);\n color: var(--ds-fg);\n font-size: clamp(0.875rem, 2.5cqi, 1.25rem);\n line-height: 1.7;\n transition: background 0.2s ease, color 0.2s ease;\n }\n .ds-card.ds-light {\n --ds-bg: #fdfcf7;\n --ds-fg: #1f2933;\n --ds-accent: #5b6b9e;\n --ds-muted: #6b7280;\n --ds-border: #e5e7eb;\n }\n .ds-card.ds-dark {\n --ds-bg: #1a1d24;\n --ds-fg: #e5e7eb;\n --ds-accent: #9aa9d4;\n --ds-muted: #9ca3af;\n --ds-border: #2d323d;\n }\n .ds-quote {\n margin: 0 0 0.875rem;\n font-size: 1.1em;\n font-weight: 500;\n letter-spacing: 0.01em;\n white-space: pre-wrap;\n }\n .ds-quote::before { content: '\\\\201C'; margin-right: 0.15em; color: var(--ds-accent); }\n .ds-quote::after { content: '\\\\201D'; margin-left: 0.15em; color: var(--ds-accent); }\n .ds-meta { display: flex; flex-direction: column; gap: 0.15rem; margin-bottom: 0.875rem; font-size: 0.875em; color: var(--ds-muted); }\n .ds-author { font-weight: 500; color: var(--ds-fg); }\n .ds-source { font-size: 0.95em; }\n .ds-source a { color: var(--ds-accent); text-decoration: none; }\n .ds-source a:hover { text-decoration: underline; }\n .ds-flag { display: inline-block; margin-left: 0.25rem; padding: 0 0.4rem; font-size: 0.75em; border: 1px solid var(--ds-border); border-radius: 9999px; color: var(--ds-muted); }\n .ds-actions { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; margin-top: 0.875rem; padding-top: 0.875rem; border-top: 1px solid var(--ds-border); }\n .ds-share { display: flex; gap: 0.35rem; }\n .ds-btn {\n appearance: none;\n background: transparent;\n color: var(--ds-fg);\n border: 1px solid var(--ds-border);\n border-radius: 0.5rem;\n padding: 0.3rem 0.7rem;\n font-size: 0.85em;\n font-family: inherit;\n cursor: pointer;\n transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;\n text-decoration: none;\n display: inline-flex;\n align-items: center;\n gap: 0.25rem;\n }\n .ds-btn:hover { color: var(--ds-accent); border-color: var(--ds-accent); }\n .ds-btn:focus-visible { outline: 2px solid var(--ds-accent); outline-offset: 2px; }\n .ds-btn.ds-toast { color: var(--ds-accent); border-color: var(--ds-accent); }\n .ds-powered { font-size: 0.75em; color: var(--ds-muted); }\n .ds-powered a { color: var(--ds-muted); text-decoration: none; }\n .ds-powered a:hover { text-decoration: underline; }\n .ds-skeleton .ds-quote { background: var(--ds-border); border-radius: 0.25rem; color: transparent; }\n .ds-skeleton .ds-quote::before, .ds-skeleton .ds-quote::after { content: ''; }\n .ds-error { color: var(--ds-muted); font-size: 0.875em; }\n\n @container (max-width: 320px) {\n .ds-card { padding: 1rem 1.1rem; }\n .ds-share-label { display: none; }\n .ds-actions { flex-wrap: wrap; }\n }\n @container (min-width: 500px) {\n .ds-quote { font-size: 1.25em; }\n .ds-meta { flex-direction: row; flex-wrap: wrap; align-items: baseline; gap: 0.25rem 0.75rem; }\n }\n @container (min-width: 700px) {\n .ds-card { padding: 1.75rem 2rem; }\n .ds-quote { font-size: 1.35em; margin-bottom: 1.125rem; }\n .ds-meta { gap: 0.25rem 1rem; margin-bottom: 1.125rem; }\n .ds-actions { margin-top: 1.125rem; padding-top: 1.125rem; }\n }\n @media (prefers-reduced-motion: reduce) {\n .ds-card, .ds-btn { transition: none; }\n }\n`;\n", "export function todayUtc8(now: Date = new Date()): string {\n const utc8 = new Date(now.getTime() + 8 * 60 * 60 * 1000);\n const yy = utc8.getUTCFullYear();\n const mm = String(utc8.getUTCMonth() + 1).padStart(2, '0');\n const dd = String(utc8.getUTCDate()).padStart(2, '0');\n return `${yy}-${mm}-${dd}`;\n}\n", "import { useEffect, useRef } from 'react';\nimport { mount } from './widget';\nimport type { DailySoupProps } from './types';\n\nexport function DailySoup({ lang = 'zh', theme = 'auto', scheduleUrl, className, maxWidth }: DailySoupProps) {\n const hostRef = useRef<HTMLDivElement | null>(null);\n\n const themeKey = typeof theme === 'string' ? theme : JSON.stringify(theme);\n useEffect(() => {\n if (!hostRef.current) return;\n const handle = mount(hostRef.current, { lang, theme, scheduleUrl, maxWidth });\n return () => handle.destroy();\n }, [lang, themeKey, scheduleUrl, maxWidth]);\n\n return <div ref={hostRef} className={className} data-daily-soup-host=\"\" />;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["import_react", "import_jsx_runtime"]
|
|
7
7
|
}
|