daily-soup-widget 0.2.2 → 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 +220 -158
- package/dist/embed.cjs.js.map +4 -4
- package/dist/embed.esm.js +219 -157
- package/dist/embed.esm.js.map +4 -4
- package/dist/embed.js +67 -25
- package/dist/embed.js.map +4 -4
- package/dist/schedule-zh.json +3 -2
- package/package.json +2 -6
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 { 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 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 unwatchVisibility: () => void;\n schedule: Schedule | null;\n displayedDate: string;\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 unwatchVisibility: () => {},\n schedule: null,\n displayedDate: '',\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 state.schedule = schedule;\n const quote = pickQuote(schedule);\n if (!quote) {\n renderError(state.cardEl, lang, resolvedTheme);\n return;\n }\n state.displayedDate = todayUtc8();\n renderQuote(state.cardEl, quote, lang, resolvedTheme);\n };\n load();\n\n if (typeof document !== 'undefined') {\n const onVisibility = () => {\n if (document.visibilityState !== 'visible') return;\n if (!state.schedule) return;\n const today = todayUtc8();\n if (today === state.displayedDate) return;\n const quote = pickQuote(state.schedule);\n if (!quote) return;\n state.displayedDate = today;\n renderQuote(state.cardEl, quote, lang, resolvedTheme);\n };\n document.addEventListener('visibilitychange', onVisibility);\n state.unwatchVisibility = () => document.removeEventListener('visibilitychange', onVisibility);\n }\n\n return {\n destroy() {\n cancelled = true;\n state.unwatchTheme();\n state.unwatchVisibility();\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
|
}
|
package/dist/embed.esm.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
// src/widget.tsx
|
|
2
|
+
import { useCallback, useEffect, useState } from "react";
|
|
3
|
+
import { createRoot } from "react-dom/client";
|
|
4
|
+
import { flushSync } from "react-dom";
|
|
5
|
+
|
|
1
6
|
// src/i18n.ts
|
|
2
7
|
var STRINGS = {
|
|
3
8
|
zh: {
|
|
@@ -166,81 +171,9 @@ function todayUtc8(now = /* @__PURE__ */ new Date()) {
|
|
|
166
171
|
return `${yy}-${mm}-${dd}`;
|
|
167
172
|
}
|
|
168
173
|
|
|
169
|
-
// src/widget.
|
|
174
|
+
// src/widget.tsx
|
|
175
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
170
176
|
var DEFAULT_SCHEDULE_BASE = "https://daily-soup-widget.vercel.app";
|
|
171
|
-
function escape(s) {
|
|
172
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
173
|
-
}
|
|
174
|
-
function attachRoot(host) {
|
|
175
|
-
if (typeof host.attachShadow === "function") {
|
|
176
|
-
if (host.shadowRoot) return host.shadowRoot;
|
|
177
|
-
return host.attachShadow({ mode: "open" });
|
|
178
|
-
}
|
|
179
|
-
console.warn("[daily-soup] attachShadow unsupported, falling back to light DOM");
|
|
180
|
-
return host;
|
|
181
|
-
}
|
|
182
|
-
function injectStyles(root) {
|
|
183
|
-
const style = document.createElement("style");
|
|
184
|
-
style.textContent = WIDGET_STYLES;
|
|
185
|
-
root.appendChild(style);
|
|
186
|
-
}
|
|
187
|
-
function buildSkeleton(theme) {
|
|
188
|
-
const card = document.createElement("div");
|
|
189
|
-
card.className = `ds-card ds-${theme} ds-skeleton`;
|
|
190
|
-
card.innerHTML = `
|
|
191
|
-
<div class="ds-quote"> </div>
|
|
192
|
-
<div class="ds-meta"><span class="ds-author"> </span><span class="ds-source"> </span></div>
|
|
193
|
-
`;
|
|
194
|
-
return card;
|
|
195
|
-
}
|
|
196
|
-
function renderQuote(card, quote, lang, theme) {
|
|
197
|
-
const s = t(lang);
|
|
198
|
-
card.className = `ds-card ds-${theme}`;
|
|
199
|
-
const sourceLabel = quote.sourceUrl ? `<a href="${escape(quote.sourceUrl)}" target="_blank" rel="noopener noreferrer">${escape(quote.source)}</a>` : escape(quote.source);
|
|
200
|
-
const flag = quote.attribution === "popular-attribution" ? `<span class="ds-flag">${escape(s.attributedPopular)}</span>` : "";
|
|
201
|
-
card.innerHTML = `
|
|
202
|
-
<p class="ds-quote">${escape(quote.text)}</p>
|
|
203
|
-
<div class="ds-meta">
|
|
204
|
-
<span class="ds-author">\u2014 ${escape(quote.author)}${flag}</span>
|
|
205
|
-
${quote.source ? `<span class="ds-source">${s.source}\uFF1A${sourceLabel}</span>` : ""}
|
|
206
|
-
</div>
|
|
207
|
-
<div class="ds-actions">
|
|
208
|
-
<div class="ds-share">
|
|
209
|
-
<button class="ds-btn" data-action="copy" type="button" aria-label="${escape(s.copy)}">
|
|
210
|
-
<span aria-hidden="true">\u29C9</span><span class="ds-share-label">${escape(s.copy)}</span>
|
|
211
|
-
</button>
|
|
212
|
-
<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)}">
|
|
213
|
-
<span aria-hidden="true">\u{1D54F}</span><span class="ds-share-label">X</span>
|
|
214
|
-
</a>
|
|
215
|
-
<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)}">
|
|
216
|
-
<span aria-hidden="true">L</span><span class="ds-share-label">LINE</span>
|
|
217
|
-
</a>
|
|
218
|
-
</div>
|
|
219
|
-
<span class="ds-powered"><a href="https://personal-site-mocha-chi.vercel.app" target="_blank" rel="noopener noreferrer">${s.poweredBy}</a></span>
|
|
220
|
-
</div>
|
|
221
|
-
`;
|
|
222
|
-
const copyBtn = card.querySelector('[data-action="copy"]');
|
|
223
|
-
if (copyBtn) {
|
|
224
|
-
copyBtn.addEventListener("click", async () => {
|
|
225
|
-
const ok = await copyToClipboard({ text: quote.text, author: quote.author });
|
|
226
|
-
if (ok) {
|
|
227
|
-
const label = copyBtn.querySelector(".ds-share-label");
|
|
228
|
-
const originalLabel = label?.textContent ?? "";
|
|
229
|
-
if (label) label.textContent = s.copied;
|
|
230
|
-
copyBtn.classList.add("ds-toast");
|
|
231
|
-
setTimeout(() => {
|
|
232
|
-
if (label) label.textContent = originalLabel;
|
|
233
|
-
copyBtn.classList.remove("ds-toast");
|
|
234
|
-
}, 2e3);
|
|
235
|
-
}
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
function renderError(card, lang, theme) {
|
|
240
|
-
card.className = `ds-card ds-${theme}`;
|
|
241
|
-
const s = t(lang);
|
|
242
|
-
card.innerHTML = `<p class="ds-error">${escape(s.loadFailed)}</p>`;
|
|
243
|
-
}
|
|
244
177
|
async function fetchSchedule(scheduleUrl, lang) {
|
|
245
178
|
const base = scheduleUrl.replace(/\/$/, "");
|
|
246
179
|
const url = `${base}/schedule-${lang}.json`;
|
|
@@ -253,8 +186,7 @@ async function fetchSchedule(scheduleUrl, lang) {
|
|
|
253
186
|
return null;
|
|
254
187
|
}
|
|
255
188
|
}
|
|
256
|
-
function pickQuote(schedule) {
|
|
257
|
-
const today = todayUtc8();
|
|
189
|
+
function pickQuote(schedule, today) {
|
|
258
190
|
const id = schedule.entries[today];
|
|
259
191
|
if (id && schedule.quotes[id]) return schedule.quotes[id];
|
|
260
192
|
const fallbackId = Object.keys(schedule.quotes).sort()[0];
|
|
@@ -264,101 +196,231 @@ function pickQuote(schedule) {
|
|
|
264
196
|
}
|
|
265
197
|
return null;
|
|
266
198
|
}
|
|
267
|
-
function
|
|
268
|
-
const
|
|
199
|
+
function buildInlineStyle(themeConfig, maxWidth) {
|
|
200
|
+
const style = {};
|
|
201
|
+
const colors = getThemeColors(themeConfig);
|
|
269
202
|
if (colors) {
|
|
270
|
-
if (colors.bg)
|
|
271
|
-
if (colors.ink)
|
|
272
|
-
if (colors.muted)
|
|
273
|
-
if (colors.border)
|
|
274
|
-
if (colors.accent)
|
|
203
|
+
if (colors.bg) style["--ds-bg"] = colors.bg;
|
|
204
|
+
if (colors.ink) style["--ds-fg"] = colors.ink;
|
|
205
|
+
if (colors.muted) style["--ds-muted"] = colors.muted;
|
|
206
|
+
if (colors.border) style["--ds-border"] = colors.border;
|
|
207
|
+
if (colors.accent) style["--ds-accent"] = colors.accent;
|
|
275
208
|
}
|
|
276
|
-
if (maxWidth)
|
|
209
|
+
if (maxWidth) style["--ds-max-width"] = maxWidth;
|
|
210
|
+
return style;
|
|
211
|
+
}
|
|
212
|
+
function DailySoupWidget({ lang, themeConfig, scheduleUrl, maxWidth }) {
|
|
213
|
+
const [resolvedTheme, setResolvedTheme] = useState(() => resolveTheme(themeConfig));
|
|
214
|
+
const [schedule, setSchedule] = useState(null);
|
|
215
|
+
const [displayedDate, setDisplayedDate] = useState("");
|
|
216
|
+
const [quote, setQuote] = useState(null);
|
|
217
|
+
const [loadFailed, setLoadFailed] = useState(false);
|
|
218
|
+
const [copyToast, setCopyToast] = useState(false);
|
|
219
|
+
useEffect(() => {
|
|
220
|
+
if (themeConfig !== "auto") {
|
|
221
|
+
setResolvedTheme(resolveTheme(themeConfig));
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
setResolvedTheme(resolveTheme(themeConfig));
|
|
225
|
+
return watchSystemTheme(setResolvedTheme);
|
|
226
|
+
}, [themeConfig]);
|
|
227
|
+
useEffect(() => {
|
|
228
|
+
let cancelled = false;
|
|
229
|
+
let retried = false;
|
|
230
|
+
const load = async () => {
|
|
231
|
+
const sch = await fetchSchedule(scheduleUrl, lang);
|
|
232
|
+
if (cancelled) return;
|
|
233
|
+
if (!sch) {
|
|
234
|
+
if (!retried) {
|
|
235
|
+
retried = true;
|
|
236
|
+
setTimeout(load, 2e3);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
setLoadFailed(true);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const today = todayUtc8();
|
|
243
|
+
const q = pickQuote(sch, today);
|
|
244
|
+
if (!q) {
|
|
245
|
+
setLoadFailed(true);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
flushSync(() => {
|
|
249
|
+
setSchedule(sch);
|
|
250
|
+
setDisplayedDate(today);
|
|
251
|
+
setQuote(q);
|
|
252
|
+
});
|
|
253
|
+
};
|
|
254
|
+
load();
|
|
255
|
+
return () => {
|
|
256
|
+
cancelled = true;
|
|
257
|
+
};
|
|
258
|
+
}, [lang, scheduleUrl]);
|
|
259
|
+
useEffect(() => {
|
|
260
|
+
if (!schedule || typeof document === "undefined") return;
|
|
261
|
+
const onVisibility = () => {
|
|
262
|
+
if (document.visibilityState !== "visible") return;
|
|
263
|
+
const today = todayUtc8();
|
|
264
|
+
if (today === displayedDate) return;
|
|
265
|
+
const q = pickQuote(schedule, today);
|
|
266
|
+
if (!q) return;
|
|
267
|
+
flushSync(() => {
|
|
268
|
+
setDisplayedDate(today);
|
|
269
|
+
setQuote(q);
|
|
270
|
+
});
|
|
271
|
+
};
|
|
272
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
273
|
+
return () => document.removeEventListener("visibilitychange", onVisibility);
|
|
274
|
+
}, [schedule, displayedDate]);
|
|
275
|
+
const inlineStyle = buildInlineStyle(themeConfig, maxWidth);
|
|
276
|
+
const s = t(lang);
|
|
277
|
+
const handleCopy = useCallback(async () => {
|
|
278
|
+
if (!quote) return;
|
|
279
|
+
const ok = await copyToClipboard({ text: quote.text, author: quote.author });
|
|
280
|
+
if (ok) {
|
|
281
|
+
setCopyToast(true);
|
|
282
|
+
setTimeout(() => setCopyToast(false), 2e3);
|
|
283
|
+
}
|
|
284
|
+
}, [quote]);
|
|
285
|
+
if (loadFailed) {
|
|
286
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
287
|
+
/* @__PURE__ */ jsx("style", { children: WIDGET_STYLES }),
|
|
288
|
+
/* @__PURE__ */ jsx("div", { className: `ds-card ds-${resolvedTheme}`, style: inlineStyle, children: /* @__PURE__ */ jsx("p", { className: "ds-error", children: s.loadFailed }) })
|
|
289
|
+
] });
|
|
290
|
+
}
|
|
291
|
+
if (!quote) {
|
|
292
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
293
|
+
/* @__PURE__ */ jsx("style", { children: WIDGET_STYLES }),
|
|
294
|
+
/* @__PURE__ */ jsxs("div", { className: `ds-card ds-${resolvedTheme} ds-skeleton`, style: inlineStyle, children: [
|
|
295
|
+
/* @__PURE__ */ jsx("div", { className: "ds-quote", children: "\xA0".repeat(25) }),
|
|
296
|
+
/* @__PURE__ */ jsxs("div", { className: "ds-meta", children: [
|
|
297
|
+
/* @__PURE__ */ jsx("span", { className: "ds-author", children: "\xA0" }),
|
|
298
|
+
/* @__PURE__ */ jsx("span", { className: "ds-source", children: "\xA0" })
|
|
299
|
+
] })
|
|
300
|
+
] })
|
|
301
|
+
] });
|
|
302
|
+
}
|
|
303
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
304
|
+
/* @__PURE__ */ jsx("style", { children: WIDGET_STYLES }),
|
|
305
|
+
/* @__PURE__ */ jsxs("div", { className: `ds-card ds-${resolvedTheme}`, style: inlineStyle, children: [
|
|
306
|
+
/* @__PURE__ */ jsx("p", { className: "ds-quote", children: quote.text }),
|
|
307
|
+
/* @__PURE__ */ jsxs("div", { className: "ds-meta", children: [
|
|
308
|
+
/* @__PURE__ */ jsxs("span", { className: "ds-author", children: [
|
|
309
|
+
"\u2014 ",
|
|
310
|
+
quote.author,
|
|
311
|
+
quote.attribution === "popular-attribution" && /* @__PURE__ */ jsx("span", { className: "ds-flag", children: s.attributedPopular })
|
|
312
|
+
] }),
|
|
313
|
+
quote.source && /* @__PURE__ */ jsxs("span", { className: "ds-source", children: [
|
|
314
|
+
s.source,
|
|
315
|
+
"\uFF1A",
|
|
316
|
+
quote.sourceUrl ? /* @__PURE__ */ jsx("a", { href: quote.sourceUrl, target: "_blank", rel: "noopener noreferrer", children: quote.source }) : quote.source
|
|
317
|
+
] })
|
|
318
|
+
] }),
|
|
319
|
+
/* @__PURE__ */ jsxs("div", { className: "ds-actions", children: [
|
|
320
|
+
/* @__PURE__ */ jsxs("div", { className: "ds-share", children: [
|
|
321
|
+
/* @__PURE__ */ jsxs(
|
|
322
|
+
"button",
|
|
323
|
+
{
|
|
324
|
+
className: `ds-btn${copyToast ? " ds-toast" : ""}`,
|
|
325
|
+
"data-action": "copy",
|
|
326
|
+
type: "button",
|
|
327
|
+
"aria-label": s.copy,
|
|
328
|
+
onClick: handleCopy,
|
|
329
|
+
children: [
|
|
330
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u29C9" }),
|
|
331
|
+
/* @__PURE__ */ jsx("span", { className: "ds-share-label", children: copyToast ? s.copied : s.copy })
|
|
332
|
+
]
|
|
333
|
+
}
|
|
334
|
+
),
|
|
335
|
+
/* @__PURE__ */ jsxs(
|
|
336
|
+
"a",
|
|
337
|
+
{
|
|
338
|
+
className: "ds-btn",
|
|
339
|
+
"data-action": "x",
|
|
340
|
+
href: buildXShareUrl({ text: quote.text, author: quote.author }),
|
|
341
|
+
target: "_blank",
|
|
342
|
+
rel: "noopener noreferrer",
|
|
343
|
+
"aria-label": s.shareX,
|
|
344
|
+
children: [
|
|
345
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u{1D54F}" }),
|
|
346
|
+
/* @__PURE__ */ jsx("span", { className: "ds-share-label", children: "X" })
|
|
347
|
+
]
|
|
348
|
+
}
|
|
349
|
+
),
|
|
350
|
+
/* @__PURE__ */ jsxs(
|
|
351
|
+
"a",
|
|
352
|
+
{
|
|
353
|
+
className: "ds-btn",
|
|
354
|
+
"data-action": "line",
|
|
355
|
+
href: buildLineShareUrl({ text: quote.text, author: quote.author }),
|
|
356
|
+
target: "_blank",
|
|
357
|
+
rel: "noopener noreferrer",
|
|
358
|
+
"aria-label": s.shareLine,
|
|
359
|
+
children: [
|
|
360
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "L" }),
|
|
361
|
+
/* @__PURE__ */ jsx("span", { className: "ds-share-label", children: "LINE" })
|
|
362
|
+
]
|
|
363
|
+
}
|
|
364
|
+
)
|
|
365
|
+
] }),
|
|
366
|
+
/* @__PURE__ */ jsx("span", { className: "ds-powered", children: /* @__PURE__ */ jsx(
|
|
367
|
+
"a",
|
|
368
|
+
{
|
|
369
|
+
href: "https://personal-site-mocha-chi.vercel.app",
|
|
370
|
+
target: "_blank",
|
|
371
|
+
rel: "noopener noreferrer",
|
|
372
|
+
children: s.poweredBy
|
|
373
|
+
}
|
|
374
|
+
) })
|
|
375
|
+
] })
|
|
376
|
+
] })
|
|
377
|
+
] });
|
|
378
|
+
}
|
|
379
|
+
function attachRoot(host) {
|
|
380
|
+
if (typeof host.attachShadow === "function") {
|
|
381
|
+
if (host.shadowRoot) return host.shadowRoot;
|
|
382
|
+
return host.attachShadow({ mode: "open" });
|
|
383
|
+
}
|
|
384
|
+
console.warn("[daily-soup] attachShadow unsupported, falling back to light DOM");
|
|
385
|
+
return host;
|
|
277
386
|
}
|
|
278
387
|
function mount(host, options = {}) {
|
|
279
388
|
const lang = options.lang ?? "zh";
|
|
280
389
|
const themeConfig = options.theme ?? "auto";
|
|
281
390
|
const scheduleUrl = options.scheduleUrl === void 0 ? DEFAULT_SCHEDULE_BASE : options.scheduleUrl;
|
|
282
391
|
const maxWidth = options.maxWidth;
|
|
283
|
-
let resolvedTheme = resolveTheme(themeConfig);
|
|
284
392
|
const root = attachRoot(host);
|
|
285
393
|
if (root === host) {
|
|
286
394
|
host.textContent = "";
|
|
287
395
|
} else {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
injectStyles(root);
|
|
291
|
-
const card = buildSkeleton(resolvedTheme);
|
|
292
|
-
applyThemeOverrides(card, themeConfig, maxWidth);
|
|
293
|
-
root.appendChild(card);
|
|
294
|
-
const state = {
|
|
295
|
-
lang,
|
|
296
|
-
themeConfig,
|
|
297
|
-
scheduleUrl,
|
|
298
|
-
host,
|
|
299
|
-
root,
|
|
300
|
-
cardEl: card,
|
|
301
|
-
unwatchTheme: () => {
|
|
302
|
-
},
|
|
303
|
-
unwatchVisibility: () => {
|
|
304
|
-
},
|
|
305
|
-
schedule: null,
|
|
306
|
-
displayedDate: ""
|
|
307
|
-
};
|
|
308
|
-
if (themeConfig === "auto") {
|
|
309
|
-
state.unwatchTheme = watchSystemTheme((t2) => {
|
|
310
|
-
resolvedTheme = t2;
|
|
311
|
-
state.cardEl.classList.remove("ds-light", "ds-dark");
|
|
312
|
-
state.cardEl.classList.add(`ds-${t2}`);
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
let cancelled = false;
|
|
316
|
-
let retried = false;
|
|
317
|
-
const load = async () => {
|
|
318
|
-
const schedule = await fetchSchedule(scheduleUrl, lang);
|
|
319
|
-
if (cancelled) return;
|
|
320
|
-
if (!schedule) {
|
|
321
|
-
if (!retried) {
|
|
322
|
-
retried = true;
|
|
323
|
-
setTimeout(load, 2e3);
|
|
324
|
-
return;
|
|
325
|
-
}
|
|
326
|
-
renderError(state.cardEl, lang, resolvedTheme);
|
|
327
|
-
return;
|
|
328
|
-
}
|
|
329
|
-
state.schedule = schedule;
|
|
330
|
-
const quote = pickQuote(schedule);
|
|
331
|
-
if (!quote) {
|
|
332
|
-
renderError(state.cardEl, lang, resolvedTheme);
|
|
333
|
-
return;
|
|
334
|
-
}
|
|
335
|
-
state.displayedDate = todayUtc8();
|
|
336
|
-
renderQuote(state.cardEl, quote, lang, resolvedTheme);
|
|
337
|
-
};
|
|
338
|
-
load();
|
|
339
|
-
if (typeof document !== "undefined") {
|
|
340
|
-
const onVisibility = () => {
|
|
341
|
-
if (document.visibilityState !== "visible") return;
|
|
342
|
-
if (!state.schedule) return;
|
|
343
|
-
const today = todayUtc8();
|
|
344
|
-
if (today === state.displayedDate) return;
|
|
345
|
-
const quote = pickQuote(state.schedule);
|
|
346
|
-
if (!quote) return;
|
|
347
|
-
state.displayedDate = today;
|
|
348
|
-
renderQuote(state.cardEl, quote, lang, resolvedTheme);
|
|
349
|
-
};
|
|
350
|
-
document.addEventListener("visibilitychange", onVisibility);
|
|
351
|
-
state.unwatchVisibility = () => document.removeEventListener("visibilitychange", onVisibility);
|
|
396
|
+
const shadow = root;
|
|
397
|
+
while (shadow.firstChild) shadow.removeChild(shadow.firstChild);
|
|
352
398
|
}
|
|
399
|
+
const container = document.createElement("div");
|
|
400
|
+
root.appendChild(container);
|
|
401
|
+
const reactRoot = createRoot(container);
|
|
402
|
+
flushSync(() => {
|
|
403
|
+
reactRoot.render(
|
|
404
|
+
/* @__PURE__ */ jsx(
|
|
405
|
+
DailySoupWidget,
|
|
406
|
+
{
|
|
407
|
+
lang,
|
|
408
|
+
themeConfig,
|
|
409
|
+
scheduleUrl,
|
|
410
|
+
maxWidth
|
|
411
|
+
}
|
|
412
|
+
)
|
|
413
|
+
);
|
|
414
|
+
});
|
|
353
415
|
return {
|
|
354
416
|
destroy() {
|
|
355
|
-
|
|
356
|
-
state.unwatchTheme();
|
|
357
|
-
state.unwatchVisibility();
|
|
417
|
+
reactRoot.unmount();
|
|
358
418
|
if (root === host) {
|
|
359
419
|
host.textContent = "";
|
|
360
420
|
} else if (host.shadowRoot) {
|
|
361
|
-
while (host.shadowRoot.firstChild)
|
|
421
|
+
while (host.shadowRoot.firstChild) {
|
|
422
|
+
host.shadowRoot.removeChild(host.shadowRoot.firstChild);
|
|
423
|
+
}
|
|
362
424
|
}
|
|
363
425
|
}
|
|
364
426
|
};
|
|
@@ -378,17 +440,17 @@ function mountAll(selector = "[data-daily-soup], #daily-soup") {
|
|
|
378
440
|
}
|
|
379
441
|
|
|
380
442
|
// src/component.tsx
|
|
381
|
-
import { useEffect, useRef } from "react";
|
|
382
|
-
import { jsx } from "react/jsx-runtime";
|
|
443
|
+
import { useEffect as useEffect2, useRef } from "react";
|
|
444
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
383
445
|
function DailySoup({ lang = "zh", theme = "auto", scheduleUrl, className, maxWidth }) {
|
|
384
446
|
const hostRef = useRef(null);
|
|
385
447
|
const themeKey = typeof theme === "string" ? theme : JSON.stringify(theme);
|
|
386
|
-
|
|
448
|
+
useEffect2(() => {
|
|
387
449
|
if (!hostRef.current) return;
|
|
388
450
|
const handle = mount(hostRef.current, { lang, theme, scheduleUrl, maxWidth });
|
|
389
451
|
return () => handle.destroy();
|
|
390
452
|
}, [lang, themeKey, scheduleUrl, maxWidth]);
|
|
391
|
-
return /* @__PURE__ */
|
|
453
|
+
return /* @__PURE__ */ jsx2("div", { ref: hostRef, className, "data-daily-soup-host": "" });
|
|
392
454
|
}
|
|
393
455
|
export {
|
|
394
456
|
DailySoup,
|