daily-soup-widget 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 coco (mshmwr)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,166 @@
1
+ # Daily Soup Widget
2
+
3
+ Embeddable daily-quote widget. Drop a `<script>` tag on any website, or `npm install` it as a React component. Same deterministic quote everywhere, every day — growth-themed, copyright-safe, zero config.
4
+
5
+ - **Live demo:** https://daily-soup-widget.vercel.app
6
+ - **Source:** https://github.com/mshmwr/daily-soup-widget
7
+ - **License:** MIT
8
+
9
+ ---
10
+
11
+ ## Why this exists
12
+
13
+ A widget shaped like an answer to three questions at once:
14
+
15
+ 1. **Technical practice** — Shadow DOM, UMD bundle, container queries, dual NPM/CDN distribution.
16
+ 2. **Portfolio piece** — a shipped end-to-end embeddable tool with a hosted live demo.
17
+ 3. **Future product** — v1 is intentionally small so v2 (analytics, personalisation, monetisation) has room.
18
+
19
+ The content theme is 人生成長 (personal growth). 30 seed quotes ship in v1; one per day, deterministically scheduled by UTC+8 date. Past dates never change once published.
20
+
21
+ ---
22
+
23
+ ## Install — script tag (any website)
24
+
25
+ ```html
26
+ <div id="daily-soup"></div>
27
+ <script src="https://daily-soup-widget.vercel.app/embed.js" async></script>
28
+ ```
29
+
30
+ Optional config via `data-*` attributes:
31
+
32
+ ```html
33
+ <div data-daily-soup data-lang="en" data-theme="dark"></div>
34
+ ```
35
+
36
+ Multiple instances on one page are supported — the bundle auto-mounts every `[data-daily-soup]` and `#daily-soup` node it finds.
37
+
38
+ ## Install — NPM (React / Next.js)
39
+
40
+ ```bash
41
+ npm install daily-soup-widget
42
+ ```
43
+
44
+ ```tsx
45
+ import { DailySoup } from 'daily-soup-widget';
46
+
47
+ export default function Page() {
48
+ return <DailySoup lang="zh" theme="auto" />;
49
+ }
50
+ ```
51
+
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
+
54
+ ## Install — framework-agnostic ESM
55
+
56
+ ```ts
57
+ import { mount } from 'daily-soup-widget';
58
+
59
+ const host = document.querySelector('#my-mount-point') as HTMLElement;
60
+ const handle = mount(host, { lang: 'zh', theme: 'auto' });
61
+ // later: handle.destroy();
62
+ ```
63
+
64
+ ---
65
+
66
+ ## Configuration
67
+
68
+ | Option | Values | Default | Description |
69
+ | ------------- | ----------------------------------- | -------------------------------- | ----------------------------------------------------------------- |
70
+ | `lang` | `'zh'` / `'en'` | `'zh'` | Content language. One value per widget instance. |
71
+ | `theme` | `'auto'` / `'light'` / `'dark'` | `'auto'` | `auto` follows host's `prefers-color-scheme` and reacts to changes. |
72
+ | `scheduleUrl` | any HTTPS URL or `''` (same-origin) | `https://daily-soup-widget.vercel.app` | Override the CDN that serves `/schedule-<lang>.json`. |
73
+
74
+ Layout is **not** configurable. The widget uses container queries against its own width — drop it in a 200px sidebar or a 700px content column and it adapts. Three breakpoints: <320px (icon-only share row), 320–500px (standard), >500px (larger quote).
75
+
76
+ ---
77
+
78
+ ## How "today" is decided
79
+
80
+ `schedule-<lang>.json` ships as a static JSON file mapping calendar date → quote ID:
81
+
82
+ ```json
83
+ {
84
+ "launchDate": "2026-05-15",
85
+ "entries": {
86
+ "2026-05-15": "0001",
87
+ "2026-05-16": "0003"
88
+ },
89
+ "quotes": { "0001": { "text": "...", "author": "..." } }
90
+ }
91
+ ```
92
+
93
+ The client fetches one JSON, computes today's UTC+8 date, looks up the entry, and renders. **No second request. No cold start. No personalisation.** Past dates carry permanent content even if the quote pool reshuffles.
94
+
95
+ A GitHub Actions cron (daily, 02:00 UTC) regenerates the JSON to extend the rolling 90-day window and redeploys. New quotes appended to the pool extend the rotation tail; existing schedule slots stay untouched.
96
+
97
+ ---
98
+
99
+ ## What's in the box
100
+
101
+ - **30 seed quotes** spanning six growth dimensions: 行動 / 學習 / 堅持 / 心態 / 蛻變 / 使命.
102
+ - **20 Chinese + 10 English.** Of the Chinese: 7 vernacular (白話/半白), 10 classical (文言), 3 poetry (詩詞).
103
+ - **Copyright posture:** all globally public-domain or PD-in-Taiwan. Each quote carries author + source attribution. Six entries marked `popular-attribution` for traditional / popularly-attributed sources.
104
+
105
+ Browse `content/quotes/*.md` to see the seed. Adding a quote = one PR per markdown file.
106
+
107
+ ---
108
+
109
+ ## Repository layout
110
+
111
+ ```
112
+ content/quotes/ hand-curated markdown quotes (the source of truth)
113
+ scripts/
114
+ build-schedule.ts content/*.md → dist/schedule-{zh,en}.json
115
+ build-bundle.ts esbuild UMD + ESM + CJS bundles
116
+ src/
117
+ embed.ts UMD entry (CDN, auto-mounts)
118
+ index.ts ESM entry (NPM)
119
+ component.tsx React wrapper
120
+ widget.ts framework-agnostic core (Shadow DOM, render, fetch)
121
+ theme.ts auto/light/dark resolution + system-theme listener
122
+ i18n.ts UI strings (zh, en)
123
+ share.ts copy / X / LINE share builders
124
+ styles.ts widget CSS (injected into Shadow DOM)
125
+ app/ Next.js landing page (live demo + install snippets)
126
+ tests/
127
+ unit/ Vitest — build-schedule, theme, i18n, date
128
+ e2e/ Playwright — landing page smoke
129
+ ```
130
+
131
+ ---
132
+
133
+ ## Local development
134
+
135
+ ```bash
136
+ npm install
137
+ npm run build:schedule # content/quotes/*.md → dist/schedule-*.json
138
+ npm run build:bundle # esbuild UMD + ESM + CJS into dist/ + public/
139
+ npm run dev # next dev — landing page at http://localhost:3000
140
+ npm test # vitest
141
+ npm run test:e2e # playwright (requires running dev server)
142
+ npm run typecheck # tsc --noEmit
143
+ npm run build # full prod build (schedule + bundle + types + Next.js)
144
+ ```
145
+
146
+ ### Adding a quote
147
+
148
+ 1. Create `content/quotes/<id>-<slug>.md` with frontmatter (see existing files for the schema).
149
+ 2. `npm run build:schedule` — extends the schedule, preserving past dates.
150
+ 3. Commit + PR. Merge triggers the GH Action to redeploy.
151
+
152
+ ---
153
+
154
+ ## Browser support
155
+
156
+ - Modern evergreens (Chrome / Safari / Firefox / Edge), 2022+.
157
+ - Container queries: Chrome 105+ / Safari 16+ / Firefox 110+. The widget renders without RWD on older browsers (fixed-size card).
158
+ - Shadow DOM fallback: light-DOM render with a console warning.
159
+
160
+ ---
161
+
162
+ ## License
163
+
164
+ MIT © 2026 coco (mshmwr). See [`LICENSE`](./LICENSE).
165
+
166
+ Quote sources are public-domain or PD-in-Taiwan; see frontmatter on each quote file for attribution.
@@ -0,0 +1,381 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ DailySoup: () => DailySoup,
24
+ mount: () => mount,
25
+ mountAll: () => mountAll
26
+ });
27
+ module.exports = __toCommonJS(index_exports);
28
+
29
+ // src/i18n.ts
30
+ var STRINGS = {
31
+ zh: {
32
+ copy: "\u8907\u88FD",
33
+ copied: "\u5DF2\u8907\u88FD",
34
+ share: "\u5206\u4EAB",
35
+ source: "\u51FA\u8655",
36
+ poweredBy: "\u7531 coco-c.dev \u63D0\u4F9B",
37
+ attributedPopular: "\u50B3\u7D71\u6B78\u5C6C",
38
+ shareX: "\u5206\u4EAB\u5230 X",
39
+ shareLine: "\u5206\u4EAB\u5230 LINE",
40
+ loadFailed: "\u672C\u65E5\u5C0F\u8A9E\u8F09\u5165\u5931\u6557"
41
+ },
42
+ en: {
43
+ copy: "Copy",
44
+ copied: "Copied!",
45
+ share: "Share",
46
+ source: "Source",
47
+ poweredBy: "powered by coco-c.dev",
48
+ attributedPopular: "popularly attributed",
49
+ shareX: "Share on X",
50
+ shareLine: "Share on LINE",
51
+ loadFailed: "Failed to load daily quote"
52
+ }
53
+ };
54
+ function t(lang) {
55
+ return STRINGS[lang] ?? STRINGS.zh;
56
+ }
57
+
58
+ // src/theme.ts
59
+ function resolveTheme(config) {
60
+ if (config === "light" || config === "dark") return config;
61
+ if (typeof window === "undefined" || !window.matchMedia) return "light";
62
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
63
+ }
64
+ function watchSystemTheme(cb) {
65
+ if (typeof window === "undefined" || !window.matchMedia) return () => {
66
+ };
67
+ const mql = window.matchMedia("(prefers-color-scheme: dark)");
68
+ const handler = (e) => cb(e.matches ? "dark" : "light");
69
+ mql.addEventListener("change", handler);
70
+ return () => mql.removeEventListener("change", handler);
71
+ }
72
+
73
+ // src/share.ts
74
+ var SHARE_URL = "https://daily-soup-widget.vercel.app";
75
+ async function copyToClipboard(content) {
76
+ const payload = `${content.text} \u2014 ${content.author}`;
77
+ if (typeof navigator !== "undefined" && navigator.clipboard) {
78
+ try {
79
+ await navigator.clipboard.writeText(payload);
80
+ return true;
81
+ } catch {
82
+ return false;
83
+ }
84
+ }
85
+ return false;
86
+ }
87
+ function buildXShareUrl(content) {
88
+ const text = encodeURIComponent(`${content.text} \u2014 ${content.author}`);
89
+ const url = encodeURIComponent(SHARE_URL);
90
+ return `https://twitter.com/intent/tweet?text=${text}&url=${url}`;
91
+ }
92
+ function buildLineShareUrl(content) {
93
+ const url = encodeURIComponent(`${SHARE_URL} \u2014 ${content.text}`);
94
+ return `https://social-plugins.line.me/lineit/share?url=${url}`;
95
+ }
96
+
97
+ // src/styles.ts
98
+ var WIDGET_STYLES = `
99
+ :host { all: initial; display: block; font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Noto Sans TC", sans-serif; }
100
+ * { box-sizing: border-box; }
101
+ .ds-card {
102
+ container-type: inline-size;
103
+ width: 100%;
104
+ max-width: 32rem;
105
+ margin: 0 auto;
106
+ padding: 1.25rem 1.5rem;
107
+ border-radius: 0.75rem;
108
+ border: 1px solid var(--ds-border);
109
+ background: var(--ds-bg);
110
+ color: var(--ds-fg);
111
+ font-size: clamp(0.875rem, 2.5cqi, 1.125rem);
112
+ line-height: 1.7;
113
+ transition: background 0.2s ease, color 0.2s ease;
114
+ }
115
+ .ds-card.ds-light {
116
+ --ds-bg: #fdfcf7;
117
+ --ds-fg: #1f2933;
118
+ --ds-accent: #5b6b9e;
119
+ --ds-muted: #6b7280;
120
+ --ds-border: #e5e7eb;
121
+ }
122
+ .ds-card.ds-dark {
123
+ --ds-bg: #1a1d24;
124
+ --ds-fg: #e5e7eb;
125
+ --ds-accent: #9aa9d4;
126
+ --ds-muted: #9ca3af;
127
+ --ds-border: #2d323d;
128
+ }
129
+ .ds-quote {
130
+ margin: 0 0 0.875rem;
131
+ font-size: 1.1em;
132
+ font-weight: 500;
133
+ letter-spacing: 0.01em;
134
+ white-space: pre-wrap;
135
+ }
136
+ .ds-quote::before { content: '\\201C'; margin-right: 0.15em; color: var(--ds-accent); }
137
+ .ds-quote::after { content: '\\201D'; margin-left: 0.15em; color: var(--ds-accent); }
138
+ .ds-meta { display: flex; flex-direction: column; gap: 0.15rem; margin-bottom: 0.875rem; font-size: 0.875em; color: var(--ds-muted); }
139
+ .ds-author { font-weight: 500; color: var(--ds-fg); }
140
+ .ds-source { font-size: 0.95em; }
141
+ .ds-source a { color: var(--ds-accent); text-decoration: none; }
142
+ .ds-source a:hover { text-decoration: underline; }
143
+ .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); }
144
+ .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); }
145
+ .ds-share { display: flex; gap: 0.35rem; }
146
+ .ds-btn {
147
+ appearance: none;
148
+ background: transparent;
149
+ color: var(--ds-fg);
150
+ border: 1px solid var(--ds-border);
151
+ border-radius: 0.5rem;
152
+ padding: 0.3rem 0.7rem;
153
+ font-size: 0.85em;
154
+ font-family: inherit;
155
+ cursor: pointer;
156
+ transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
157
+ text-decoration: none;
158
+ display: inline-flex;
159
+ align-items: center;
160
+ gap: 0.25rem;
161
+ }
162
+ .ds-btn:hover { background: var(--ds-accent); color: var(--ds-bg); border-color: var(--ds-accent); }
163
+ .ds-btn:focus-visible { outline: 2px solid var(--ds-accent); outline-offset: 2px; }
164
+ .ds-btn.ds-toast { background: var(--ds-accent); color: var(--ds-bg); border-color: var(--ds-accent); }
165
+ .ds-powered { font-size: 0.75em; color: var(--ds-muted); }
166
+ .ds-powered a { color: var(--ds-muted); text-decoration: none; }
167
+ .ds-powered a:hover { text-decoration: underline; }
168
+ .ds-skeleton .ds-quote { background: var(--ds-border); border-radius: 0.25rem; color: transparent; }
169
+ .ds-skeleton .ds-quote::before, .ds-skeleton .ds-quote::after { content: ''; }
170
+ .ds-error { color: var(--ds-muted); font-size: 0.875em; }
171
+
172
+ @container (max-width: 320px) {
173
+ .ds-card { padding: 1rem 1.1rem; }
174
+ .ds-share-label { display: none; }
175
+ .ds-actions { flex-wrap: wrap; }
176
+ }
177
+ @container (min-width: 500px) {
178
+ .ds-quote { font-size: 1.25em; }
179
+ }
180
+ @media (prefers-reduced-motion: reduce) {
181
+ .ds-card, .ds-btn { transition: none; }
182
+ }
183
+ `;
184
+
185
+ // src/date.ts
186
+ function todayUtc8(now = /* @__PURE__ */ new Date()) {
187
+ const utc8 = new Date(now.getTime() + 8 * 60 * 60 * 1e3);
188
+ const yy = utc8.getUTCFullYear();
189
+ const mm = String(utc8.getUTCMonth() + 1).padStart(2, "0");
190
+ const dd = String(utc8.getUTCDate()).padStart(2, "0");
191
+ return `${yy}-${mm}-${dd}`;
192
+ }
193
+
194
+ // src/widget.ts
195
+ var DEFAULT_SCHEDULE_BASE = "https://daily-soup-widget.vercel.app";
196
+ function escape(s) {
197
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
198
+ }
199
+ function attachRoot(host) {
200
+ if (typeof host.attachShadow === "function") {
201
+ if (host.shadowRoot) return host.shadowRoot;
202
+ return host.attachShadow({ mode: "open" });
203
+ }
204
+ console.warn("[daily-soup] attachShadow unsupported, falling back to light DOM");
205
+ return host;
206
+ }
207
+ function injectStyles(root) {
208
+ const style = document.createElement("style");
209
+ style.textContent = WIDGET_STYLES;
210
+ root.appendChild(style);
211
+ }
212
+ function buildSkeleton(theme) {
213
+ const card = document.createElement("div");
214
+ card.className = `ds-card ds-${theme} ds-skeleton`;
215
+ card.innerHTML = `
216
+ <div class="ds-quote">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</div>
217
+ <div class="ds-meta"><span class="ds-author">&nbsp;</span><span class="ds-source">&nbsp;</span></div>
218
+ `;
219
+ return card;
220
+ }
221
+ function renderQuote(card, quote, lang, theme) {
222
+ const s = t(lang);
223
+ card.className = `ds-card ds-${theme}`;
224
+ const sourceLabel = quote.sourceUrl ? `<a href="${escape(quote.sourceUrl)}" target="_blank" rel="noopener noreferrer">${escape(quote.source)}</a>` : escape(quote.source);
225
+ const flag = quote.attribution === "popular-attribution" ? `<span class="ds-flag">${escape(s.attributedPopular)}</span>` : "";
226
+ card.innerHTML = `
227
+ <p class="ds-quote">${escape(quote.text)}</p>
228
+ <div class="ds-meta">
229
+ <span class="ds-author">\u2014 ${escape(quote.author)}${flag}</span>
230
+ ${quote.source ? `<span class="ds-source">${s.source}\uFF1A${sourceLabel}</span>` : ""}
231
+ </div>
232
+ <div class="ds-actions">
233
+ <div class="ds-share">
234
+ <button class="ds-btn" data-action="copy" type="button" aria-label="${escape(s.copy)}">
235
+ <span aria-hidden="true">\u29C9</span><span class="ds-share-label">${escape(s.copy)}</span>
236
+ </button>
237
+ <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)}">
238
+ <span aria-hidden="true">\u{1D54F}</span><span class="ds-share-label">X</span>
239
+ </a>
240
+ <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)}">
241
+ <span aria-hidden="true">L</span><span class="ds-share-label">LINE</span>
242
+ </a>
243
+ </div>
244
+ <span class="ds-powered"><a href="https://coco-c.dev" target="_blank" rel="noopener noreferrer">${s.poweredBy}</a></span>
245
+ </div>
246
+ `;
247
+ const copyBtn = card.querySelector('[data-action="copy"]');
248
+ if (copyBtn) {
249
+ copyBtn.addEventListener("click", async () => {
250
+ const ok = await copyToClipboard({ text: quote.text, author: quote.author });
251
+ if (ok) {
252
+ const label = copyBtn.querySelector(".ds-share-label");
253
+ const originalLabel = label?.textContent ?? "";
254
+ if (label) label.textContent = s.copied;
255
+ copyBtn.classList.add("ds-toast");
256
+ setTimeout(() => {
257
+ if (label) label.textContent = originalLabel;
258
+ copyBtn.classList.remove("ds-toast");
259
+ }, 2e3);
260
+ }
261
+ });
262
+ }
263
+ }
264
+ function renderError(card, lang, theme) {
265
+ card.className = `ds-card ds-${theme}`;
266
+ const s = t(lang);
267
+ card.innerHTML = `<p class="ds-error">${escape(s.loadFailed)}</p>`;
268
+ }
269
+ async function fetchSchedule(scheduleUrl, lang) {
270
+ const base = scheduleUrl.replace(/\/$/, "");
271
+ const url = `${base}/schedule-${lang}.json`;
272
+ try {
273
+ const init = base === "" ? { credentials: "omit" } : { credentials: "omit", mode: "cors" };
274
+ const res = await fetch(url, init);
275
+ if (!res.ok) return null;
276
+ return await res.json();
277
+ } catch {
278
+ return null;
279
+ }
280
+ }
281
+ function pickQuote(schedule) {
282
+ const today = todayUtc8();
283
+ const id = schedule.entries[today];
284
+ if (id && schedule.quotes[id]) return schedule.quotes[id];
285
+ const fallbackId = Object.keys(schedule.quotes).sort()[0];
286
+ if (fallbackId && schedule.quotes[fallbackId]) {
287
+ console.warn("[daily-soup] today entry missing or stale, falling back to first quote");
288
+ return schedule.quotes[fallbackId];
289
+ }
290
+ return null;
291
+ }
292
+ function mount(host, options = {}) {
293
+ const lang = options.lang ?? "zh";
294
+ const themeConfig = options.theme ?? "auto";
295
+ const scheduleUrl = options.scheduleUrl === void 0 ? DEFAULT_SCHEDULE_BASE : options.scheduleUrl;
296
+ let resolvedTheme = resolveTheme(themeConfig);
297
+ const root = attachRoot(host);
298
+ if (root === host) {
299
+ host.textContent = "";
300
+ } else {
301
+ while (root.firstChild) root.removeChild(root.firstChild);
302
+ }
303
+ injectStyles(root);
304
+ const card = buildSkeleton(resolvedTheme);
305
+ root.appendChild(card);
306
+ const state = {
307
+ lang,
308
+ themeConfig,
309
+ scheduleUrl,
310
+ host,
311
+ root,
312
+ cardEl: card,
313
+ unwatchTheme: () => {
314
+ }
315
+ };
316
+ if (themeConfig === "auto") {
317
+ state.unwatchTheme = watchSystemTheme((t2) => {
318
+ resolvedTheme = t2;
319
+ state.cardEl.classList.remove("ds-light", "ds-dark");
320
+ state.cardEl.classList.add(`ds-${t2}`);
321
+ });
322
+ }
323
+ let cancelled = false;
324
+ let retried = false;
325
+ const load = async () => {
326
+ const schedule = await fetchSchedule(scheduleUrl, lang);
327
+ if (cancelled) return;
328
+ if (!schedule) {
329
+ if (!retried) {
330
+ retried = true;
331
+ setTimeout(load, 2e3);
332
+ return;
333
+ }
334
+ renderError(state.cardEl, lang, resolvedTheme);
335
+ return;
336
+ }
337
+ const quote = pickQuote(schedule);
338
+ if (!quote) {
339
+ renderError(state.cardEl, lang, resolvedTheme);
340
+ return;
341
+ }
342
+ renderQuote(state.cardEl, quote, lang, resolvedTheme);
343
+ };
344
+ load();
345
+ return {
346
+ destroy() {
347
+ cancelled = true;
348
+ state.unwatchTheme();
349
+ if (root === host) {
350
+ host.textContent = "";
351
+ } else if (host.shadowRoot) {
352
+ while (host.shadowRoot.firstChild) host.shadowRoot.removeChild(host.shadowRoot.firstChild);
353
+ }
354
+ }
355
+ };
356
+ }
357
+ function mountAll(selector = "[data-daily-soup], #daily-soup") {
358
+ if (typeof document === "undefined") return [];
359
+ const nodes = document.querySelectorAll(selector);
360
+ const handles = [];
361
+ nodes.forEach((node) => {
362
+ const lang = node.dataset.lang ?? "zh";
363
+ const theme = node.dataset.theme ?? "auto";
364
+ const scheduleUrl = node.dataset.scheduleUrl;
365
+ handles.push(mount(node, { lang, theme, scheduleUrl }));
366
+ });
367
+ return handles;
368
+ }
369
+
370
+ // src/component.tsx
371
+ var import_react = require("react");
372
+ function DailySoup({ lang = "zh", theme = "auto", scheduleUrl, className }) {
373
+ const hostRef = (0, import_react.useRef)(null);
374
+ (0, import_react.useEffect)(() => {
375
+ if (!hostRef.current) return;
376
+ const handle = mount(hostRef.current, { lang, theme, scheduleUrl });
377
+ return () => handle.destroy();
378
+ }, [lang, theme, scheduleUrl]);
379
+ return /* @__PURE__ */ React.createElement("div", { ref: hostRef, className, "data-daily-soup-host": "" });
380
+ }
381
+ //# sourceMappingURL=embed.cjs.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/index.ts", "../src/i18n.ts", "../src/theme.ts", "../src/share.ts", "../src/styles.ts", "../src/date.ts", "../src/widget.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 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 coco-c.dev \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 en: {\n copy: 'Copy',\n copied: 'Copied!',\n share: 'Share',\n source: 'Source',\n poweredBy: 'powered by coco-c.dev',\n attributedPopular: 'popularly attributed',\n shareX: 'Share on X',\n shareLine: 'Share on LINE',\n loadFailed: 'Failed to load daily quote',\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 } from './types';\n\nexport function resolveTheme(config: ThemeConfig): ResolvedTheme {\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 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: 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.125rem);\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 }\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 } 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, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;');\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\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</div>\n <div class=\"ds-meta\"><span class=\"ds-author\">&nbsp;</span><span class=\"ds-source\">&nbsp;</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://coco-c.dev\" 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\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\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 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 ThemeConfig | undefined) ?? 'auto';\n const scheduleUrl = node.dataset.scheduleUrl;\n handles.push(mount(node, { lang, theme, scheduleUrl }));\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 }: DailySoupProps) {\n const hostRef = useRef<HTMLDivElement | null>(null);\n\n useEffect(() => {\n if (!hostRef.current) return;\n const handle = mount(hostRef.current, { lang, theme, scheduleUrl });\n return () => handle.destroy();\n }, [lang, theme, scheduleUrl]);\n\n return <div ref={hostRef} className={className} data-daily-soup-host=\"\" />;\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["t"]
7
+ }