changelogdev-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 +21 -0
- package/README.md +173 -0
- package/dist/index.cjs +417 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +65 -0
- package/dist/index.d.ts +65 -0
- package/dist/index.iife.js +417 -0
- package/dist/index.iife.js.map +1 -0
- package/dist/index.js +417 -0
- package/dist/index.js.map +1 -0
- package/package.json +47 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/api.ts","../src/styles.ts","../src/types.ts","../src/widget.ts"],"sourcesContent":["import { ChangelogWidget } from './widget'\nimport type { WidgetConfig } from './types'\n\nexport type { WidgetConfig, ChangelogEntry } from './types'\nexport { ChangelogWidget }\n\n/**\n * Register the <changelog-widget> custom element if not already registered.\n */\nfunction register(): void {\n if (\n typeof window !== 'undefined' &&\n !customElements.get('changelog-widget')\n ) {\n customElements.define('changelog-widget', ChangelogWidget)\n }\n}\n\n/**\n * Programmatic initialization. Creates and appends a <changelog-widget>\n * element to the document body.\n *\n * @example\n * ```ts\n * import { init } from '@changelogdev/widget'\n *\n * init({\n * projectId: 'my-project',\n * position: 'bottom-right',\n * theme: 'dark',\n * })\n * ```\n */\nexport function init(config: WidgetConfig): ChangelogWidget {\n register()\n\n const widget = document.createElement(\n 'changelog-widget',\n ) as ChangelogWidget\n\n widget.setAttribute('project-id', config.projectId)\n\n if (config.apiUrl) {\n widget.setAttribute('api-url', config.apiUrl)\n }\n\n if (config.position) {\n widget.setAttribute('position', config.position)\n }\n\n if (config.theme) {\n widget.setAttribute('theme', config.theme)\n }\n\n if (config.accentColor) {\n widget.setAttribute('accent-color', config.accentColor)\n }\n\n document.body.appendChild(widget)\n\n return widget\n}\n\n// Auto-register when loaded via script tag\nregister()\n","import type { ChangelogEntry } from './types'\n\nconst MOCK_ENTRIES: ChangelogEntry[] = [\n {\n id: 'mock-1',\n title: 'AI-Powered Release Summaries',\n description:\n 'Changelog entries are now automatically summarized using AI. Connect your GitHub repo and we generate human-readable release notes from your commits and PRs.',\n type: 'feature',\n date: '2026-03-05',\n url: 'https://www.changelogdev.com/blog/ai-summaries',\n },\n {\n id: 'mock-2',\n title: 'Embeddable Widget',\n description:\n 'You can now embed a changelog widget directly on your site. One script tag, zero dependencies. Your users see what\\'s new without leaving your app.',\n type: 'feature',\n date: '2026-03-01',\n url: 'https://www.changelogdev.com/blog/widget-launch',\n },\n {\n id: 'mock-3',\n title: 'Faster Dashboard Loading',\n description:\n 'Dashboard load times reduced by 60% through query optimization and edge caching. The changelog list now renders in under 200ms.',\n type: 'improvement',\n date: '2026-02-25',\n },\n {\n id: 'mock-4',\n title: 'Markdown Rendering Fix',\n description:\n 'Fixed an issue where inline code blocks in changelog descriptions were not rendering correctly in the email digest.',\n type: 'fix',\n date: '2026-02-20',\n },\n {\n id: 'mock-5',\n title: 'Webhook Notifications',\n description:\n 'Send changelog updates to Slack, Discord, or any webhook endpoint. Configure per-project notification rules from the dashboard.',\n type: 'feature',\n date: '2026-02-15',\n url: 'https://www.changelogdev.com/blog/webhooks',\n },\n {\n id: 'mock-6',\n title: 'Public Changelog Pages',\n description:\n 'Every project now gets a public changelog page at changelog.dev/your-project. Share it with your users or link to it from your docs.',\n type: 'announcement',\n date: '2026-02-10',\n url: 'https://www.changelogdev.com/blog/public-pages',\n },\n]\n\nexport async function fetchEntries(\n apiUrl: string,\n projectId: string,\n): Promise<ChangelogEntry[]> {\n try {\n const url = `${apiUrl}?projectId=${encodeURIComponent(projectId)}`\n const response = await fetch(url, {\n headers: { Accept: 'application/json' },\n signal: AbortSignal.timeout(5000),\n })\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`)\n }\n\n const data = await response.json()\n\n if (Array.isArray(data)) return data as ChangelogEntry[]\n if (data?.entries && Array.isArray(data.entries))\n return data.entries as ChangelogEntry[]\n\n throw new Error('Unexpected response shape')\n } catch {\n // Fall back to mock data for demo / offline usage\n return MOCK_ENTRIES\n }\n}\n","export function getStyles(accentColor: string): string {\n return `\n :host {\n --clw-accent: ${accentColor};\n --clw-accent-hover: color-mix(in srgb, ${accentColor} 85%, white);\n --clw-radius: 12px;\n --clw-radius-sm: 8px;\n --clw-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;\n --clw-transition: 0.2s ease;\n\n position: fixed;\n z-index: 2147483647;\n font-family: var(--clw-font);\n font-size: 14px;\n line-height: 1.5;\n }\n\n :host([position=\"bottom-right\"]) {\n bottom: 20px;\n right: 20px;\n }\n\n :host([position=\"bottom-left\"]) {\n bottom: 20px;\n left: 20px;\n }\n\n /* ---- Theme variables ---- */\n :host([data-theme=\"dark\"]) {\n --clw-bg: #111113;\n --clw-bg-elevated: #1a1a1f;\n --clw-bg-hover: #222228;\n --clw-border: #2a2a32;\n --clw-text: #ededef;\n --clw-text-secondary: #9393a0;\n --clw-text-muted: #65656d;\n --clw-shadow: 0 16px 48px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.06);\n --clw-btn-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);\n }\n\n :host([data-theme=\"light\"]) {\n --clw-bg: #ffffff;\n --clw-bg-elevated: #f8f8fa;\n --clw-bg-hover: #f0f0f3;\n --clw-border: #e4e4e8;\n --clw-text: #111113;\n --clw-text-secondary: #65656d;\n --clw-text-muted: #9393a0;\n --clw-shadow: 0 16px 48px rgba(0, 0, 0, 0.12), 0 0 0 1px rgba(0, 0, 0, 0.06);\n --clw-btn-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);\n }\n\n *, *::before, *::after {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n }\n\n /* ---- Trigger button ---- */\n .clw-trigger {\n width: 48px;\n height: 48px;\n border-radius: 50%;\n border: none;\n background: var(--clw-accent);\n color: #fff;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow: var(--clw-btn-shadow);\n transition: transform var(--clw-transition), box-shadow var(--clw-transition);\n position: relative;\n }\n\n .clw-trigger:hover {\n transform: scale(1.08);\n box-shadow: 0 6px 24px rgba(0, 0, 0, 0.3);\n }\n\n .clw-trigger:active {\n transform: scale(0.96);\n }\n\n .clw-trigger svg {\n width: 22px;\n height: 22px;\n fill: currentColor;\n }\n\n /* ---- Badge ---- */\n .clw-badge {\n position: absolute;\n top: -4px;\n right: -4px;\n min-width: 20px;\n height: 20px;\n padding: 0 6px;\n border-radius: 10px;\n background: #ef4444;\n color: #fff;\n font-size: 11px;\n font-weight: 700;\n display: flex;\n align-items: center;\n justify-content: center;\n pointer-events: none;\n line-height: 1;\n }\n\n .clw-badge:empty,\n .clw-badge[data-count=\"0\"] {\n display: none;\n }\n\n /* ---- Panel ---- */\n .clw-panel {\n position: fixed;\n bottom: 80px;\n width: 380px;\n max-height: min(520px, calc(100vh - 120px));\n background: var(--clw-bg);\n border-radius: var(--clw-radius);\n box-shadow: var(--clw-shadow);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n opacity: 0;\n transform: translateY(12px) scale(0.96);\n pointer-events: none;\n transition: opacity var(--clw-transition), transform var(--clw-transition);\n }\n\n :host([position=\"bottom-right\"]) .clw-panel {\n right: 0;\n }\n\n :host([position=\"bottom-left\"]) .clw-panel {\n left: 0;\n }\n\n .clw-panel.open {\n opacity: 1;\n transform: translateY(0) scale(1);\n pointer-events: auto;\n }\n\n /* ---- Panel header ---- */\n .clw-header {\n padding: 16px 20px;\n border-bottom: 1px solid var(--clw-border);\n display: flex;\n align-items: center;\n justify-content: space-between;\n flex-shrink: 0;\n }\n\n .clw-header-title {\n font-size: 15px;\n font-weight: 600;\n color: var(--clw-text);\n letter-spacing: -0.01em;\n }\n\n .clw-close {\n width: 28px;\n height: 28px;\n border-radius: 6px;\n border: none;\n background: transparent;\n color: var(--clw-text-muted);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background var(--clw-transition), color var(--clw-transition);\n }\n\n .clw-close:hover {\n background: var(--clw-bg-hover);\n color: var(--clw-text);\n }\n\n .clw-close svg {\n width: 16px;\n height: 16px;\n stroke: currentColor;\n fill: none;\n stroke-width: 2;\n }\n\n /* ---- Entries list ---- */\n .clw-entries {\n overflow-y: auto;\n flex: 1;\n padding: 8px 0;\n overscroll-behavior: contain;\n }\n\n .clw-entries::-webkit-scrollbar {\n width: 6px;\n }\n\n .clw-entries::-webkit-scrollbar-track {\n background: transparent;\n }\n\n .clw-entries::-webkit-scrollbar-thumb {\n background: var(--clw-border);\n border-radius: 3px;\n }\n\n /* ---- Single entry ---- */\n .clw-entry {\n padding: 14px 20px;\n border-bottom: 1px solid var(--clw-border);\n transition: background var(--clw-transition);\n }\n\n .clw-entry:last-child {\n border-bottom: none;\n }\n\n .clw-entry:hover {\n background: var(--clw-bg-hover);\n }\n\n .clw-entry-meta {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-bottom: 6px;\n }\n\n .clw-type-badge {\n font-size: 11px;\n font-weight: 600;\n padding: 2px 8px;\n border-radius: 4px;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n line-height: 1.4;\n }\n\n .clw-type-feature {\n background: rgba(59, 130, 246, 0.15);\n color: #60a5fa;\n }\n\n .clw-type-improvement {\n background: rgba(168, 85, 247, 0.15);\n color: #c084fc;\n }\n\n .clw-type-fix {\n background: rgba(34, 197, 94, 0.15);\n color: #4ade80;\n }\n\n .clw-type-announcement {\n background: rgba(251, 191, 36, 0.15);\n color: #fbbf24;\n }\n\n /* Light theme badge overrides */\n :host([data-theme=\"light\"]) .clw-type-feature {\n background: rgba(59, 130, 246, 0.1);\n color: #2563eb;\n }\n\n :host([data-theme=\"light\"]) .clw-type-improvement {\n background: rgba(168, 85, 247, 0.1);\n color: #7c3aed;\n }\n\n :host([data-theme=\"light\"]) .clw-type-fix {\n background: rgba(34, 197, 94, 0.1);\n color: #16a34a;\n }\n\n :host([data-theme=\"light\"]) .clw-type-announcement {\n background: rgba(251, 191, 36, 0.1);\n color: #d97706;\n }\n\n .clw-entry-date {\n font-size: 12px;\n color: var(--clw-text-muted);\n }\n\n .clw-entry-title {\n font-size: 14px;\n font-weight: 600;\n color: var(--clw-text);\n margin-bottom: 4px;\n line-height: 1.4;\n }\n\n .clw-entry-title a {\n color: inherit;\n text-decoration: none;\n transition: color var(--clw-transition);\n }\n\n .clw-entry-title a:hover {\n color: var(--clw-accent);\n }\n\n .clw-entry-desc {\n font-size: 13px;\n color: var(--clw-text-secondary);\n line-height: 1.55;\n display: -webkit-box;\n -webkit-line-clamp: 3;\n -webkit-box-orient: vertical;\n overflow: hidden;\n }\n\n /* ---- Footer ---- */\n .clw-footer {\n padding: 10px 20px;\n border-top: 1px solid var(--clw-border);\n text-align: center;\n flex-shrink: 0;\n }\n\n .clw-footer a {\n font-size: 11px;\n color: var(--clw-text-muted);\n text-decoration: none;\n transition: color var(--clw-transition);\n }\n\n .clw-footer a:hover {\n color: var(--clw-text-secondary);\n }\n\n /* ---- Loading / Empty ---- */\n .clw-loading,\n .clw-empty {\n padding: 40px 20px;\n text-align: center;\n color: var(--clw-text-muted);\n font-size: 13px;\n }\n\n .clw-spinner {\n width: 24px;\n height: 24px;\n border: 2px solid var(--clw-border);\n border-top-color: var(--clw-accent);\n border-radius: 50%;\n animation: clw-spin 0.7s linear infinite;\n margin: 0 auto 12px;\n }\n\n @keyframes clw-spin {\n to { transform: rotate(360deg); }\n }\n\n /* ---- Mobile ---- */\n @media (max-width: 440px) {\n .clw-panel {\n width: calc(100vw - 24px);\n bottom: 76px;\n max-height: calc(100vh - 100px);\n }\n\n :host([position=\"bottom-right\"]) .clw-panel {\n right: -8px;\n }\n\n :host([position=\"bottom-left\"]) .clw-panel {\n left: -8px;\n }\n }\n `\n}\n","export interface ChangelogEntry {\n id: string\n title: string\n description: string\n type: 'feature' | 'improvement' | 'fix' | 'announcement'\n date: string\n url?: string\n}\n\nexport interface WidgetConfig {\n projectId: string\n apiUrl?: string\n position?: 'bottom-right' | 'bottom-left'\n theme?: 'light' | 'dark' | 'auto'\n accentColor?: string\n}\n\nexport interface WidgetAttributes {\n 'project-id': string\n 'api-url': string\n position: 'bottom-right' | 'bottom-left'\n theme: 'light' | 'dark' | 'auto'\n 'accent-color': string\n}\n\nexport type TypeBadgeColor = {\n bg: string\n text: string\n}\n\nexport const TYPE_LABELS: Record<ChangelogEntry['type'], string> = {\n feature: 'New',\n improvement: 'Improved',\n fix: 'Fixed',\n announcement: 'Announcement',\n}\n","import { fetchEntries } from './api'\nimport { getStyles } from './styles'\nimport { TYPE_LABELS } from './types'\nimport type { ChangelogEntry } from './types'\n\nconst STORAGE_KEY_PREFIX = 'clw_last_seen_'\n\nconst BELL_ICON = `<svg viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M12 2C10.343 2 9 3.343 9 5v.26C6.667 6.34 5 8.66 5 11.4V16l-2 2v1h18v-1l-2-2v-4.6c0-2.74-1.667-5.06-4-6.14V5c0-1.657-1.343-3-3-3zm0 20c1.105 0 2-.895 2-2h-4c0 1.105.895 2 2 2z\"/></svg>`\n\nconst CLOSE_ICON = `<svg viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\"><line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/></svg>`\n\nexport class ChangelogWidget extends HTMLElement {\n static get observedAttributes(): string[] {\n return ['project-id', 'api-url', 'position', 'theme', 'accent-color']\n }\n\n private shadow: ShadowRoot\n private entries: ChangelogEntry[] = []\n private isOpen = false\n private isLoading = false\n\n constructor() {\n super()\n this.shadow = this.attachShadow({ mode: 'open' })\n }\n\n // -- Attribute accessors --\n\n get projectId(): string {\n return this.getAttribute('project-id') || ''\n }\n\n get apiUrl(): string {\n return (\n this.getAttribute('api-url') || 'https://www.changelogdev.com/api/widget'\n )\n }\n\n get position(): 'bottom-right' | 'bottom-left' {\n const pos = this.getAttribute('position')\n return pos === 'bottom-left' ? 'bottom-left' : 'bottom-right'\n }\n\n get theme(): 'light' | 'dark' | 'auto' {\n const t = this.getAttribute('theme')\n if (t === 'light' || t === 'dark') return t\n return 'auto'\n }\n\n get accentColor(): string {\n return this.getAttribute('accent-color') || '#6366f1'\n }\n\n // -- Lifecycle --\n\n connectedCallback(): void {\n // Ensure position attribute exists for CSS selectors\n if (!this.hasAttribute('position')) {\n this.setAttribute('position', 'bottom-right')\n }\n\n this.resolveTheme()\n this.render()\n this.loadEntries()\n\n // Listen for system theme changes when in auto mode\n if (this.theme === 'auto') {\n window\n .matchMedia('(prefers-color-scheme: dark)')\n .addEventListener('change', () => this.resolveTheme())\n }\n }\n\n attributeChangedCallback(\n name: string,\n oldValue: string | null,\n newValue: string | null,\n ): void {\n if (oldValue === newValue) return\n\n if (name === 'theme') {\n this.resolveTheme()\n }\n\n if (name === 'accent-color') {\n this.render()\n }\n\n if (name === 'project-id' || name === 'api-url') {\n this.loadEntries()\n }\n\n if (name === 'position') {\n // Position is handled via CSS attribute selectors, no re-render needed\n }\n }\n\n // -- Theme resolution --\n\n private resolveTheme(): void {\n let resolved: 'light' | 'dark' = 'dark'\n\n if (this.theme === 'auto') {\n resolved = window.matchMedia('(prefers-color-scheme: dark)').matches\n ? 'dark'\n : 'light'\n } else {\n resolved = this.theme\n }\n\n this.setAttribute('data-theme', resolved)\n }\n\n // -- Data --\n\n private async loadEntries(): Promise<void> {\n if (!this.projectId) return\n\n this.isLoading = true\n this.updatePanel()\n\n this.entries = await fetchEntries(this.apiUrl, this.projectId)\n this.isLoading = false\n this.updatePanel()\n this.updateBadge()\n }\n\n // -- Storage (unread tracking) --\n\n private get storageKey(): string {\n return `${STORAGE_KEY_PREFIX}${this.projectId}`\n }\n\n private getLastSeenDate(): string | null {\n try {\n return localStorage.getItem(this.storageKey)\n } catch {\n return null\n }\n }\n\n private markAsSeen(): void {\n if (this.entries.length === 0) return\n try {\n localStorage.setItem(this.storageKey, this.entries[0].date)\n } catch {\n // localStorage not available\n }\n }\n\n private getUnreadCount(): number {\n const lastSeen = this.getLastSeenDate()\n if (!lastSeen) return this.entries.length\n\n return this.entries.filter((e) => e.date > lastSeen).length\n }\n\n // -- Render --\n\n private render(): void {\n const style = document.createElement('style')\n style.textContent = getStyles(this.accentColor)\n\n const container = document.createElement('div')\n container.innerHTML = `\n <button class=\"clw-trigger\" aria-label=\"View changelog\">\n ${BELL_ICON}\n <span class=\"clw-badge\" data-count=\"0\"></span>\n </button>\n <div class=\"clw-panel\" role=\"dialog\" aria-label=\"Changelog\">\n <div class=\"clw-header\">\n <span class=\"clw-header-title\">What's New</span>\n <button class=\"clw-close\" aria-label=\"Close changelog\">\n ${CLOSE_ICON}\n </button>\n </div>\n <div class=\"clw-entries\">\n <div class=\"clw-loading\">\n <div class=\"clw-spinner\"></div>\n Loading...\n </div>\n </div>\n <div class=\"clw-footer\">\n <a href=\"https://www.changelogdev.com?ref=widget\" target=\"_blank\" rel=\"noopener noreferrer\">\n Powered by Changelog.dev\n </a>\n </div>\n </div>\n `\n\n this.shadow.innerHTML = ''\n this.shadow.appendChild(style)\n this.shadow.appendChild(container)\n\n // Bind events\n const trigger = this.shadow.querySelector('.clw-trigger') as HTMLButtonElement\n trigger.addEventListener('click', () => this.toggle())\n\n const close = this.shadow.querySelector('.clw-close') as HTMLButtonElement\n close.addEventListener('click', () => this.close())\n\n // Close on outside click\n document.addEventListener('click', (e: MouseEvent) => {\n if (this.isOpen && !this.contains(e.target as Node)) {\n this.close()\n }\n })\n\n // Close on Escape\n document.addEventListener('keydown', (e: KeyboardEvent) => {\n if (e.key === 'Escape' && this.isOpen) {\n this.close()\n }\n })\n }\n\n private toggle(): void {\n this.isOpen ? this.close() : this.open()\n }\n\n private open(): void {\n this.isOpen = true\n const panel = this.shadow.querySelector('.clw-panel')\n panel?.classList.add('open')\n\n // Mark entries as seen\n this.markAsSeen()\n this.updateBadge()\n }\n\n private close(): void {\n this.isOpen = false\n const panel = this.shadow.querySelector('.clw-panel')\n panel?.classList.remove('open')\n }\n\n private updateBadge(): void {\n const badge = this.shadow.querySelector('.clw-badge') as HTMLElement | null\n if (!badge) return\n\n const count = this.getUnreadCount()\n badge.textContent = count > 0 ? (count > 9 ? '9+' : String(count)) : ''\n badge.setAttribute('data-count', String(count))\n }\n\n private updatePanel(): void {\n const entriesContainer = this.shadow.querySelector('.clw-entries')\n if (!entriesContainer) return\n\n if (this.isLoading) {\n entriesContainer.innerHTML = `\n <div class=\"clw-loading\">\n <div class=\"clw-spinner\"></div>\n Loading...\n </div>\n `\n return\n }\n\n if (this.entries.length === 0) {\n entriesContainer.innerHTML = `\n <div class=\"clw-empty\">No changelog entries yet.</div>\n `\n return\n }\n\n entriesContainer.innerHTML = this.entries\n .map((entry) => this.renderEntry(entry))\n .join('')\n }\n\n private renderEntry(entry: ChangelogEntry): string {\n const label = TYPE_LABELS[entry.type] || entry.type\n const formattedDate = this.formatDate(entry.date)\n\n const titleHtml = entry.url\n ? `<a href=\"${this.escapeHtml(entry.url)}\" target=\"_blank\" rel=\"noopener noreferrer\">${this.escapeHtml(entry.title)}</a>`\n : this.escapeHtml(entry.title)\n\n return `\n <div class=\"clw-entry\">\n <div class=\"clw-entry-meta\">\n <span class=\"clw-type-badge clw-type-${entry.type}\">${label}</span>\n <span class=\"clw-entry-date\">${formattedDate}</span>\n </div>\n <div class=\"clw-entry-title\">${titleHtml}</div>\n <div class=\"clw-entry-desc\">${this.escapeHtml(entry.description)}</div>\n </div>\n `\n }\n\n private formatDate(dateStr: string): string {\n try {\n const date = new Date(dateStr + 'T00:00:00')\n const now = new Date()\n const diffMs = now.getTime() - date.getTime()\n const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24))\n\n if (diffDays === 0) return 'Today'\n if (diffDays === 1) return 'Yesterday'\n if (diffDays < 7) return `${diffDays} days ago`\n if (diffDays < 30) {\n const weeks = Math.floor(diffDays / 7)\n return `${weeks} ${weeks === 1 ? 'week' : 'weeks'} ago`\n }\n\n return date.toLocaleDateString('en-US', {\n month: 'short',\n day: 'numeric',\n year: date.getFullYear() !== now.getFullYear() ? 'numeric' : undefined,\n })\n } catch {\n return dateStr\n }\n }\n\n private escapeHtml(str: string): string {\n const div = document.createElement('div')\n div.textContent = str\n return div.innerHTML\n }\n}\n"],"mappings":"yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,qBAAAE,EAAA,SAAAC,IAAA,eAAAC,EAAAJ,GCEA,IAAMK,EAAiC,CACrC,CACE,GAAI,SACJ,MAAO,+BACP,YACE,gKACF,KAAM,UACN,KAAM,aACN,IAAK,gDACP,EACA,CACE,GAAI,SACJ,MAAO,oBACP,YACE,qJACF,KAAM,UACN,KAAM,aACN,IAAK,iDACP,EACA,CACE,GAAI,SACJ,MAAO,2BACP,YACE,kIACF,KAAM,cACN,KAAM,YACR,EACA,CACE,GAAI,SACJ,MAAO,yBACP,YACE,sHACF,KAAM,MACN,KAAM,YACR,EACA,CACE,GAAI,SACJ,MAAO,wBACP,YACE,kIACF,KAAM,UACN,KAAM,aACN,IAAK,4CACP,EACA,CACE,GAAI,SACJ,MAAO,yBACP,YACE,uIACF,KAAM,eACN,KAAM,aACN,IAAK,gDACP,CACF,EAEA,eAAsBC,EACpBC,EACAC,EAC2B,CAC3B,GAAI,CACF,IAAMC,EAAM,GAAGF,CAAM,cAAc,mBAAmBC,CAAS,CAAC,GAC1DE,EAAW,MAAM,MAAMD,EAAK,CAChC,QAAS,CAAE,OAAQ,kBAAmB,EACtC,OAAQ,YAAY,QAAQ,GAAI,CAClC,CAAC,EAED,GAAI,CAACC,EAAS,GACZ,MAAM,IAAI,MAAM,QAAQA,EAAS,MAAM,EAAE,EAG3C,IAAMC,EAAO,MAAMD,EAAS,KAAK,EAEjC,GAAI,MAAM,QAAQC,CAAI,EAAG,OAAOA,EAChC,GAAIA,GAAM,SAAW,MAAM,QAAQA,EAAK,OAAO,EAC7C,OAAOA,EAAK,QAEd,MAAM,IAAI,MAAM,2BAA2B,CAC7C,MAAQ,CAEN,OAAON,CACT,CACF,CCnFO,SAASO,EAAUC,EAA6B,CACrD,MAAO;AAAA;AAAA,sBAEaA,CAAW;AAAA,+CACcA,CAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;A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","names":["index_exports","__export","ChangelogWidget","init","__toCommonJS","MOCK_ENTRIES","fetchEntries","apiUrl","projectId","url","response","data","getStyles","accentColor","TYPE_LABELS","STORAGE_KEY_PREFIX","BELL_ICON","CLOSE_ICON","ChangelogWidget","t","name","oldValue","newValue","resolved","fetchEntries","lastSeen","e","style","getStyles","container","badge","count","entriesContainer","entry","label","TYPE_LABELS","formattedDate","titleHtml","dateStr","date","now","diffMs","diffDays","weeks","str","div","register","ChangelogWidget","init","config","widget"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
declare class ChangelogWidget extends HTMLElement {
|
|
2
|
+
static get observedAttributes(): string[];
|
|
3
|
+
private shadow;
|
|
4
|
+
private entries;
|
|
5
|
+
private isOpen;
|
|
6
|
+
private isLoading;
|
|
7
|
+
constructor();
|
|
8
|
+
get projectId(): string;
|
|
9
|
+
get apiUrl(): string;
|
|
10
|
+
get position(): 'bottom-right' | 'bottom-left';
|
|
11
|
+
get theme(): 'light' | 'dark' | 'auto';
|
|
12
|
+
get accentColor(): string;
|
|
13
|
+
connectedCallback(): void;
|
|
14
|
+
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
|
|
15
|
+
private resolveTheme;
|
|
16
|
+
private loadEntries;
|
|
17
|
+
private get storageKey();
|
|
18
|
+
private getLastSeenDate;
|
|
19
|
+
private markAsSeen;
|
|
20
|
+
private getUnreadCount;
|
|
21
|
+
private render;
|
|
22
|
+
private toggle;
|
|
23
|
+
private open;
|
|
24
|
+
private close;
|
|
25
|
+
private updateBadge;
|
|
26
|
+
private updatePanel;
|
|
27
|
+
private renderEntry;
|
|
28
|
+
private formatDate;
|
|
29
|
+
private escapeHtml;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface ChangelogEntry {
|
|
33
|
+
id: string;
|
|
34
|
+
title: string;
|
|
35
|
+
description: string;
|
|
36
|
+
type: 'feature' | 'improvement' | 'fix' | 'announcement';
|
|
37
|
+
date: string;
|
|
38
|
+
url?: string;
|
|
39
|
+
}
|
|
40
|
+
interface WidgetConfig {
|
|
41
|
+
projectId: string;
|
|
42
|
+
apiUrl?: string;
|
|
43
|
+
position?: 'bottom-right' | 'bottom-left';
|
|
44
|
+
theme?: 'light' | 'dark' | 'auto';
|
|
45
|
+
accentColor?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Programmatic initialization. Creates and appends a <changelog-widget>
|
|
50
|
+
* element to the document body.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```ts
|
|
54
|
+
* import { init } from '@changelogdev/widget'
|
|
55
|
+
*
|
|
56
|
+
* init({
|
|
57
|
+
* projectId: 'my-project',
|
|
58
|
+
* position: 'bottom-right',
|
|
59
|
+
* theme: 'dark',
|
|
60
|
+
* })
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
declare function init(config: WidgetConfig): ChangelogWidget;
|
|
64
|
+
|
|
65
|
+
export { type ChangelogEntry, ChangelogWidget, type WidgetConfig, init };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
declare class ChangelogWidget extends HTMLElement {
|
|
2
|
+
static get observedAttributes(): string[];
|
|
3
|
+
private shadow;
|
|
4
|
+
private entries;
|
|
5
|
+
private isOpen;
|
|
6
|
+
private isLoading;
|
|
7
|
+
constructor();
|
|
8
|
+
get projectId(): string;
|
|
9
|
+
get apiUrl(): string;
|
|
10
|
+
get position(): 'bottom-right' | 'bottom-left';
|
|
11
|
+
get theme(): 'light' | 'dark' | 'auto';
|
|
12
|
+
get accentColor(): string;
|
|
13
|
+
connectedCallback(): void;
|
|
14
|
+
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
|
|
15
|
+
private resolveTheme;
|
|
16
|
+
private loadEntries;
|
|
17
|
+
private get storageKey();
|
|
18
|
+
private getLastSeenDate;
|
|
19
|
+
private markAsSeen;
|
|
20
|
+
private getUnreadCount;
|
|
21
|
+
private render;
|
|
22
|
+
private toggle;
|
|
23
|
+
private open;
|
|
24
|
+
private close;
|
|
25
|
+
private updateBadge;
|
|
26
|
+
private updatePanel;
|
|
27
|
+
private renderEntry;
|
|
28
|
+
private formatDate;
|
|
29
|
+
private escapeHtml;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface ChangelogEntry {
|
|
33
|
+
id: string;
|
|
34
|
+
title: string;
|
|
35
|
+
description: string;
|
|
36
|
+
type: 'feature' | 'improvement' | 'fix' | 'announcement';
|
|
37
|
+
date: string;
|
|
38
|
+
url?: string;
|
|
39
|
+
}
|
|
40
|
+
interface WidgetConfig {
|
|
41
|
+
projectId: string;
|
|
42
|
+
apiUrl?: string;
|
|
43
|
+
position?: 'bottom-right' | 'bottom-left';
|
|
44
|
+
theme?: 'light' | 'dark' | 'auto';
|
|
45
|
+
accentColor?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Programmatic initialization. Creates and appends a <changelog-widget>
|
|
50
|
+
* element to the document body.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```ts
|
|
54
|
+
* import { init } from '@changelogdev/widget'
|
|
55
|
+
*
|
|
56
|
+
* init({
|
|
57
|
+
* projectId: 'my-project',
|
|
58
|
+
* position: 'bottom-right',
|
|
59
|
+
* theme: 'dark',
|
|
60
|
+
* })
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
declare function init(config: WidgetConfig): ChangelogWidget;
|
|
64
|
+
|
|
65
|
+
export { type ChangelogEntry, ChangelogWidget, type WidgetConfig, init };
|
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
"use strict";var ChangelogWidget=(()=>{var l=Object.defineProperty;var w=Object.getOwnPropertyDescriptor;var u=Object.getOwnPropertyNames;var m=Object.prototype.hasOwnProperty;var b=(r,o)=>{for(var e in o)l(r,e,{get:o[e],enumerable:!0})},f=(r,o,e,t)=>{if(o&&typeof o=="object"||typeof o=="function")for(let i of u(o))!m.call(r,i)&&i!==e&&l(r,i,{get:()=>o[i],enumerable:!(t=w(o,i))||t.enumerable});return r};var v=r=>f(l({},"__esModule",{value:!0}),r);var L={};b(L,{ChangelogWidget:()=>a,init:()=>C});var x=[{id:"mock-1",title:"AI-Powered Release Summaries",description:"Changelog entries are now automatically summarized using AI. Connect your GitHub repo and we generate human-readable release notes from your commits and PRs.",type:"feature",date:"2026-03-05",url:"https://www.changelogdev.com/blog/ai-summaries"},{id:"mock-2",title:"Embeddable Widget",description:"You can now embed a changelog widget directly on your site. One script tag, zero dependencies. Your users see what's new without leaving your app.",type:"feature",date:"2026-03-01",url:"https://www.changelogdev.com/blog/widget-launch"},{id:"mock-3",title:"Faster Dashboard Loading",description:"Dashboard load times reduced by 60% through query optimization and edge caching. The changelog list now renders in under 200ms.",type:"improvement",date:"2026-02-25"},{id:"mock-4",title:"Markdown Rendering Fix",description:"Fixed an issue where inline code blocks in changelog descriptions were not rendering correctly in the email digest.",type:"fix",date:"2026-02-20"},{id:"mock-5",title:"Webhook Notifications",description:"Send changelog updates to Slack, Discord, or any webhook endpoint. Configure per-project notification rules from the dashboard.",type:"feature",date:"2026-02-15",url:"https://www.changelogdev.com/blog/webhooks"},{id:"mock-6",title:"Public Changelog Pages",description:"Every project now gets a public changelog page at changelog.dev/your-project. Share it with your users or link to it from your docs.",type:"announcement",date:"2026-02-10",url:"https://www.changelogdev.com/blog/public-pages"}];async function d(r,o){try{let e=`${r}?projectId=${encodeURIComponent(o)}`,t=await fetch(e,{headers:{Accept:"application/json"},signal:AbortSignal.timeout(5e3)});if(!t.ok)throw new Error(`HTTP ${t.status}`);let i=await t.json();if(Array.isArray(i))return i;if(i?.entries&&Array.isArray(i.entries))return i.entries;throw new Error("Unexpected response shape")}catch{return x}}function g(r){return`
|
|
2
|
+
:host {
|
|
3
|
+
--clw-accent: ${r};
|
|
4
|
+
--clw-accent-hover: color-mix(in srgb, ${r} 85%, white);
|
|
5
|
+
--clw-radius: 12px;
|
|
6
|
+
--clw-radius-sm: 8px;
|
|
7
|
+
--clw-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
8
|
+
--clw-transition: 0.2s ease;
|
|
9
|
+
|
|
10
|
+
position: fixed;
|
|
11
|
+
z-index: 2147483647;
|
|
12
|
+
font-family: var(--clw-font);
|
|
13
|
+
font-size: 14px;
|
|
14
|
+
line-height: 1.5;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
:host([position="bottom-right"]) {
|
|
18
|
+
bottom: 20px;
|
|
19
|
+
right: 20px;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
:host([position="bottom-left"]) {
|
|
23
|
+
bottom: 20px;
|
|
24
|
+
left: 20px;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* ---- Theme variables ---- */
|
|
28
|
+
:host([data-theme="dark"]) {
|
|
29
|
+
--clw-bg: #111113;
|
|
30
|
+
--clw-bg-elevated: #1a1a1f;
|
|
31
|
+
--clw-bg-hover: #222228;
|
|
32
|
+
--clw-border: #2a2a32;
|
|
33
|
+
--clw-text: #ededef;
|
|
34
|
+
--clw-text-secondary: #9393a0;
|
|
35
|
+
--clw-text-muted: #65656d;
|
|
36
|
+
--clw-shadow: 0 16px 48px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.06);
|
|
37
|
+
--clw-btn-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
:host([data-theme="light"]) {
|
|
41
|
+
--clw-bg: #ffffff;
|
|
42
|
+
--clw-bg-elevated: #f8f8fa;
|
|
43
|
+
--clw-bg-hover: #f0f0f3;
|
|
44
|
+
--clw-border: #e4e4e8;
|
|
45
|
+
--clw-text: #111113;
|
|
46
|
+
--clw-text-secondary: #65656d;
|
|
47
|
+
--clw-text-muted: #9393a0;
|
|
48
|
+
--clw-shadow: 0 16px 48px rgba(0, 0, 0, 0.12), 0 0 0 1px rgba(0, 0, 0, 0.06);
|
|
49
|
+
--clw-btn-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
*, *::before, *::after {
|
|
53
|
+
box-sizing: border-box;
|
|
54
|
+
margin: 0;
|
|
55
|
+
padding: 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* ---- Trigger button ---- */
|
|
59
|
+
.clw-trigger {
|
|
60
|
+
width: 48px;
|
|
61
|
+
height: 48px;
|
|
62
|
+
border-radius: 50%;
|
|
63
|
+
border: none;
|
|
64
|
+
background: var(--clw-accent);
|
|
65
|
+
color: #fff;
|
|
66
|
+
cursor: pointer;
|
|
67
|
+
display: flex;
|
|
68
|
+
align-items: center;
|
|
69
|
+
justify-content: center;
|
|
70
|
+
box-shadow: var(--clw-btn-shadow);
|
|
71
|
+
transition: transform var(--clw-transition), box-shadow var(--clw-transition);
|
|
72
|
+
position: relative;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.clw-trigger:hover {
|
|
76
|
+
transform: scale(1.08);
|
|
77
|
+
box-shadow: 0 6px 24px rgba(0, 0, 0, 0.3);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.clw-trigger:active {
|
|
81
|
+
transform: scale(0.96);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.clw-trigger svg {
|
|
85
|
+
width: 22px;
|
|
86
|
+
height: 22px;
|
|
87
|
+
fill: currentColor;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* ---- Badge ---- */
|
|
91
|
+
.clw-badge {
|
|
92
|
+
position: absolute;
|
|
93
|
+
top: -4px;
|
|
94
|
+
right: -4px;
|
|
95
|
+
min-width: 20px;
|
|
96
|
+
height: 20px;
|
|
97
|
+
padding: 0 6px;
|
|
98
|
+
border-radius: 10px;
|
|
99
|
+
background: #ef4444;
|
|
100
|
+
color: #fff;
|
|
101
|
+
font-size: 11px;
|
|
102
|
+
font-weight: 700;
|
|
103
|
+
display: flex;
|
|
104
|
+
align-items: center;
|
|
105
|
+
justify-content: center;
|
|
106
|
+
pointer-events: none;
|
|
107
|
+
line-height: 1;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.clw-badge:empty,
|
|
111
|
+
.clw-badge[data-count="0"] {
|
|
112
|
+
display: none;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/* ---- Panel ---- */
|
|
116
|
+
.clw-panel {
|
|
117
|
+
position: fixed;
|
|
118
|
+
bottom: 80px;
|
|
119
|
+
width: 380px;
|
|
120
|
+
max-height: min(520px, calc(100vh - 120px));
|
|
121
|
+
background: var(--clw-bg);
|
|
122
|
+
border-radius: var(--clw-radius);
|
|
123
|
+
box-shadow: var(--clw-shadow);
|
|
124
|
+
display: flex;
|
|
125
|
+
flex-direction: column;
|
|
126
|
+
overflow: hidden;
|
|
127
|
+
opacity: 0;
|
|
128
|
+
transform: translateY(12px) scale(0.96);
|
|
129
|
+
pointer-events: none;
|
|
130
|
+
transition: opacity var(--clw-transition), transform var(--clw-transition);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
:host([position="bottom-right"]) .clw-panel {
|
|
134
|
+
right: 0;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
:host([position="bottom-left"]) .clw-panel {
|
|
138
|
+
left: 0;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.clw-panel.open {
|
|
142
|
+
opacity: 1;
|
|
143
|
+
transform: translateY(0) scale(1);
|
|
144
|
+
pointer-events: auto;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* ---- Panel header ---- */
|
|
148
|
+
.clw-header {
|
|
149
|
+
padding: 16px 20px;
|
|
150
|
+
border-bottom: 1px solid var(--clw-border);
|
|
151
|
+
display: flex;
|
|
152
|
+
align-items: center;
|
|
153
|
+
justify-content: space-between;
|
|
154
|
+
flex-shrink: 0;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.clw-header-title {
|
|
158
|
+
font-size: 15px;
|
|
159
|
+
font-weight: 600;
|
|
160
|
+
color: var(--clw-text);
|
|
161
|
+
letter-spacing: -0.01em;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.clw-close {
|
|
165
|
+
width: 28px;
|
|
166
|
+
height: 28px;
|
|
167
|
+
border-radius: 6px;
|
|
168
|
+
border: none;
|
|
169
|
+
background: transparent;
|
|
170
|
+
color: var(--clw-text-muted);
|
|
171
|
+
cursor: pointer;
|
|
172
|
+
display: flex;
|
|
173
|
+
align-items: center;
|
|
174
|
+
justify-content: center;
|
|
175
|
+
transition: background var(--clw-transition), color var(--clw-transition);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.clw-close:hover {
|
|
179
|
+
background: var(--clw-bg-hover);
|
|
180
|
+
color: var(--clw-text);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.clw-close svg {
|
|
184
|
+
width: 16px;
|
|
185
|
+
height: 16px;
|
|
186
|
+
stroke: currentColor;
|
|
187
|
+
fill: none;
|
|
188
|
+
stroke-width: 2;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/* ---- Entries list ---- */
|
|
192
|
+
.clw-entries {
|
|
193
|
+
overflow-y: auto;
|
|
194
|
+
flex: 1;
|
|
195
|
+
padding: 8px 0;
|
|
196
|
+
overscroll-behavior: contain;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.clw-entries::-webkit-scrollbar {
|
|
200
|
+
width: 6px;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.clw-entries::-webkit-scrollbar-track {
|
|
204
|
+
background: transparent;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.clw-entries::-webkit-scrollbar-thumb {
|
|
208
|
+
background: var(--clw-border);
|
|
209
|
+
border-radius: 3px;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/* ---- Single entry ---- */
|
|
213
|
+
.clw-entry {
|
|
214
|
+
padding: 14px 20px;
|
|
215
|
+
border-bottom: 1px solid var(--clw-border);
|
|
216
|
+
transition: background var(--clw-transition);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.clw-entry:last-child {
|
|
220
|
+
border-bottom: none;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.clw-entry:hover {
|
|
224
|
+
background: var(--clw-bg-hover);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.clw-entry-meta {
|
|
228
|
+
display: flex;
|
|
229
|
+
align-items: center;
|
|
230
|
+
gap: 8px;
|
|
231
|
+
margin-bottom: 6px;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.clw-type-badge {
|
|
235
|
+
font-size: 11px;
|
|
236
|
+
font-weight: 600;
|
|
237
|
+
padding: 2px 8px;
|
|
238
|
+
border-radius: 4px;
|
|
239
|
+
text-transform: uppercase;
|
|
240
|
+
letter-spacing: 0.04em;
|
|
241
|
+
line-height: 1.4;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.clw-type-feature {
|
|
245
|
+
background: rgba(59, 130, 246, 0.15);
|
|
246
|
+
color: #60a5fa;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.clw-type-improvement {
|
|
250
|
+
background: rgba(168, 85, 247, 0.15);
|
|
251
|
+
color: #c084fc;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.clw-type-fix {
|
|
255
|
+
background: rgba(34, 197, 94, 0.15);
|
|
256
|
+
color: #4ade80;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.clw-type-announcement {
|
|
260
|
+
background: rgba(251, 191, 36, 0.15);
|
|
261
|
+
color: #fbbf24;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/* Light theme badge overrides */
|
|
265
|
+
:host([data-theme="light"]) .clw-type-feature {
|
|
266
|
+
background: rgba(59, 130, 246, 0.1);
|
|
267
|
+
color: #2563eb;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
:host([data-theme="light"]) .clw-type-improvement {
|
|
271
|
+
background: rgba(168, 85, 247, 0.1);
|
|
272
|
+
color: #7c3aed;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
:host([data-theme="light"]) .clw-type-fix {
|
|
276
|
+
background: rgba(34, 197, 94, 0.1);
|
|
277
|
+
color: #16a34a;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
:host([data-theme="light"]) .clw-type-announcement {
|
|
281
|
+
background: rgba(251, 191, 36, 0.1);
|
|
282
|
+
color: #d97706;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
.clw-entry-date {
|
|
286
|
+
font-size: 12px;
|
|
287
|
+
color: var(--clw-text-muted);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.clw-entry-title {
|
|
291
|
+
font-size: 14px;
|
|
292
|
+
font-weight: 600;
|
|
293
|
+
color: var(--clw-text);
|
|
294
|
+
margin-bottom: 4px;
|
|
295
|
+
line-height: 1.4;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.clw-entry-title a {
|
|
299
|
+
color: inherit;
|
|
300
|
+
text-decoration: none;
|
|
301
|
+
transition: color var(--clw-transition);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.clw-entry-title a:hover {
|
|
305
|
+
color: var(--clw-accent);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.clw-entry-desc {
|
|
309
|
+
font-size: 13px;
|
|
310
|
+
color: var(--clw-text-secondary);
|
|
311
|
+
line-height: 1.55;
|
|
312
|
+
display: -webkit-box;
|
|
313
|
+
-webkit-line-clamp: 3;
|
|
314
|
+
-webkit-box-orient: vertical;
|
|
315
|
+
overflow: hidden;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/* ---- Footer ---- */
|
|
319
|
+
.clw-footer {
|
|
320
|
+
padding: 10px 20px;
|
|
321
|
+
border-top: 1px solid var(--clw-border);
|
|
322
|
+
text-align: center;
|
|
323
|
+
flex-shrink: 0;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.clw-footer a {
|
|
327
|
+
font-size: 11px;
|
|
328
|
+
color: var(--clw-text-muted);
|
|
329
|
+
text-decoration: none;
|
|
330
|
+
transition: color var(--clw-transition);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.clw-footer a:hover {
|
|
334
|
+
color: var(--clw-text-secondary);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/* ---- Loading / Empty ---- */
|
|
338
|
+
.clw-loading,
|
|
339
|
+
.clw-empty {
|
|
340
|
+
padding: 40px 20px;
|
|
341
|
+
text-align: center;
|
|
342
|
+
color: var(--clw-text-muted);
|
|
343
|
+
font-size: 13px;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
.clw-spinner {
|
|
347
|
+
width: 24px;
|
|
348
|
+
height: 24px;
|
|
349
|
+
border: 2px solid var(--clw-border);
|
|
350
|
+
border-top-color: var(--clw-accent);
|
|
351
|
+
border-radius: 50%;
|
|
352
|
+
animation: clw-spin 0.7s linear infinite;
|
|
353
|
+
margin: 0 auto 12px;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
@keyframes clw-spin {
|
|
357
|
+
to { transform: rotate(360deg); }
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/* ---- Mobile ---- */
|
|
361
|
+
@media (max-width: 440px) {
|
|
362
|
+
.clw-panel {
|
|
363
|
+
width: calc(100vw - 24px);
|
|
364
|
+
bottom: 76px;
|
|
365
|
+
max-height: calc(100vh - 100px);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
:host([position="bottom-right"]) .clw-panel {
|
|
369
|
+
right: -8px;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
:host([position="bottom-left"]) .clw-panel {
|
|
373
|
+
left: -8px;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
`}var p={feature:"New",improvement:"Improved",fix:"Fixed",announcement:"Announcement"};var y="clw_last_seen_",k='<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 2C10.343 2 9 3.343 9 5v.26C6.667 6.34 5 8.66 5 11.4V16l-2 2v1h18v-1l-2-2v-4.6c0-2.74-1.667-5.06-4-6.14V5c0-1.657-1.343-3-3-3zm0 20c1.105 0 2-.895 2-2h-4c0 1.105.895 2 2 2z"/></svg>',E='<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>',a=class extends HTMLElement{constructor(){super();this.entries=[];this.isOpen=!1;this.isLoading=!1;this.shadow=this.attachShadow({mode:"open"})}static get observedAttributes(){return["project-id","api-url","position","theme","accent-color"]}get projectId(){return this.getAttribute("project-id")||""}get apiUrl(){return this.getAttribute("api-url")||"https://www.changelogdev.com/api/widget"}get position(){return this.getAttribute("position")==="bottom-left"?"bottom-left":"bottom-right"}get theme(){let e=this.getAttribute("theme");return e==="light"||e==="dark"?e:"auto"}get accentColor(){return this.getAttribute("accent-color")||"#6366f1"}connectedCallback(){this.hasAttribute("position")||this.setAttribute("position","bottom-right"),this.resolveTheme(),this.render(),this.loadEntries(),this.theme==="auto"&&window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",()=>this.resolveTheme())}attributeChangedCallback(e,t,i){t!==i&&(e==="theme"&&this.resolveTheme(),e==="accent-color"&&this.render(),(e==="project-id"||e==="api-url")&&this.loadEntries())}resolveTheme(){let e="dark";this.theme==="auto"?e=window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light":e=this.theme,this.setAttribute("data-theme",e)}async loadEntries(){this.projectId&&(this.isLoading=!0,this.updatePanel(),this.entries=await d(this.apiUrl,this.projectId),this.isLoading=!1,this.updatePanel(),this.updateBadge())}get storageKey(){return`${y}${this.projectId}`}getLastSeenDate(){try{return localStorage.getItem(this.storageKey)}catch{return null}}markAsSeen(){if(this.entries.length!==0)try{localStorage.setItem(this.storageKey,this.entries[0].date)}catch{}}getUnreadCount(){let e=this.getLastSeenDate();return e?this.entries.filter(t=>t.date>e).length:this.entries.length}render(){let e=document.createElement("style");e.textContent=g(this.accentColor);let t=document.createElement("div");t.innerHTML=`
|
|
377
|
+
<button class="clw-trigger" aria-label="View changelog">
|
|
378
|
+
${k}
|
|
379
|
+
<span class="clw-badge" data-count="0"></span>
|
|
380
|
+
</button>
|
|
381
|
+
<div class="clw-panel" role="dialog" aria-label="Changelog">
|
|
382
|
+
<div class="clw-header">
|
|
383
|
+
<span class="clw-header-title">What's New</span>
|
|
384
|
+
<button class="clw-close" aria-label="Close changelog">
|
|
385
|
+
${E}
|
|
386
|
+
</button>
|
|
387
|
+
</div>
|
|
388
|
+
<div class="clw-entries">
|
|
389
|
+
<div class="clw-loading">
|
|
390
|
+
<div class="clw-spinner"></div>
|
|
391
|
+
Loading...
|
|
392
|
+
</div>
|
|
393
|
+
</div>
|
|
394
|
+
<div class="clw-footer">
|
|
395
|
+
<a href="https://www.changelogdev.com?ref=widget" target="_blank" rel="noopener noreferrer">
|
|
396
|
+
Powered by Changelog.dev
|
|
397
|
+
</a>
|
|
398
|
+
</div>
|
|
399
|
+
</div>
|
|
400
|
+
`,this.shadow.innerHTML="",this.shadow.appendChild(e),this.shadow.appendChild(t),this.shadow.querySelector(".clw-trigger").addEventListener("click",()=>this.toggle()),this.shadow.querySelector(".clw-close").addEventListener("click",()=>this.close()),document.addEventListener("click",n=>{this.isOpen&&!this.contains(n.target)&&this.close()}),document.addEventListener("keydown",n=>{n.key==="Escape"&&this.isOpen&&this.close()})}toggle(){this.isOpen?this.close():this.open()}open(){this.isOpen=!0,this.shadow.querySelector(".clw-panel")?.classList.add("open"),this.markAsSeen(),this.updateBadge()}close(){this.isOpen=!1,this.shadow.querySelector(".clw-panel")?.classList.remove("open")}updateBadge(){let e=this.shadow.querySelector(".clw-badge");if(!e)return;let t=this.getUnreadCount();e.textContent=t>0?t>9?"9+":String(t):"",e.setAttribute("data-count",String(t))}updatePanel(){let e=this.shadow.querySelector(".clw-entries");if(e){if(this.isLoading){e.innerHTML=`
|
|
401
|
+
<div class="clw-loading">
|
|
402
|
+
<div class="clw-spinner"></div>
|
|
403
|
+
Loading...
|
|
404
|
+
</div>
|
|
405
|
+
`;return}if(this.entries.length===0){e.innerHTML=`
|
|
406
|
+
<div class="clw-empty">No changelog entries yet.</div>
|
|
407
|
+
`;return}e.innerHTML=this.entries.map(t=>this.renderEntry(t)).join("")}}renderEntry(e){let t=p[e.type]||e.type,i=this.formatDate(e.date),s=e.url?`<a href="${this.escapeHtml(e.url)}" target="_blank" rel="noopener noreferrer">${this.escapeHtml(e.title)}</a>`:this.escapeHtml(e.title);return`
|
|
408
|
+
<div class="clw-entry">
|
|
409
|
+
<div class="clw-entry-meta">
|
|
410
|
+
<span class="clw-type-badge clw-type-${e.type}">${t}</span>
|
|
411
|
+
<span class="clw-entry-date">${i}</span>
|
|
412
|
+
</div>
|
|
413
|
+
<div class="clw-entry-title">${s}</div>
|
|
414
|
+
<div class="clw-entry-desc">${this.escapeHtml(e.description)}</div>
|
|
415
|
+
</div>
|
|
416
|
+
`}formatDate(e){try{let t=new Date(e+"T00:00:00"),i=new Date,s=i.getTime()-t.getTime(),n=Math.floor(s/(1e3*60*60*24));if(n===0)return"Today";if(n===1)return"Yesterday";if(n<7)return`${n} days ago`;if(n<30){let c=Math.floor(n/7);return`${c} ${c===1?"week":"weeks"} ago`}return t.toLocaleDateString("en-US",{month:"short",day:"numeric",year:t.getFullYear()!==i.getFullYear()?"numeric":void 0})}catch{return e}}escapeHtml(e){let t=document.createElement("div");return t.textContent=e,t.innerHTML}};function h(){typeof window<"u"&&!customElements.get("changelog-widget")&&customElements.define("changelog-widget",a)}function C(r){h();let o=document.createElement("changelog-widget");return o.setAttribute("project-id",r.projectId),r.apiUrl&&o.setAttribute("api-url",r.apiUrl),r.position&&o.setAttribute("position",r.position),r.theme&&o.setAttribute("theme",r.theme),r.accentColor&&o.setAttribute("accent-color",r.accentColor),document.body.appendChild(o),o}h();return v(L);})();
|
|
417
|
+
//# sourceMappingURL=index.iife.js.map
|