featuredrop 1.0.1 → 1.2.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 +626 -186
- package/dist/angular.cjs +286 -0
- package/dist/angular.cjs.map +1 -0
- package/dist/angular.d.cts +229 -0
- package/dist/angular.d.ts +229 -0
- package/dist/angular.js +283 -0
- package/dist/angular.js.map +1 -0
- package/dist/featuredrop.cjs +1256 -0
- package/dist/featuredrop.cjs.map +1 -0
- package/dist/index.cjs +2769 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1020 -9
- package/dist/index.d.ts +1020 -9
- package/dist/index.js +2726 -10
- package/dist/index.js.map +1 -1
- package/dist/preact.cjs +7289 -0
- package/dist/preact.cjs.map +1 -0
- package/dist/preact.d.cts +1266 -0
- package/dist/preact.d.ts +1266 -0
- package/dist/preact.js +7259 -0
- package/dist/preact.js.map +1 -0
- package/dist/react.cjs +7142 -49
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +1119 -7
- package/dist/react.d.ts +1119 -7
- package/dist/react.js +7122 -52
- package/dist/react.js.map +1 -1
- package/dist/schema.cjs +215 -0
- package/dist/schema.cjs.map +1 -0
- package/dist/schema.d.cts +203 -0
- package/dist/schema.d.ts +203 -0
- package/dist/schema.js +209 -0
- package/dist/schema.js.map +1 -0
- package/dist/solid.cjs +373 -0
- package/dist/solid.cjs.map +1 -0
- package/dist/solid.d.cts +242 -0
- package/dist/solid.d.ts +242 -0
- package/dist/solid.js +366 -0
- package/dist/solid.js.map +1 -0
- package/dist/svelte.cjs +329 -0
- package/dist/svelte.cjs.map +1 -0
- package/dist/svelte.js +324 -0
- package/dist/svelte.js.map +1 -0
- package/dist/testing.cjs +1422 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +339 -0
- package/dist/testing.d.ts +339 -0
- package/dist/testing.js +1415 -0
- package/dist/testing.js.map +1 -0
- package/dist/vue.cjs +1084 -0
- package/dist/vue.cjs.map +1 -0
- package/dist/vue.js +1072 -0
- package/dist/vue.js.map +1 -0
- package/dist/web-components.cjs +483 -0
- package/dist/web-components.cjs.map +1 -0
- package/dist/web-components.d.cts +211 -0
- package/dist/web-components.d.ts +211 -0
- package/dist/web-components.js +477 -0
- package/dist/web-components.js.map +1 -0
- package/package.json +126 -3
package/dist/vue.js
ADDED
|
@@ -0,0 +1,1072 @@
|
|
|
1
|
+
import { defineComponent, ref, watch, computed, provide, h, inject, onBeforeUnmount } from 'vue';
|
|
2
|
+
import { createRequire } from 'module';
|
|
3
|
+
|
|
4
|
+
// src/vue/provider.ts
|
|
5
|
+
|
|
6
|
+
// src/semver.ts
|
|
7
|
+
var SEMVER_REGEX = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?/;
|
|
8
|
+
function parseSemver(input) {
|
|
9
|
+
const match = input.trim().match(SEMVER_REGEX);
|
|
10
|
+
if (!match) return null;
|
|
11
|
+
return {
|
|
12
|
+
major: Number(match[1]),
|
|
13
|
+
minor: Number(match[2]),
|
|
14
|
+
patch: Number(match[3]),
|
|
15
|
+
prerelease: match[4] ? match[4].split(".") : []
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function compareSemver(a, b) {
|
|
19
|
+
const pa = parseSemver(a);
|
|
20
|
+
const pb = parseSemver(b);
|
|
21
|
+
if (!pa || !pb) return 0;
|
|
22
|
+
for (const key of ["major", "minor", "patch"]) {
|
|
23
|
+
if (pa[key] !== pb[key]) return pa[key] - pb[key];
|
|
24
|
+
}
|
|
25
|
+
const aPre = pa.prerelease;
|
|
26
|
+
const bPre = pb.prerelease;
|
|
27
|
+
if (aPre.length === 0 && bPre.length === 0) return 0;
|
|
28
|
+
if (aPre.length === 0) return 1;
|
|
29
|
+
if (bPre.length === 0) return -1;
|
|
30
|
+
const len = Math.max(aPre.length, bPre.length);
|
|
31
|
+
for (let i = 0; i < len; i++) {
|
|
32
|
+
const ai = aPre[i];
|
|
33
|
+
const bi = bPre[i];
|
|
34
|
+
if (ai === void 0) return -1;
|
|
35
|
+
if (bi === void 0) return 1;
|
|
36
|
+
const aNum = Number(ai);
|
|
37
|
+
const bNum = Number(bi);
|
|
38
|
+
const aIsNum = Number.isInteger(aNum);
|
|
39
|
+
const bIsNum = Number.isInteger(bNum);
|
|
40
|
+
if (aIsNum && bIsNum && aNum !== bNum) return aNum - bNum;
|
|
41
|
+
if (aIsNum !== bIsNum) return aIsNum ? -1 : 1;
|
|
42
|
+
if (ai !== bi) return ai < bi ? -1 : 1;
|
|
43
|
+
}
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
function parseComparator(comp) {
|
|
47
|
+
const match = comp.trim().match(/^(>=|<=|>|<|=)?\\s*(.+)$/);
|
|
48
|
+
if (!match) return null;
|
|
49
|
+
const op = match[1] || ">=";
|
|
50
|
+
const version = match[2];
|
|
51
|
+
if (!parseSemver(version)) return null;
|
|
52
|
+
return { op, version };
|
|
53
|
+
}
|
|
54
|
+
function satisfiesComparator(version, comp) {
|
|
55
|
+
const diff = compareSemver(version, comp.version);
|
|
56
|
+
switch (comp.op) {
|
|
57
|
+
case ">":
|
|
58
|
+
return diff > 0;
|
|
59
|
+
case ">=":
|
|
60
|
+
return diff >= 0;
|
|
61
|
+
case "<":
|
|
62
|
+
return diff < 0;
|
|
63
|
+
case "<=":
|
|
64
|
+
return diff <= 0;
|
|
65
|
+
case "=":
|
|
66
|
+
return diff === 0;
|
|
67
|
+
default:
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function satisfiesRange(version, range) {
|
|
72
|
+
const parts = range.split(/\s+/).filter(Boolean);
|
|
73
|
+
if (parts.length === 0) return true;
|
|
74
|
+
for (const part of parts) {
|
|
75
|
+
const comp = parseComparator(part);
|
|
76
|
+
if (!comp) return false;
|
|
77
|
+
if (!satisfiesComparator(version, comp)) return false;
|
|
78
|
+
}
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
function isTriggerMatch(trigger, context) {
|
|
82
|
+
if (!trigger) return true;
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/core.ts
|
|
87
|
+
function matchesAudience(audience, userContext) {
|
|
88
|
+
if (audience.plan && audience.plan.length > 0) {
|
|
89
|
+
if (!userContext.plan || !audience.plan.includes(userContext.plan)) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (audience.role && audience.role.length > 0) {
|
|
94
|
+
if (!userContext.role || !audience.role.includes(userContext.role)) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (audience.region && audience.region.length > 0) {
|
|
99
|
+
if (!userContext.region || !audience.region.includes(userContext.region)) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
function isAudienceMatch(feature, userContext, matchFn) {
|
|
106
|
+
if (!feature.audience) return true;
|
|
107
|
+
const { plan, role, region, custom } = feature.audience;
|
|
108
|
+
const hasRules = plan && plan.length > 0 || role && role.length > 0 || region && region.length > 0 || custom && Object.keys(custom).length > 0;
|
|
109
|
+
if (!hasRules) return true;
|
|
110
|
+
if (!userContext) return false;
|
|
111
|
+
if (matchFn) return matchFn(feature.audience, userContext);
|
|
112
|
+
return matchesAudience(feature.audience, userContext);
|
|
113
|
+
}
|
|
114
|
+
function isVersionMatch(feature, appVersion) {
|
|
115
|
+
const v = feature.version;
|
|
116
|
+
if (!v || typeof v === "string") return true;
|
|
117
|
+
if (!appVersion) return false;
|
|
118
|
+
if (!v.introduced && !v.showNewUntil && !v.deprecatedAt && !v.showIn) return true;
|
|
119
|
+
if (v.showIn && !satisfiesRange(appVersion, v.showIn)) return false;
|
|
120
|
+
if (v.introduced && compareSemver(appVersion, v.introduced) < 0) return false;
|
|
121
|
+
if (v.deprecatedAt && compareSemver(appVersion, v.deprecatedAt) >= 0) return false;
|
|
122
|
+
if (v.showNewUntil && compareSemver(appVersion, v.showNewUntil) >= 0) return false;
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
function isDependencyMatch(feature, dismissedIds, dependencyState) {
|
|
126
|
+
const dependsOn = feature.dependsOn;
|
|
127
|
+
if (!dependsOn) return true;
|
|
128
|
+
const dismissedDependencyIds = dismissedIds;
|
|
129
|
+
if (dependsOn.seen && dependsOn.seen.length > 0) {
|
|
130
|
+
for (const id of dependsOn.seen) {
|
|
131
|
+
if (!dismissedDependencyIds.has(id)) return false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (dependsOn.clicked && dependsOn.clicked.length > 0) {
|
|
135
|
+
for (const id of dependsOn.clicked) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (dependsOn.dismissed && dependsOn.dismissed.length > 0) {
|
|
140
|
+
for (const id of dependsOn.dismissed) {
|
|
141
|
+
if (!dismissedDependencyIds.has(id)) return false;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
function isNew(feature, watermark, dismissedIds, now = /* @__PURE__ */ new Date(), userContext, matchAudience, appVersion, dependencyState, triggerContext) {
|
|
147
|
+
if (dismissedIds.has(feature.id)) return false;
|
|
148
|
+
if (!isAudienceMatch(feature, userContext, matchAudience)) return false;
|
|
149
|
+
if (!isDependencyMatch(feature, dismissedIds)) return false;
|
|
150
|
+
if (!isVersionMatch(feature, appVersion)) return false;
|
|
151
|
+
if (!isTriggerMatch(feature.trigger)) return false;
|
|
152
|
+
const nowMs = now.getTime();
|
|
153
|
+
if (feature.publishAt) {
|
|
154
|
+
const publishMs = new Date(feature.publishAt).getTime();
|
|
155
|
+
if (nowMs < publishMs) return false;
|
|
156
|
+
}
|
|
157
|
+
const showUntilMs = new Date(feature.showNewUntil).getTime();
|
|
158
|
+
if (nowMs >= showUntilMs) return false;
|
|
159
|
+
if (watermark) {
|
|
160
|
+
const watermarkMs = new Date(watermark).getTime();
|
|
161
|
+
const releasedMs = new Date(feature.releasedAt).getTime();
|
|
162
|
+
if (releasedMs <= watermarkMs) return false;
|
|
163
|
+
}
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
function getNewFeatures(manifest, storage, now = /* @__PURE__ */ new Date(), userContext, matchAudience, appVersion, dependencyState, triggerContext) {
|
|
167
|
+
const watermark = storage.getWatermark();
|
|
168
|
+
const dismissedIds = storage.getDismissedIds();
|
|
169
|
+
return manifest.filter(
|
|
170
|
+
(f) => isNew(
|
|
171
|
+
f,
|
|
172
|
+
watermark,
|
|
173
|
+
dismissedIds,
|
|
174
|
+
now,
|
|
175
|
+
userContext,
|
|
176
|
+
matchAudience,
|
|
177
|
+
appVersion)
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
function hasNewFeature(manifest, sidebarKey, storage, now = /* @__PURE__ */ new Date(), userContext, matchAudience, appVersion, dependencyState, triggerContext) {
|
|
181
|
+
const watermark = storage.getWatermark();
|
|
182
|
+
const dismissedIds = storage.getDismissedIds();
|
|
183
|
+
return manifest.some(
|
|
184
|
+
(f) => f.sidebarKey === sidebarKey && isNew(
|
|
185
|
+
f,
|
|
186
|
+
watermark,
|
|
187
|
+
dismissedIds,
|
|
188
|
+
now,
|
|
189
|
+
userContext,
|
|
190
|
+
matchAudience,
|
|
191
|
+
appVersion)
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// src/vue/context.ts
|
|
196
|
+
var FeatureDropVueContextKey = /* @__PURE__ */ Symbol("FeatureDropVueContext");
|
|
197
|
+
|
|
198
|
+
// src/vue/provider.ts
|
|
199
|
+
var FeatureDropProvider = defineComponent({
|
|
200
|
+
name: "FeatureDropProvider",
|
|
201
|
+
props: {
|
|
202
|
+
manifest: {
|
|
203
|
+
type: Array,
|
|
204
|
+
required: true
|
|
205
|
+
},
|
|
206
|
+
storage: {
|
|
207
|
+
type: Object,
|
|
208
|
+
required: true
|
|
209
|
+
},
|
|
210
|
+
analytics: {
|
|
211
|
+
type: Object,
|
|
212
|
+
required: false
|
|
213
|
+
},
|
|
214
|
+
userContext: {
|
|
215
|
+
type: Object,
|
|
216
|
+
required: false
|
|
217
|
+
},
|
|
218
|
+
matchAudience: {
|
|
219
|
+
type: Function,
|
|
220
|
+
required: false
|
|
221
|
+
},
|
|
222
|
+
appVersion: {
|
|
223
|
+
type: String,
|
|
224
|
+
required: false
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
setup(props, { slots }) {
|
|
228
|
+
const newFeatures = ref(
|
|
229
|
+
getNewFeatures(
|
|
230
|
+
props.manifest,
|
|
231
|
+
props.storage,
|
|
232
|
+
/* @__PURE__ */ new Date(),
|
|
233
|
+
props.userContext,
|
|
234
|
+
props.matchAudience,
|
|
235
|
+
props.appVersion
|
|
236
|
+
)
|
|
237
|
+
);
|
|
238
|
+
const recompute = () => {
|
|
239
|
+
newFeatures.value = getNewFeatures(
|
|
240
|
+
props.manifest,
|
|
241
|
+
props.storage,
|
|
242
|
+
/* @__PURE__ */ new Date(),
|
|
243
|
+
props.userContext,
|
|
244
|
+
props.matchAudience,
|
|
245
|
+
props.appVersion
|
|
246
|
+
);
|
|
247
|
+
};
|
|
248
|
+
watch(
|
|
249
|
+
() => [props.manifest, props.storage, props.userContext, props.matchAudience, props.appVersion],
|
|
250
|
+
recompute,
|
|
251
|
+
{ deep: false }
|
|
252
|
+
);
|
|
253
|
+
const dismiss = (id) => {
|
|
254
|
+
const feature = newFeatures.value.find((f) => f.id === id);
|
|
255
|
+
props.storage.dismiss(id);
|
|
256
|
+
if (feature) {
|
|
257
|
+
props.analytics?.onFeatureDismissed?.(feature);
|
|
258
|
+
}
|
|
259
|
+
recompute();
|
|
260
|
+
};
|
|
261
|
+
const dismissAll = async () => {
|
|
262
|
+
await props.storage.dismissAll(/* @__PURE__ */ new Date());
|
|
263
|
+
newFeatures.value = [];
|
|
264
|
+
props.analytics?.onAllDismissed?.();
|
|
265
|
+
};
|
|
266
|
+
const newCount = computed(() => newFeatures.value.length);
|
|
267
|
+
const newFeaturesSorted = computed(() => {
|
|
268
|
+
const priorityOrder = { critical: 0, normal: 1, low: 2 };
|
|
269
|
+
return [...newFeatures.value].sort((a, b) => {
|
|
270
|
+
const pa = priorityOrder[a.priority ?? "normal"];
|
|
271
|
+
const pb = priorityOrder[b.priority ?? "normal"];
|
|
272
|
+
if (pa !== pb) return pa - pb;
|
|
273
|
+
return new Date(b.releasedAt).getTime() - new Date(a.releasedAt).getTime();
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
const getFeature = (sidebarKey) => newFeatures.value.find((f) => f.sidebarKey === sidebarKey);
|
|
277
|
+
const isNewFn = (sidebarKey) => hasNewFeature(
|
|
278
|
+
props.manifest,
|
|
279
|
+
sidebarKey,
|
|
280
|
+
props.storage,
|
|
281
|
+
/* @__PURE__ */ new Date(),
|
|
282
|
+
props.userContext,
|
|
283
|
+
props.matchAudience,
|
|
284
|
+
props.appVersion
|
|
285
|
+
);
|
|
286
|
+
provide(FeatureDropVueContextKey, {
|
|
287
|
+
manifest: props.manifest,
|
|
288
|
+
newFeatures,
|
|
289
|
+
newCount,
|
|
290
|
+
newFeaturesSorted,
|
|
291
|
+
isNew: isNewFn,
|
|
292
|
+
dismiss,
|
|
293
|
+
dismissAll,
|
|
294
|
+
getFeature
|
|
295
|
+
});
|
|
296
|
+
return () => slots.default?.();
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
function useFeatureDrop() {
|
|
300
|
+
const context = inject(FeatureDropVueContextKey, null);
|
|
301
|
+
if (!context) {
|
|
302
|
+
throw new Error("useFeatureDrop must be used within a <FeatureDropProvider>");
|
|
303
|
+
}
|
|
304
|
+
return context;
|
|
305
|
+
}
|
|
306
|
+
function useNewFeature(sidebarKey) {
|
|
307
|
+
const { isNew: isNew2, getFeature, dismiss } = useFeatureDrop();
|
|
308
|
+
const feature = computed(() => getFeature(sidebarKey));
|
|
309
|
+
const isNewValue = computed(() => isNew2(sidebarKey));
|
|
310
|
+
const dismissFeature = () => {
|
|
311
|
+
const value = feature.value;
|
|
312
|
+
if (value) {
|
|
313
|
+
dismiss(value.id);
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
return {
|
|
317
|
+
feature,
|
|
318
|
+
isNew: isNewValue,
|
|
319
|
+
dismiss: dismissFeature
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
function useNewCount() {
|
|
323
|
+
const { newCount } = useFeatureDrop();
|
|
324
|
+
return computed(() => newCount.value);
|
|
325
|
+
}
|
|
326
|
+
function useTabNotification(options = {}) {
|
|
327
|
+
const {
|
|
328
|
+
enabled = true,
|
|
329
|
+
template = "({count}) {title}",
|
|
330
|
+
flash = false,
|
|
331
|
+
flashInterval = 1500
|
|
332
|
+
} = options;
|
|
333
|
+
const { newCount } = useFeatureDrop();
|
|
334
|
+
if (typeof document === "undefined") return;
|
|
335
|
+
const originalTitle = document.title;
|
|
336
|
+
let interval = null;
|
|
337
|
+
const stop = watch(
|
|
338
|
+
() => newCount.value,
|
|
339
|
+
(count) => {
|
|
340
|
+
if (!enabled || count === 0) {
|
|
341
|
+
document.title = originalTitle;
|
|
342
|
+
if (interval) {
|
|
343
|
+
clearInterval(interval);
|
|
344
|
+
interval = null;
|
|
345
|
+
}
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
const nextTitle = template.replace("{count}", String(count)).replace("{title}", originalTitle);
|
|
349
|
+
if (!flash) {
|
|
350
|
+
document.title = nextTitle;
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
let showNotification = true;
|
|
354
|
+
document.title = nextTitle;
|
|
355
|
+
if (interval) clearInterval(interval);
|
|
356
|
+
interval = setInterval(() => {
|
|
357
|
+
showNotification = !showNotification;
|
|
358
|
+
document.title = showNotification ? nextTitle : originalTitle;
|
|
359
|
+
}, flashInterval);
|
|
360
|
+
},
|
|
361
|
+
{ immediate: true }
|
|
362
|
+
);
|
|
363
|
+
onBeforeUnmount(() => {
|
|
364
|
+
stop();
|
|
365
|
+
document.title = originalTitle;
|
|
366
|
+
if (interval) {
|
|
367
|
+
clearInterval(interval);
|
|
368
|
+
interval = null;
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
var NewBadge = defineComponent({
|
|
373
|
+
name: "NewBadge",
|
|
374
|
+
props: {
|
|
375
|
+
variant: {
|
|
376
|
+
type: String,
|
|
377
|
+
default: "pill"
|
|
378
|
+
},
|
|
379
|
+
show: {
|
|
380
|
+
type: Boolean,
|
|
381
|
+
default: true
|
|
382
|
+
},
|
|
383
|
+
count: {
|
|
384
|
+
type: Number,
|
|
385
|
+
required: false
|
|
386
|
+
},
|
|
387
|
+
label: {
|
|
388
|
+
type: String,
|
|
389
|
+
default: "New"
|
|
390
|
+
}
|
|
391
|
+
},
|
|
392
|
+
setup(props, { slots }) {
|
|
393
|
+
return () => {
|
|
394
|
+
if (slots.default) {
|
|
395
|
+
return slots.default({ isNew: props.show });
|
|
396
|
+
}
|
|
397
|
+
if (!props.show) return null;
|
|
398
|
+
const baseStyle = {
|
|
399
|
+
display: "inline-flex",
|
|
400
|
+
alignItems: "center",
|
|
401
|
+
justifyContent: "center",
|
|
402
|
+
fontFamily: "inherit"
|
|
403
|
+
};
|
|
404
|
+
let style = baseStyle;
|
|
405
|
+
let content = props.label;
|
|
406
|
+
if (props.variant === "dot") {
|
|
407
|
+
style = {
|
|
408
|
+
...baseStyle,
|
|
409
|
+
width: "8px",
|
|
410
|
+
height: "8px",
|
|
411
|
+
borderRadius: "9999px",
|
|
412
|
+
backgroundColor: "var(--featuredrop-color, #f59e0b)"
|
|
413
|
+
};
|
|
414
|
+
content = null;
|
|
415
|
+
} else if (props.variant === "count") {
|
|
416
|
+
style = {
|
|
417
|
+
...baseStyle,
|
|
418
|
+
minWidth: "18px",
|
|
419
|
+
height: "18px",
|
|
420
|
+
padding: "0 4px",
|
|
421
|
+
borderRadius: "9999px",
|
|
422
|
+
fontSize: "11px",
|
|
423
|
+
fontWeight: 700,
|
|
424
|
+
color: "var(--featuredrop-count-color, #fff)",
|
|
425
|
+
backgroundColor: "var(--featuredrop-count-bg, #f59e0b)"
|
|
426
|
+
};
|
|
427
|
+
content = props.count ?? 0;
|
|
428
|
+
} else {
|
|
429
|
+
style = {
|
|
430
|
+
...baseStyle,
|
|
431
|
+
padding: "2px 6px",
|
|
432
|
+
borderRadius: "9999px",
|
|
433
|
+
fontSize: "10px",
|
|
434
|
+
fontWeight: 700,
|
|
435
|
+
color: "var(--featuredrop-color, #b45309)",
|
|
436
|
+
backgroundColor: "var(--featuredrop-bg, rgba(245,158,11,0.15))",
|
|
437
|
+
textTransform: "uppercase",
|
|
438
|
+
letterSpacing: "0.05em"
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
return h(
|
|
442
|
+
"span",
|
|
443
|
+
{
|
|
444
|
+
"data-featuredrop": props.variant,
|
|
445
|
+
style
|
|
446
|
+
},
|
|
447
|
+
content ?? void 0
|
|
448
|
+
);
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
var dynamicRequire = createRequire(import.meta.url);
|
|
453
|
+
var cachedMarked = null;
|
|
454
|
+
var cachedShiki = null;
|
|
455
|
+
function optionalRequire(name) {
|
|
456
|
+
try {
|
|
457
|
+
return dynamicRequire(name);
|
|
458
|
+
} catch (error) {
|
|
459
|
+
if (error && typeof error === "object" && "code" in error && error.code === "MODULE_NOT_FOUND") {
|
|
460
|
+
return null;
|
|
461
|
+
}
|
|
462
|
+
return null;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
function getMarked() {
|
|
466
|
+
if (cachedMarked !== null) return cachedMarked || null;
|
|
467
|
+
cachedMarked = optionalRequire("marked") ?? false;
|
|
468
|
+
return cachedMarked || null;
|
|
469
|
+
}
|
|
470
|
+
function getShiki() {
|
|
471
|
+
if (cachedShiki !== null) return cachedShiki || null;
|
|
472
|
+
cachedShiki = optionalRequire("shiki") ?? false;
|
|
473
|
+
return cachedShiki || null;
|
|
474
|
+
}
|
|
475
|
+
function escapeHtml(value) {
|
|
476
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
477
|
+
}
|
|
478
|
+
function sanitizeUrl(url) {
|
|
479
|
+
if (!url) return null;
|
|
480
|
+
const trimmed = url.trim();
|
|
481
|
+
if (!trimmed) return null;
|
|
482
|
+
const lower = trimmed.toLowerCase();
|
|
483
|
+
if (lower.startsWith("javascript:")) return null;
|
|
484
|
+
if (lower.startsWith("data:")) return null;
|
|
485
|
+
if (lower.startsWith("vbscript:")) return null;
|
|
486
|
+
if (/['"<>\s]/.test(trimmed)) return null;
|
|
487
|
+
return trimmed;
|
|
488
|
+
}
|
|
489
|
+
function sanitizeHtml(html) {
|
|
490
|
+
return html.replace(/<script[\s\S]*?>[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?>[\s\S]*?<\/style>/gi, "").replace(/\s+on[a-z]+\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/gi, "").replace(/\s+(?:href|src|xlink:href)\s*=\s*("|')(?:javascript:|data:)[^"']*\1/gi, "");
|
|
491
|
+
}
|
|
492
|
+
function decodeAllowedEntities(html) {
|
|
493
|
+
const allowTags = [
|
|
494
|
+
"p",
|
|
495
|
+
"strong",
|
|
496
|
+
"em",
|
|
497
|
+
"a",
|
|
498
|
+
"code",
|
|
499
|
+
"pre",
|
|
500
|
+
"img",
|
|
501
|
+
"ul",
|
|
502
|
+
"ol",
|
|
503
|
+
"li",
|
|
504
|
+
"blockquote",
|
|
505
|
+
"h1",
|
|
506
|
+
"h2",
|
|
507
|
+
"h3",
|
|
508
|
+
"h4",
|
|
509
|
+
"h5",
|
|
510
|
+
"h6",
|
|
511
|
+
"br"
|
|
512
|
+
];
|
|
513
|
+
return html.replace(/<(\/?)([a-z0-9]+)([^>]*)>/gi, (match, slash, tag, rest) => {
|
|
514
|
+
if (!allowTags.includes(tag.toLowerCase())) return match;
|
|
515
|
+
const decodedRest = rest.replace(/"/g, '"').replace(/'/g, "'").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
516
|
+
return `<${slash}${tag}${decodedRest}>`;
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
function renderCodeBlock(code, language) {
|
|
520
|
+
const shiki = getShiki();
|
|
521
|
+
if (shiki?.codeToHtml) {
|
|
522
|
+
try {
|
|
523
|
+
const rendered = shiki.codeToHtml(code, { lang: language || "text", theme: "github-dark" });
|
|
524
|
+
if (typeof rendered === "string") return rendered;
|
|
525
|
+
} catch {
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
const langAttr = language ? ` class="language-${escapeHtml(language)}"` : "";
|
|
529
|
+
return `<pre><code${langAttr}>${escapeHtml(code)}</code></pre>`;
|
|
530
|
+
}
|
|
531
|
+
function inlineMarkdown(text) {
|
|
532
|
+
let result = escapeHtml(text);
|
|
533
|
+
const codeSpans = [];
|
|
534
|
+
result = result.replace(/`([^`]+)`/g, (_match, code) => {
|
|
535
|
+
const idx = codeSpans.length;
|
|
536
|
+
codeSpans.push(`<code>${escapeHtml(code)}</code>`);
|
|
537
|
+
return `\xA7\xA7CODE${idx}\xA7\xA7`;
|
|
538
|
+
});
|
|
539
|
+
result = result.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (_match, alt, url) => {
|
|
540
|
+
const safeUrl = sanitizeUrl(url);
|
|
541
|
+
const safeAlt = escapeHtml(alt ?? "");
|
|
542
|
+
if (!safeUrl) return safeAlt;
|
|
543
|
+
return `<img src="${escapeHtml(safeUrl)}" alt="${safeAlt}" />`;
|
|
544
|
+
});
|
|
545
|
+
result = result.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_match, label, url) => {
|
|
546
|
+
const safeUrl = sanitizeUrl(url);
|
|
547
|
+
const safeLabel = escapeHtml(label ?? "");
|
|
548
|
+
if (!safeUrl) return safeLabel;
|
|
549
|
+
return `<a href="${escapeHtml(safeUrl)}" target="_blank" rel="noopener noreferrer">${safeLabel}</a>`;
|
|
550
|
+
});
|
|
551
|
+
result = result.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
|
|
552
|
+
result = result.replace(/\*([^*]+)\*/g, "<em>$1</em>");
|
|
553
|
+
result = result.replace(/§§CODE(\d+)§§/g, (_m, idx) => codeSpans[Number(idx)] ?? "");
|
|
554
|
+
return result;
|
|
555
|
+
}
|
|
556
|
+
function fallbackParse(markdown) {
|
|
557
|
+
const lines = markdown.split(/\r?\n/);
|
|
558
|
+
const blocks = [];
|
|
559
|
+
let listBuffer = null;
|
|
560
|
+
let quoteBuffer = null;
|
|
561
|
+
let inCodeBlock = false;
|
|
562
|
+
let codeLang;
|
|
563
|
+
let codeLines = [];
|
|
564
|
+
const flushList = () => {
|
|
565
|
+
if (!listBuffer) return;
|
|
566
|
+
blocks.push(`<ul>${listBuffer.map((item) => `<li>${item}</li>`).join("")}</ul>`);
|
|
567
|
+
listBuffer = null;
|
|
568
|
+
};
|
|
569
|
+
const flushQuote = () => {
|
|
570
|
+
if (!quoteBuffer) return;
|
|
571
|
+
const content = quoteBuffer.map((line) => inlineMarkdown(line.trim())).join("<br>");
|
|
572
|
+
blocks.push(`<blockquote>${content}</blockquote>`);
|
|
573
|
+
quoteBuffer = null;
|
|
574
|
+
};
|
|
575
|
+
const flushCode = () => {
|
|
576
|
+
if (!inCodeBlock) return;
|
|
577
|
+
blocks.push(renderCodeBlock(codeLines.join("\n"), codeLang));
|
|
578
|
+
codeLines = [];
|
|
579
|
+
codeLang = void 0;
|
|
580
|
+
inCodeBlock = false;
|
|
581
|
+
};
|
|
582
|
+
for (const rawLine of lines) {
|
|
583
|
+
const line = rawLine.replace(/\s+$/, "");
|
|
584
|
+
const codeFence = line.match(/^```(.*)$/);
|
|
585
|
+
if (codeFence) {
|
|
586
|
+
if (inCodeBlock) {
|
|
587
|
+
flushCode();
|
|
588
|
+
} else {
|
|
589
|
+
flushList();
|
|
590
|
+
flushQuote();
|
|
591
|
+
inCodeBlock = true;
|
|
592
|
+
codeLang = codeFence[1]?.trim() || void 0;
|
|
593
|
+
codeLines = [];
|
|
594
|
+
}
|
|
595
|
+
continue;
|
|
596
|
+
}
|
|
597
|
+
if (inCodeBlock) {
|
|
598
|
+
codeLines.push(rawLine);
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
601
|
+
const listMatch = line.match(/^\s*[-*+]\s+(.*)$/);
|
|
602
|
+
if (listMatch) {
|
|
603
|
+
flushQuote();
|
|
604
|
+
listBuffer = listBuffer ?? [];
|
|
605
|
+
listBuffer.push(inlineMarkdown(listMatch[1].trim()));
|
|
606
|
+
continue;
|
|
607
|
+
}
|
|
608
|
+
if (listBuffer) flushList();
|
|
609
|
+
const headingMatch = line.match(/^(#{1,6})\s+(.*)$/);
|
|
610
|
+
if (headingMatch) {
|
|
611
|
+
flushQuote();
|
|
612
|
+
const level = headingMatch[1].length;
|
|
613
|
+
const content = inlineMarkdown(headingMatch[2].trim());
|
|
614
|
+
blocks.push(`<h${level}>${content}</h${level}>`);
|
|
615
|
+
continue;
|
|
616
|
+
}
|
|
617
|
+
const quoteMatch = line.match(/^>\s?(.*)$/);
|
|
618
|
+
if (quoteMatch) {
|
|
619
|
+
quoteBuffer = quoteBuffer ?? [];
|
|
620
|
+
quoteBuffer.push(quoteMatch[1]);
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
623
|
+
if (quoteBuffer) flushQuote();
|
|
624
|
+
if (!line.trim()) {
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
blocks.push(`<p>${inlineMarkdown(line.trim())}</p>`);
|
|
628
|
+
}
|
|
629
|
+
flushList();
|
|
630
|
+
flushQuote();
|
|
631
|
+
flushCode();
|
|
632
|
+
return blocks.join("\n");
|
|
633
|
+
}
|
|
634
|
+
function renderWithMarked(markdown, marked) {
|
|
635
|
+
if (!marked.parse) return null;
|
|
636
|
+
const renderer = marked.Renderer ? new marked.Renderer() : void 0;
|
|
637
|
+
if (renderer) {
|
|
638
|
+
renderer.link = (href, _title, text) => {
|
|
639
|
+
const safeUrl = sanitizeUrl(href);
|
|
640
|
+
if (!safeUrl) return escapeHtml(text);
|
|
641
|
+
return `<a href="${escapeHtml(safeUrl)}" target="_blank" rel="noopener noreferrer">${text}</a>`;
|
|
642
|
+
};
|
|
643
|
+
renderer.image = (href, _title, text) => {
|
|
644
|
+
const safeUrl = sanitizeUrl(href);
|
|
645
|
+
const safeAlt = escapeHtml(text ?? "");
|
|
646
|
+
if (!safeUrl) return safeAlt;
|
|
647
|
+
return `<img src="${escapeHtml(safeUrl)}" alt="${safeAlt}" />`;
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
const output = marked.parse(markdown, renderer ? { renderer } : void 0);
|
|
651
|
+
if (typeof output === "string") return output;
|
|
652
|
+
return output ? String(output) : null;
|
|
653
|
+
}
|
|
654
|
+
function parseDescription(markdown) {
|
|
655
|
+
if (!markdown) return "";
|
|
656
|
+
const marked = getMarked();
|
|
657
|
+
if (marked) {
|
|
658
|
+
try {
|
|
659
|
+
const rendered = renderWithMarked(markdown, marked);
|
|
660
|
+
if (rendered) {
|
|
661
|
+
const sanitized2 = sanitizeHtml(rendered);
|
|
662
|
+
const decoded2 = decodeAllowedEntities(sanitized2);
|
|
663
|
+
return sanitizeHtml(decoded2);
|
|
664
|
+
}
|
|
665
|
+
} catch {
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
if (/<[^>]+>/.test(markdown)) {
|
|
669
|
+
const sanitized2 = sanitizeHtml(markdown);
|
|
670
|
+
const decoded2 = decodeAllowedEntities(sanitized2);
|
|
671
|
+
return sanitizeHtml(decoded2);
|
|
672
|
+
}
|
|
673
|
+
const fallback = fallbackParse(markdown);
|
|
674
|
+
const sanitized = sanitizeHtml(fallback);
|
|
675
|
+
const decoded = decodeAllowedEntities(sanitized);
|
|
676
|
+
return sanitizeHtml(decoded);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// src/vue/components/changelog-widget.ts
|
|
680
|
+
var ChangelogWidget = defineComponent({
|
|
681
|
+
name: "ChangelogWidget",
|
|
682
|
+
props: {
|
|
683
|
+
title: {
|
|
684
|
+
type: String,
|
|
685
|
+
default: "What's New"
|
|
686
|
+
},
|
|
687
|
+
triggerLabel: {
|
|
688
|
+
type: String,
|
|
689
|
+
default: "What's New"
|
|
690
|
+
},
|
|
691
|
+
showCount: {
|
|
692
|
+
type: Boolean,
|
|
693
|
+
default: true
|
|
694
|
+
},
|
|
695
|
+
analytics: {
|
|
696
|
+
type: Object,
|
|
697
|
+
required: false
|
|
698
|
+
}
|
|
699
|
+
},
|
|
700
|
+
setup(props, { slots }) {
|
|
701
|
+
const { newFeaturesSorted, newCount, dismiss, dismissAll } = useFeatureDrop();
|
|
702
|
+
const isOpen = ref(false);
|
|
703
|
+
const close = () => {
|
|
704
|
+
isOpen.value = false;
|
|
705
|
+
props.analytics?.onWidgetClosed?.();
|
|
706
|
+
};
|
|
707
|
+
const open = () => {
|
|
708
|
+
isOpen.value = true;
|
|
709
|
+
props.analytics?.onWidgetOpened?.();
|
|
710
|
+
};
|
|
711
|
+
const toggle = () => {
|
|
712
|
+
if (isOpen.value) close();
|
|
713
|
+
else open();
|
|
714
|
+
};
|
|
715
|
+
const renderEntry = (feature) => {
|
|
716
|
+
if (slots.entry) {
|
|
717
|
+
return slots.entry({ feature, dismiss: () => dismiss(feature.id) });
|
|
718
|
+
}
|
|
719
|
+
return h("div", { style: { padding: "10px 0", borderBottom: "1px solid #e5e7eb" } }, [
|
|
720
|
+
h("p", { style: { margin: "0 0 4px", fontWeight: 600 } }, feature.label),
|
|
721
|
+
feature.description ? h("div", {
|
|
722
|
+
style: { margin: "0 0 6px", color: "#6b7280", fontSize: "13px", lineHeight: 1.5 },
|
|
723
|
+
innerHTML: parseDescription(feature.description)
|
|
724
|
+
}) : null,
|
|
725
|
+
h(
|
|
726
|
+
"button",
|
|
727
|
+
{
|
|
728
|
+
onClick: () => dismiss(feature.id),
|
|
729
|
+
style: {
|
|
730
|
+
border: "1px solid #e5e7eb",
|
|
731
|
+
background: "#fff",
|
|
732
|
+
borderRadius: "6px",
|
|
733
|
+
padding: "4px 8px",
|
|
734
|
+
cursor: "pointer",
|
|
735
|
+
fontSize: "12px"
|
|
736
|
+
}
|
|
737
|
+
},
|
|
738
|
+
"Dismiss"
|
|
739
|
+
)
|
|
740
|
+
]);
|
|
741
|
+
};
|
|
742
|
+
return () => {
|
|
743
|
+
if (slots.default) {
|
|
744
|
+
return slots.default({
|
|
745
|
+
isOpen: isOpen.value,
|
|
746
|
+
toggle,
|
|
747
|
+
open,
|
|
748
|
+
close,
|
|
749
|
+
features: newFeaturesSorted.value,
|
|
750
|
+
count: newCount.value,
|
|
751
|
+
dismiss,
|
|
752
|
+
dismissAll
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
return h("div", { "data-featuredrop-widget": true }, [
|
|
756
|
+
h(
|
|
757
|
+
"button",
|
|
758
|
+
{
|
|
759
|
+
onClick: toggle,
|
|
760
|
+
style: {
|
|
761
|
+
display: "inline-flex",
|
|
762
|
+
alignItems: "center",
|
|
763
|
+
gap: "8px",
|
|
764
|
+
border: "1px solid #d1d5db",
|
|
765
|
+
background: "#fff",
|
|
766
|
+
borderRadius: "10px",
|
|
767
|
+
padding: "8px 12px",
|
|
768
|
+
cursor: "pointer"
|
|
769
|
+
}
|
|
770
|
+
},
|
|
771
|
+
[
|
|
772
|
+
props.triggerLabel,
|
|
773
|
+
props.showCount && newCount.value > 0 ? h(
|
|
774
|
+
"span",
|
|
775
|
+
{
|
|
776
|
+
style: {
|
|
777
|
+
display: "inline-flex",
|
|
778
|
+
minWidth: "18px",
|
|
779
|
+
height: "18px",
|
|
780
|
+
borderRadius: "999px",
|
|
781
|
+
background: "#f59e0b",
|
|
782
|
+
color: "#fff",
|
|
783
|
+
alignItems: "center",
|
|
784
|
+
justifyContent: "center",
|
|
785
|
+
fontSize: "11px",
|
|
786
|
+
fontWeight: 700,
|
|
787
|
+
padding: "0 4px"
|
|
788
|
+
}
|
|
789
|
+
},
|
|
790
|
+
String(newCount.value)
|
|
791
|
+
) : null
|
|
792
|
+
]
|
|
793
|
+
),
|
|
794
|
+
isOpen.value ? h(
|
|
795
|
+
"div",
|
|
796
|
+
{
|
|
797
|
+
role: "dialog",
|
|
798
|
+
style: {
|
|
799
|
+
marginTop: "8px",
|
|
800
|
+
border: "1px solid #e5e7eb",
|
|
801
|
+
borderRadius: "10px",
|
|
802
|
+
background: "#fff",
|
|
803
|
+
padding: "12px",
|
|
804
|
+
minWidth: "300px"
|
|
805
|
+
}
|
|
806
|
+
},
|
|
807
|
+
[
|
|
808
|
+
h("div", { style: { display: "flex", justifyContent: "space-between", marginBottom: "8px" } }, [
|
|
809
|
+
h("strong", props.title),
|
|
810
|
+
h("button", { onClick: close, style: { border: "none", background: "transparent", cursor: "pointer" } }, "x")
|
|
811
|
+
]),
|
|
812
|
+
newFeaturesSorted.value.length === 0 ? h("p", { style: { margin: 0, color: "#6b7280" } }, "You're all caught up!") : newFeaturesSorted.value.map((feature) => renderEntry(feature)),
|
|
813
|
+
newFeaturesSorted.value.length > 0 ? h(
|
|
814
|
+
"button",
|
|
815
|
+
{
|
|
816
|
+
onClick: () => void dismissAll(),
|
|
817
|
+
style: {
|
|
818
|
+
marginTop: "10px",
|
|
819
|
+
border: "none",
|
|
820
|
+
background: "transparent",
|
|
821
|
+
color: "#2563eb",
|
|
822
|
+
cursor: "pointer",
|
|
823
|
+
padding: 0
|
|
824
|
+
}
|
|
825
|
+
},
|
|
826
|
+
"Mark all as read"
|
|
827
|
+
) : null
|
|
828
|
+
]
|
|
829
|
+
) : null
|
|
830
|
+
]);
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
});
|
|
834
|
+
var Spotlight = defineComponent({
|
|
835
|
+
name: "Spotlight",
|
|
836
|
+
props: {
|
|
837
|
+
featureId: {
|
|
838
|
+
type: String,
|
|
839
|
+
required: true
|
|
840
|
+
},
|
|
841
|
+
label: {
|
|
842
|
+
type: String,
|
|
843
|
+
default: "New feature"
|
|
844
|
+
}
|
|
845
|
+
},
|
|
846
|
+
setup(props, { slots }) {
|
|
847
|
+
const { newFeatures, dismiss } = useFeatureDrop();
|
|
848
|
+
const open = ref(false);
|
|
849
|
+
const feature = computed(() => newFeatures.value.find((f) => f.id === props.featureId));
|
|
850
|
+
return () => {
|
|
851
|
+
if (slots.default) {
|
|
852
|
+
return slots.default({
|
|
853
|
+
feature: feature.value,
|
|
854
|
+
isActive: !!feature.value,
|
|
855
|
+
isTooltipOpen: open.value,
|
|
856
|
+
openTooltip: () => {
|
|
857
|
+
open.value = true;
|
|
858
|
+
},
|
|
859
|
+
closeTooltip: () => {
|
|
860
|
+
open.value = false;
|
|
861
|
+
},
|
|
862
|
+
dismiss: () => dismiss(props.featureId)
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
if (!feature.value) return null;
|
|
866
|
+
return h("div", { "data-featuredrop-spotlight": true, style: { display: "inline-block", position: "relative" } }, [
|
|
867
|
+
h(
|
|
868
|
+
"button",
|
|
869
|
+
{
|
|
870
|
+
onClick: () => {
|
|
871
|
+
open.value = !open.value;
|
|
872
|
+
},
|
|
873
|
+
style: {
|
|
874
|
+
width: "20px",
|
|
875
|
+
height: "20px",
|
|
876
|
+
borderRadius: "9999px",
|
|
877
|
+
border: "2px solid #f59e0b",
|
|
878
|
+
background: "#fff",
|
|
879
|
+
cursor: "pointer"
|
|
880
|
+
},
|
|
881
|
+
"aria-label": props.label
|
|
882
|
+
},
|
|
883
|
+
""
|
|
884
|
+
),
|
|
885
|
+
open.value ? h(
|
|
886
|
+
"div",
|
|
887
|
+
{
|
|
888
|
+
style: {
|
|
889
|
+
position: "absolute",
|
|
890
|
+
top: "24px",
|
|
891
|
+
left: 0,
|
|
892
|
+
border: "1px solid #e5e7eb",
|
|
893
|
+
borderRadius: "10px",
|
|
894
|
+
background: "#fff",
|
|
895
|
+
padding: "10px",
|
|
896
|
+
width: "240px",
|
|
897
|
+
zIndex: 30
|
|
898
|
+
}
|
|
899
|
+
},
|
|
900
|
+
[
|
|
901
|
+
h("p", { style: { margin: "0 0 4px", fontWeight: 600 } }, feature.value.label),
|
|
902
|
+
feature.value.description ? h("div", {
|
|
903
|
+
style: { marginBottom: "8px", color: "#6b7280", fontSize: "13px" },
|
|
904
|
+
innerHTML: parseDescription(feature.value.description)
|
|
905
|
+
}) : null,
|
|
906
|
+
h(
|
|
907
|
+
"button",
|
|
908
|
+
{
|
|
909
|
+
onClick: () => {
|
|
910
|
+
dismiss(props.featureId);
|
|
911
|
+
open.value = false;
|
|
912
|
+
},
|
|
913
|
+
style: {
|
|
914
|
+
border: "1px solid #e5e7eb",
|
|
915
|
+
background: "#fff",
|
|
916
|
+
borderRadius: "6px",
|
|
917
|
+
padding: "4px 8px",
|
|
918
|
+
cursor: "pointer"
|
|
919
|
+
}
|
|
920
|
+
},
|
|
921
|
+
"Got it"
|
|
922
|
+
)
|
|
923
|
+
]
|
|
924
|
+
) : null
|
|
925
|
+
]);
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
});
|
|
929
|
+
var Banner = defineComponent({
|
|
930
|
+
name: "Banner",
|
|
931
|
+
props: {
|
|
932
|
+
featureId: {
|
|
933
|
+
type: String,
|
|
934
|
+
required: true
|
|
935
|
+
},
|
|
936
|
+
dismissible: {
|
|
937
|
+
type: Boolean,
|
|
938
|
+
default: true
|
|
939
|
+
},
|
|
940
|
+
analytics: {
|
|
941
|
+
type: Object,
|
|
942
|
+
required: false
|
|
943
|
+
}
|
|
944
|
+
},
|
|
945
|
+
setup(props, { slots }) {
|
|
946
|
+
const { newFeatures, dismiss } = useFeatureDrop();
|
|
947
|
+
const feature = computed(() => newFeatures.value.find((f) => f.id === props.featureId));
|
|
948
|
+
return () => {
|
|
949
|
+
if (slots.default) {
|
|
950
|
+
return slots.default({
|
|
951
|
+
feature: feature.value,
|
|
952
|
+
isActive: !!feature.value,
|
|
953
|
+
dismiss: () => dismiss(props.featureId)
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
if (!feature.value) return null;
|
|
957
|
+
return h(
|
|
958
|
+
"div",
|
|
959
|
+
{
|
|
960
|
+
"data-featuredrop-banner": true,
|
|
961
|
+
style: {
|
|
962
|
+
display: "flex",
|
|
963
|
+
alignItems: "center",
|
|
964
|
+
justifyContent: "space-between",
|
|
965
|
+
gap: "8px",
|
|
966
|
+
border: "1px solid #fbbf24",
|
|
967
|
+
background: "#fffbeb",
|
|
968
|
+
color: "#92400e",
|
|
969
|
+
borderRadius: "10px",
|
|
970
|
+
padding: "10px 12px"
|
|
971
|
+
}
|
|
972
|
+
},
|
|
973
|
+
[
|
|
974
|
+
h("div", [
|
|
975
|
+
h("strong", feature.value.label),
|
|
976
|
+
feature.value.description ? h("span", {
|
|
977
|
+
style: { marginLeft: "6px" },
|
|
978
|
+
innerHTML: parseDescription(feature.value.description)
|
|
979
|
+
}) : null
|
|
980
|
+
]),
|
|
981
|
+
props.dismissible ? h(
|
|
982
|
+
"button",
|
|
983
|
+
{
|
|
984
|
+
onClick: () => {
|
|
985
|
+
dismiss(props.featureId);
|
|
986
|
+
props.analytics?.onFeatureDismissed?.(feature.value);
|
|
987
|
+
},
|
|
988
|
+
style: {
|
|
989
|
+
border: "none",
|
|
990
|
+
background: "transparent",
|
|
991
|
+
cursor: "pointer",
|
|
992
|
+
fontSize: "16px",
|
|
993
|
+
lineHeight: 1
|
|
994
|
+
}
|
|
995
|
+
},
|
|
996
|
+
"x"
|
|
997
|
+
) : null
|
|
998
|
+
]
|
|
999
|
+
);
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
});
|
|
1003
|
+
var Toast = defineComponent({
|
|
1004
|
+
name: "Toast",
|
|
1005
|
+
props: {
|
|
1006
|
+
maxVisible: {
|
|
1007
|
+
type: Number,
|
|
1008
|
+
default: 3
|
|
1009
|
+
}
|
|
1010
|
+
},
|
|
1011
|
+
setup(props, { slots }) {
|
|
1012
|
+
const { newFeaturesSorted, dismiss } = useFeatureDrop();
|
|
1013
|
+
const visible = computed(() => newFeaturesSorted.value.slice(0, props.maxVisible));
|
|
1014
|
+
return () => {
|
|
1015
|
+
if (slots.default) {
|
|
1016
|
+
return slots.default({
|
|
1017
|
+
toasts: visible.value,
|
|
1018
|
+
dismiss
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1021
|
+
if (visible.value.length === 0) return null;
|
|
1022
|
+
return h(
|
|
1023
|
+
"div",
|
|
1024
|
+
{
|
|
1025
|
+
"data-featuredrop-toast-container": true,
|
|
1026
|
+
style: {
|
|
1027
|
+
position: "fixed",
|
|
1028
|
+
right: "16px",
|
|
1029
|
+
bottom: "16px",
|
|
1030
|
+
display: "flex",
|
|
1031
|
+
flexDirection: "column",
|
|
1032
|
+
gap: "8px",
|
|
1033
|
+
zIndex: 40
|
|
1034
|
+
}
|
|
1035
|
+
},
|
|
1036
|
+
visible.value.map(
|
|
1037
|
+
(feature) => h("div", {
|
|
1038
|
+
key: feature.id,
|
|
1039
|
+
style: {
|
|
1040
|
+
width: "320px",
|
|
1041
|
+
border: "1px solid #e5e7eb",
|
|
1042
|
+
borderRadius: "10px",
|
|
1043
|
+
background: "#fff",
|
|
1044
|
+
padding: "10px 12px",
|
|
1045
|
+
boxShadow: "0 4px 20px rgba(0,0,0,0.08)"
|
|
1046
|
+
}
|
|
1047
|
+
}, [
|
|
1048
|
+
h("div", { style: { display: "flex", justifyContent: "space-between", gap: "8px" } }, [
|
|
1049
|
+
h("strong", feature.label),
|
|
1050
|
+
h(
|
|
1051
|
+
"button",
|
|
1052
|
+
{
|
|
1053
|
+
onClick: () => dismiss(feature.id),
|
|
1054
|
+
style: { border: "none", background: "transparent", cursor: "pointer" }
|
|
1055
|
+
},
|
|
1056
|
+
"x"
|
|
1057
|
+
)
|
|
1058
|
+
]),
|
|
1059
|
+
feature.description ? h("div", {
|
|
1060
|
+
style: { marginTop: "4px", color: "#6b7280", fontSize: "13px" },
|
|
1061
|
+
innerHTML: parseDescription(feature.description)
|
|
1062
|
+
}) : null
|
|
1063
|
+
])
|
|
1064
|
+
)
|
|
1065
|
+
);
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
1068
|
+
});
|
|
1069
|
+
|
|
1070
|
+
export { Banner, ChangelogWidget, FeatureDropProvider, NewBadge, Spotlight, Toast, useFeatureDrop, useNewCount, useNewFeature, useTabNotification };
|
|
1071
|
+
//# sourceMappingURL=vue.js.map
|
|
1072
|
+
//# sourceMappingURL=vue.js.map
|