@squeletteapp/widget 1.1.0 → 3.0.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/dist/changelog-entry.js +224 -0
- package/dist/index.js +1 -4057
- package/package.json +5 -6
- package/dist/index.js.map +0 -7
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { createWidget } from '@squeletteapp/widget-builder';
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// Constants
|
|
4
|
+
// ============================================================================
|
|
5
|
+
const CACHE_DURATION_MS = 5 * 60 * 1000; // 5 minutes
|
|
6
|
+
const STORAGE_KEY = 'squelette_banner_last_ticket';
|
|
7
|
+
const DEFAULT_BASE = 'https://www.squelette.app';
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// Cache Utilities
|
|
10
|
+
// ============================================================================
|
|
11
|
+
function getEnableCache(debug) {
|
|
12
|
+
// Only disable cache when debug is explicitly true
|
|
13
|
+
if (debug === true)
|
|
14
|
+
return false;
|
|
15
|
+
try {
|
|
16
|
+
const userPreference = localStorage.getItem('squelette-enable-cache');
|
|
17
|
+
if (userPreference === 'false')
|
|
18
|
+
return false;
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async function fetchLastChangelogTicket(base, slug, debug) {
|
|
26
|
+
try {
|
|
27
|
+
const enableCache = getEnableCache(debug);
|
|
28
|
+
// Check cache first if enabled
|
|
29
|
+
if (enableCache) {
|
|
30
|
+
const cacheKey = `squelette_banner_cache_${slug}`;
|
|
31
|
+
const cached = localStorage.getItem(cacheKey);
|
|
32
|
+
if (cached) {
|
|
33
|
+
const { ticket, timestamp } = JSON.parse(cached);
|
|
34
|
+
const isExpired = Date.now() - timestamp > CACHE_DURATION_MS;
|
|
35
|
+
if (!isExpired) {
|
|
36
|
+
return ticket;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Fetch from API
|
|
41
|
+
const url = `${base}/api/public/w/${slug}/changelog/last`;
|
|
42
|
+
const response = await fetch(url);
|
|
43
|
+
const json = (await response.json());
|
|
44
|
+
const ticket = json?.ticket ?? null;
|
|
45
|
+
// Store in cache if enabled
|
|
46
|
+
if (enableCache && ticket) {
|
|
47
|
+
const cacheKey = `squelette_banner_cache_${slug}`;
|
|
48
|
+
const cachedData = {
|
|
49
|
+
ticket,
|
|
50
|
+
timestamp: Date.now(),
|
|
51
|
+
};
|
|
52
|
+
localStorage.setItem(cacheKey, JSON.stringify(cachedData));
|
|
53
|
+
}
|
|
54
|
+
return ticket;
|
|
55
|
+
}
|
|
56
|
+
catch (e) {
|
|
57
|
+
console.error('[SqueletteBanner] Error fetching changelog ticket:', e);
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function shouldShowBanner(ticket, debug) {
|
|
62
|
+
const lastSeenCreatedAt = localStorage.getItem(STORAGE_KEY);
|
|
63
|
+
// Show if cache is disabled, never seen before, or newer ticket
|
|
64
|
+
return (!getEnableCache(debug) ||
|
|
65
|
+
!lastSeenCreatedAt ||
|
|
66
|
+
new Date(ticket.created_at) > new Date(lastSeenCreatedAt));
|
|
67
|
+
}
|
|
68
|
+
function markTicketAsSeen(ticket) {
|
|
69
|
+
localStorage.setItem(STORAGE_KEY, ticket.created_at);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Wrap a Widget with setTheme functionality
|
|
73
|
+
* Sends a THEME_UPDATE message to the iframe instead of reloading
|
|
74
|
+
*/
|
|
75
|
+
function withTheme(widget) {
|
|
76
|
+
return {
|
|
77
|
+
...widget,
|
|
78
|
+
setTheme(theme) {
|
|
79
|
+
const message = {
|
|
80
|
+
type: 'THEME_UPDATE',
|
|
81
|
+
payload: { theme },
|
|
82
|
+
};
|
|
83
|
+
widget.sendMessage(message);
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
// ============================================================================
|
|
88
|
+
// Widget Store
|
|
89
|
+
// ============================================================================
|
|
90
|
+
export function createChangelogStore() {
|
|
91
|
+
const widgets = {};
|
|
92
|
+
return {
|
|
93
|
+
getState: () => widgets,
|
|
94
|
+
mount: async (source, widget) => {
|
|
95
|
+
widgets[source] = widget;
|
|
96
|
+
widget.preload();
|
|
97
|
+
},
|
|
98
|
+
open: async (source) => {
|
|
99
|
+
const widget = widgets[source];
|
|
100
|
+
if (widget) {
|
|
101
|
+
widget.open();
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
unmount: (source) => {
|
|
105
|
+
const widget = widgets[source];
|
|
106
|
+
if (widget) {
|
|
107
|
+
widget.destroy();
|
|
108
|
+
delete widgets[source];
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
setTheme: (theme) => {
|
|
112
|
+
for (const widget of Object.values(widgets)) {
|
|
113
|
+
widget.setTheme(theme);
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
// ============================================================================
|
|
119
|
+
// Widget Creators
|
|
120
|
+
// ============================================================================
|
|
121
|
+
export function createChangelogEntryWidget(ticketId, options = {}) {
|
|
122
|
+
const { base = DEFAULT_BASE, contentTheme = 'light', slug = 'squelette' } = options;
|
|
123
|
+
const widget = createWidget({
|
|
124
|
+
elementName: 'sq-changelog-entry-widget',
|
|
125
|
+
source: `${base}/widget/${contentTheme}/${slug}/changelog/${ticketId}`,
|
|
126
|
+
position: 'center',
|
|
127
|
+
overlay: true,
|
|
128
|
+
});
|
|
129
|
+
return withTheme(widget);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Creates and initializes a changelog banner widget.
|
|
133
|
+
* - Fetches the latest changelog ticket
|
|
134
|
+
* - Checks if it should be shown (not already seen)
|
|
135
|
+
* - Preloads the iframe, which will send OPEN_WIDGET when ready
|
|
136
|
+
* - Returns null if nothing to show
|
|
137
|
+
*/
|
|
138
|
+
export async function createChangelogBannerWidget(store, options) {
|
|
139
|
+
const { base = DEFAULT_BASE, contentTheme = 'light', slug, debug = false } = options;
|
|
140
|
+
// Fetch the latest ticket
|
|
141
|
+
const ticket = await fetchLastChangelogTicket(base, slug, debug);
|
|
142
|
+
if (!ticket) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
const shouldRender = shouldShowBanner(ticket, debug);
|
|
146
|
+
// Check if we should show the banner
|
|
147
|
+
if (!shouldRender) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
const ticketId = String(ticket.id);
|
|
151
|
+
// Preload the entry widget for when user clicks the banner
|
|
152
|
+
store.mount(ticketId, createChangelogEntryWidget(ticketId, { base, contentTheme, slug }));
|
|
153
|
+
const widget = createWidget({
|
|
154
|
+
elementName: 'sq-changelog-banner-widget',
|
|
155
|
+
source: `${base}/widget/${contentTheme}/${slug}/changelog/${ticketId}/banner`,
|
|
156
|
+
position: 's',
|
|
157
|
+
overlay: false,
|
|
158
|
+
fitToContent: true,
|
|
159
|
+
style: {
|
|
160
|
+
borderRadius: 32,
|
|
161
|
+
height: 32,
|
|
162
|
+
},
|
|
163
|
+
onMessage: (type, message, actions) => {
|
|
164
|
+
// Iframe signals it's ready to be shown
|
|
165
|
+
if (type === 'OPEN_WIDGET') {
|
|
166
|
+
widget.open();
|
|
167
|
+
}
|
|
168
|
+
// User clicked on the banner content
|
|
169
|
+
if (type === 'OPEN_CHANGELOG_ENTRY') {
|
|
170
|
+
const id = message.payload.ticketId;
|
|
171
|
+
actions.close();
|
|
172
|
+
store.open(id);
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
// Register callback to mark ticket as seen when banner closes
|
|
177
|
+
widget.onOpenChange((isOpen) => {
|
|
178
|
+
if (!isOpen) {
|
|
179
|
+
markTicketAsSeen(ticket);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
// Preload the iframe - it will send OPEN_WIDGET when content is ready
|
|
183
|
+
widget.preload();
|
|
184
|
+
return withTheme(widget);
|
|
185
|
+
}
|
|
186
|
+
export function createChangelogEntriesListDropdownWidget(anchor, store, options = {}) {
|
|
187
|
+
const { base = DEFAULT_BASE, contentTheme = 'light', slug = 'squelette', position = 'nw' } = options;
|
|
188
|
+
const widget = createWidget({
|
|
189
|
+
elementName: 'sq-changelog-entries-list-dropdown-widget',
|
|
190
|
+
source: `${base}/widget/${contentTheme}/${slug}/changelog/list`,
|
|
191
|
+
position,
|
|
192
|
+
anchor,
|
|
193
|
+
fitToContent: true,
|
|
194
|
+
onMessage: (type, message, actions) => {
|
|
195
|
+
if (type === 'PRELOAD_CHANGELOG_ENTRIES') {
|
|
196
|
+
const ids = message.payload.ids;
|
|
197
|
+
for (const id of ids) {
|
|
198
|
+
store.mount(id, createChangelogEntryWidget(id, { base, contentTheme, slug }));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (type === 'OPEN_CHANGELOG_ENTRY') {
|
|
202
|
+
const ticketId = message.payload.ticketId;
|
|
203
|
+
actions.close();
|
|
204
|
+
store.open(ticketId);
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
return withTheme(widget);
|
|
209
|
+
}
|
|
210
|
+
// ============================================================================
|
|
211
|
+
// Utility
|
|
212
|
+
// ============================================================================
|
|
213
|
+
/**
|
|
214
|
+
* Clear the cache for a specific workspace
|
|
215
|
+
*/
|
|
216
|
+
export function clearBannerCache(slug) {
|
|
217
|
+
try {
|
|
218
|
+
localStorage.removeItem(`squelette_banner_cache_${slug}`);
|
|
219
|
+
localStorage.removeItem(STORAGE_KEY);
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
// Ignore localStorage errors
|
|
223
|
+
}
|
|
224
|
+
}
|