@antelopejs/dms-marketing 0.5.0 → 0.6.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 +57 -131
- package/dist/config.d.ts +10 -1
- package/dist/config.js +29 -1
- package/dist/config.js.map +1 -1
- package/dist/db/models/funnels.model.d.ts +2 -0
- package/dist/db/models/funnels.model.js +18 -0
- package/dist/db/models/funnels.model.js.map +1 -1
- package/dist/db/models/index.d.ts +1 -0
- package/dist/db/models/index.js +1 -0
- package/dist/db/models/index.js.map +1 -1
- package/dist/db/models/marketing_events.model.d.ts +1 -0
- package/dist/db/models/marketing_imports.model.d.ts +27 -0
- package/dist/db/models/marketing_imports.model.js +38 -0
- package/dist/db/models/marketing_imports.model.js.map +1 -0
- package/dist/db/models/marketing_preferences.model.d.ts +1 -0
- package/dist/db/models/marketing_sessions.model.d.ts +1 -0
- package/dist/db/models/marketing_settings.model.d.ts +1 -0
- package/dist/db/models/page_snapshots.model.d.ts +2 -0
- package/dist/db/models/page_snapshots.model.js +14 -0
- package/dist/db/models/page_snapshots.model.js.map +1 -1
- package/dist/db/models/website_statistics.model.d.ts +1 -0
- package/dist/db/models/websites.model.d.ts +2 -0
- package/dist/db/models/websites.model.js +29 -0
- package/dist/db/models/websites.model.js.map +1 -1
- package/dist/db/tables/funnels.table.d.ts +1 -0
- package/dist/db/tables/funnels.table.js +4 -0
- package/dist/db/tables/funnels.table.js.map +1 -1
- package/dist/db/tables/index.d.ts +1 -0
- package/dist/db/tables/index.js +1 -0
- package/dist/db/tables/index.js.map +1 -1
- package/dist/db/tables/marketing_imports.table.d.ts +15 -0
- package/dist/db/tables/marketing_imports.table.js +63 -0
- package/dist/db/tables/marketing_imports.table.js.map +1 -0
- package/dist/db/tables/page_snapshots.table.d.ts +1 -0
- package/dist/db/tables/page_snapshots.table.js +4 -0
- package/dist/db/tables/page_snapshots.table.js.map +1 -1
- package/dist/db/tables/websites.table.d.ts +1 -0
- package/dist/db/tables/websites.table.js +4 -0
- package/dist/db/tables/websites.table.js.map +1 -1
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/index.js.map +1 -1
- package/dist/implementations/dms-marketing/index.d.ts +5 -1
- package/dist/implementations/dms-marketing/index.js +25 -0
- package/dist/implementations/dms-marketing/index.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/routes/dashboard-tracking.d.ts +9 -0
- package/dist/routes/dashboard-tracking.js +57 -0
- package/dist/routes/dashboard-tracking.js.map +1 -0
- package/dist/routes/index.d.ts +1 -0
- package/dist/routes/index.js +1 -0
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/stats.js.map +1 -1
- package/dist/routes/websites.js +12 -25
- package/dist/routes/websites.js.map +1 -1
- package/dist/services/analytics-import.d.ts +2 -0
- package/dist/services/analytics-import.js +294 -0
- package/dist/services/analytics-import.js.map +1 -0
- package/dist/services/collect.d.ts +20 -0
- package/dist/services/collect.js +43 -17
- package/dist/services/collect.js.map +1 -1
- package/dist/services/context.d.ts +8 -3
- package/dist/services/context.js +21 -7
- package/dist/services/context.js.map +1 -1
- package/dist/services/dashboard-tracking.d.ts +25 -0
- package/dist/services/dashboard-tracking.js +85 -0
- package/dist/services/dashboard-tracking.js.map +1 -0
- package/dist/services/dashboard.js +7 -3
- package/dist/services/dashboard.js.map +1 -1
- package/dist/services/declarations.d.ts +11 -0
- package/dist/services/declarations.js +64 -0
- package/dist/services/declarations.js.map +1 -0
- package/dist/services/declared-funnel.d.ts +2 -0
- package/dist/services/declared-funnel.js +230 -0
- package/dist/services/declared-funnel.js.map +1 -0
- package/dist/services/declared-snapshot.d.ts +2 -0
- package/dist/services/declared-snapshot.js +113 -0
- package/dist/services/declared-snapshot.js.map +1 -0
- package/dist/services/declared-website.d.ts +2 -0
- package/dist/services/declared-website.js +118 -0
- package/dist/services/declared-website.js.map +1 -0
- package/dist/services/geoip.js.map +1 -1
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.js +1 -0
- package/dist/services/index.js.map +1 -1
- package/dist/services/readiness.d.ts +2 -0
- package/dist/services/readiness.js +15 -0
- package/dist/services/readiness.js.map +1 -0
- package/dist/services/sessionize.d.ts +1 -0
- package/dist/services/sessionize.js +3 -0
- package/dist/services/sessionize.js.map +1 -1
- package/dist/services/snapshots.d.ts +3 -0
- package/dist/services/snapshots.js +6 -0
- package/dist/services/snapshots.js.map +1 -1
- package/dist/services/tenant-website.js +1 -0
- package/dist/services/tenant-website.js.map +1 -1
- package/dist/services/website-report.js +4 -3
- package/dist/services/website-report.js.map +1 -1
- package/dist/services/website-rules.d.ts +8 -0
- package/dist/services/website-rules.js +34 -0
- package/dist/services/website-rules.js.map +1 -0
- package/dist/types/constants.d.ts +3 -0
- package/dist/types/constants.js +5 -2
- package/dist/types/constants.js.map +1 -1
- package/dist/types/snapshots.d.ts +55 -0
- package/dist/types/snapshots.js +8 -1
- package/dist/types/snapshots.js.map +1 -1
- package/frontend-vue/app/components/Context.vue +5 -1
- package/frontend-vue/app/components/FirstRun.vue +1 -1
- package/frontend-vue/app/components/PagePreview.vue +1 -0
- package/frontend-vue/app/components/WebsitesGrid.vue +6 -2
- package/frontend-vue/app/composables/useMarketingApi.ts +12 -0
- package/frontend-vue/app/composables/useMarketingContext.ts +2 -1
- package/frontend-vue/app/composables/useMarketingReads.ts +6 -0
- package/frontend-vue/app/constants.ts +6 -0
- package/frontend-vue/app/plugins/dashboard-tracking.client.ts +44 -0
- package/frontend-vue/app/utils/dashboard-tracker.ts +127 -0
- package/frontend-vue/app/utils/format.ts +6 -3
- package/frontend-vue/dms.frontend.ts +10 -0
- package/frontend-vue/i18n/locales/marketing-en-GB.json +2 -0
- package/frontend-vue/i18n/locales/marketing-fr-FR.json +2 -0
- package/frontend-vue/package.json +1 -1
- package/frontend-vue/tests/dashboard-tracking.test.ts +219 -0
- package/frontend-vue/tests/format.test.ts +9 -0
- package/frontend-vue/tests/marketing-reads.test.ts +23 -0
- package/frontend-vue/tests/server-parity.test.ts +5 -0
- package/frontend-vue/tests/stubs/frontend-module.ts +7 -0
- package/package.json +34 -15
- package/skills/dms-marketing/SKILL.md +107 -0
- package/static/tracker.js +90 -5
- package/frontend-vue/pnpm-workspace.yaml +0 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/types/constants.ts"],"names":[],"mappings":";;;;AAIa,QAAA,mBAAmB,GAAG,WAAW,CAAC;AAClC,QAAA,gBAAgB,GAAG,UAAU,CAAC;AAG9B,QAAA,qBAAqB,GAAG,YAAY,2BAAmB,EAAE,CAAC;AAG1D,QAAA,oBAAoB,GAAG,wCAAwC,CAAC;
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/types/constants.ts"],"names":[],"mappings":"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"}
|
|
@@ -99,4 +99,59 @@ export declare const snapshotRequestSchema: z.ZodUnion<[z.ZodObject<{
|
|
|
99
99
|
colorScheme?: "light" | "dark" | undefined;
|
|
100
100
|
}>]>;
|
|
101
101
|
export type SnapshotRequestInput = z.infer<typeof snapshotRequestSchema>;
|
|
102
|
+
export declare const snapshotDeclarationSchema: z.ZodObject<Omit<{
|
|
103
|
+
website: z.ZodString;
|
|
104
|
+
url: z.ZodString;
|
|
105
|
+
viewport: z.ZodObject<{
|
|
106
|
+
width: z.ZodNumber;
|
|
107
|
+
height: z.ZodNumber;
|
|
108
|
+
}, "strip", z.ZodTypeAny, {
|
|
109
|
+
width: number;
|
|
110
|
+
height: number;
|
|
111
|
+
}, {
|
|
112
|
+
width: number;
|
|
113
|
+
height: number;
|
|
114
|
+
}>;
|
|
115
|
+
colorScheme: z.ZodOptional<z.ZodEnum<["light", "dark"]>>;
|
|
116
|
+
token: z.ZodString;
|
|
117
|
+
}, "website" | "token"> & {
|
|
118
|
+
websiteId: z.ZodString;
|
|
119
|
+
document: z.ZodObject<{
|
|
120
|
+
width: z.ZodNumber;
|
|
121
|
+
height: z.ZodNumber;
|
|
122
|
+
}, "strip", z.ZodTypeAny, {
|
|
123
|
+
width: number;
|
|
124
|
+
height: number;
|
|
125
|
+
}, {
|
|
126
|
+
width: number;
|
|
127
|
+
height: number;
|
|
128
|
+
}>;
|
|
129
|
+
html: z.ZodString;
|
|
130
|
+
}, "strip", z.ZodTypeAny, {
|
|
131
|
+
url: string;
|
|
132
|
+
websiteId: string;
|
|
133
|
+
viewport: {
|
|
134
|
+
width: number;
|
|
135
|
+
height: number;
|
|
136
|
+
};
|
|
137
|
+
document: {
|
|
138
|
+
width: number;
|
|
139
|
+
height: number;
|
|
140
|
+
};
|
|
141
|
+
html: string;
|
|
142
|
+
colorScheme?: "light" | "dark" | undefined;
|
|
143
|
+
}, {
|
|
144
|
+
url: string;
|
|
145
|
+
websiteId: string;
|
|
146
|
+
viewport: {
|
|
147
|
+
width: number;
|
|
148
|
+
height: number;
|
|
149
|
+
};
|
|
150
|
+
document: {
|
|
151
|
+
width: number;
|
|
152
|
+
height: number;
|
|
153
|
+
};
|
|
154
|
+
html: string;
|
|
155
|
+
colorScheme?: "light" | "dark" | undefined;
|
|
156
|
+
}>;
|
|
102
157
|
export {};
|
package/dist/types/snapshots.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.snapshotRequestSchema = exports.SNAPSHOT_LAYOUTS = void 0;
|
|
3
|
+
exports.snapshotDeclarationSchema = exports.snapshotRequestSchema = exports.SNAPSHOT_LAYOUTS = void 0;
|
|
4
4
|
exports.layoutOfWidth = layoutOfWidth;
|
|
5
5
|
exports.isSnapshotLayout = isSnapshotLayout;
|
|
6
6
|
const zod_1 = require("zod");
|
|
@@ -40,4 +40,11 @@ exports.snapshotRequestSchema = zod_1.z.union([
|
|
|
40
40
|
oversizeBytes: zod_1.z.number().int().positive().max(constants_1.MAX_REPORTED_SNAPSHOT_BYTES),
|
|
41
41
|
}),
|
|
42
42
|
]);
|
|
43
|
+
exports.snapshotDeclarationSchema = snapshotPageSchema
|
|
44
|
+
.omit({ website: true, token: true })
|
|
45
|
+
.extend({
|
|
46
|
+
websiteId: zod_1.z.string().min(1),
|
|
47
|
+
document: dimensionSchema(constants_1.MAX_SNAPSHOT_DOCUMENT_PX),
|
|
48
|
+
html: zod_1.z.string().min(1).max(constants_1.MAX_SNAPSHOT_BYTES),
|
|
49
|
+
});
|
|
43
50
|
//# sourceMappingURL=snapshots.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshots.js","sourceRoot":"","sources":["../../src/types/snapshots.ts"],"names":[],"mappings":";;;AA0BA,sCAKC;AAED,4CAEC;AAnCD,6BAAwB;AACxB,2CAOqB;AAErB,MAAM,yBAAyB,GAAG,EAAE,CAAC;AAMxB,QAAA,gBAAgB,GAAG,CAAC,SAAS,EAAE,QAAQ,EAAE,OAAO,CAAU,CAAC;AAIxE,MAAM,iBAAiB,GAAmC;IACxD,OAAO,EAAE,IAAI;IACb,MAAM,EAAE,GAAG;IACX,KAAK,EAAE,CAAC;CACT,CAAC;AAEF,SAAgB,aAAa,CAAC,KAAa;IACzC,OAAO,CACL,wBAAgB,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,KAAK,IAAI,iBAAiB,CAAC,MAAM,CAAC,CAAC;QACrE,OAAO,CACR,CAAC;AACJ,CAAC;AAED,SAAgB,gBAAgB,CAAC,KAAc;IAC7C,OAAO,wBAAgB,CAAC,QAAQ,CAAC,KAAuB,CAAC,CAAC;AAC5D,CAAC;AAED,MAAM,sBAAsB,GAAG,CAAC,OAAO,EAAE,MAAM,CAAU,CAAC;AAI1D,MAAM,eAAe,GAAG,CAAC,GAAW,EAAE,EAAE,CACtC,OAAC,CAAC,MAAM,CAAC;IACP,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;IACvC,MAAM,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;CACzC,CAAC,CAAC;AAEL,MAAM,kBAAkB,GAAG,OAAC,CAAC,MAAM,CAAC;IAClC,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,iCAAqB,CAAC;IAErD,GAAG,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,0BAAc,CAAC;IAC1C,QAAQ,EAAE,eAAe,CAAC,oCAAwB,CAAC;IACnD,WAAW,EAAE,OAAC,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC,QAAQ,EAAE;IAEtD,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,yBAAyB,CAAC;CACxD,CAAC,CAAC;AAGU,QAAA,qBAAqB,GAAG,OAAC,CAAC,KAAK,CAAC;IAC3C,kBAAkB,CAAC,MAAM,CAAC;QACxB,QAAQ,EAAE,eAAe,CAAC,oCAAwB,CAAC;QACnD,IAAI,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,8BAAkB,CAAC;KAChD,CAAC;IACF,kBAAkB,CAAC,MAAM,CAAC;QACxB,aAAa,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,uCAA2B,CAAC;KAC5E,CAAC;CACH,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"snapshots.js","sourceRoot":"","sources":["../../src/types/snapshots.ts"],"names":[],"mappings":";;;AA0BA,sCAKC;AAED,4CAEC;AAnCD,6BAAwB;AACxB,2CAOqB;AAErB,MAAM,yBAAyB,GAAG,EAAE,CAAC;AAMxB,QAAA,gBAAgB,GAAG,CAAC,SAAS,EAAE,QAAQ,EAAE,OAAO,CAAU,CAAC;AAIxE,MAAM,iBAAiB,GAAmC;IACxD,OAAO,EAAE,IAAI;IACb,MAAM,EAAE,GAAG;IACX,KAAK,EAAE,CAAC;CACT,CAAC;AAEF,SAAgB,aAAa,CAAC,KAAa;IACzC,OAAO,CACL,wBAAgB,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,KAAK,IAAI,iBAAiB,CAAC,MAAM,CAAC,CAAC;QACrE,OAAO,CACR,CAAC;AACJ,CAAC;AAED,SAAgB,gBAAgB,CAAC,KAAc;IAC7C,OAAO,wBAAgB,CAAC,QAAQ,CAAC,KAAuB,CAAC,CAAC;AAC5D,CAAC;AAED,MAAM,sBAAsB,GAAG,CAAC,OAAO,EAAE,MAAM,CAAU,CAAC;AAI1D,MAAM,eAAe,GAAG,CAAC,GAAW,EAAE,EAAE,CACtC,OAAC,CAAC,MAAM,CAAC;IACP,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;IACvC,MAAM,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;CACzC,CAAC,CAAC;AAEL,MAAM,kBAAkB,GAAG,OAAC,CAAC,MAAM,CAAC;IAClC,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,iCAAqB,CAAC;IAErD,GAAG,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,0BAAc,CAAC;IAC1C,QAAQ,EAAE,eAAe,CAAC,oCAAwB,CAAC;IACnD,WAAW,EAAE,OAAC,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC,QAAQ,EAAE;IAEtD,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,yBAAyB,CAAC;CACxD,CAAC,CAAC;AAGU,QAAA,qBAAqB,GAAG,OAAC,CAAC,KAAK,CAAC;IAC3C,kBAAkB,CAAC,MAAM,CAAC;QACxB,QAAQ,EAAE,eAAe,CAAC,oCAAwB,CAAC;QACnD,IAAI,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,8BAAkB,CAAC;KAChD,CAAC;IACF,kBAAkB,CAAC,MAAM,CAAC;QACxB,aAAa,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,uCAA2B,CAAC;KAC5E,CAAC;CACH,CAAC,CAAC;AAMU,QAAA,yBAAyB,GAAG,kBAAkB;KACxD,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;KACpC,MAAM,CAAC;IACN,SAAS,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5B,QAAQ,EAAE,eAAe,CAAC,oCAAwB,CAAC;IACnD,IAAI,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,8BAAkB,CAAC;CAChD,CAAC,CAAC"}
|
|
@@ -233,6 +233,7 @@ type DisplayState = ContextWebsite['state'] | 'idle'
|
|
|
233
233
|
const STATE_TONES: Record<DisplayState, string> = {
|
|
234
234
|
live: 'bg-success',
|
|
235
235
|
idle: 'bg-(--ui-text-muted)',
|
|
236
|
+
imported: 'bg-info',
|
|
236
237
|
waiting: 'bg-warning',
|
|
237
238
|
paused: 'bg-(--ui-text-dimmed)',
|
|
238
239
|
}
|
|
@@ -250,7 +251,10 @@ function displayState(site: ContextWebsite): DisplayState {
|
|
|
250
251
|
|
|
251
252
|
function stateLine(site: ContextWebsite): string {
|
|
252
253
|
const state = displayState(site)
|
|
253
|
-
if (
|
|
254
|
+
if (
|
|
255
|
+
(state === 'live' || state === 'idle' || state === 'imported') &&
|
|
256
|
+
site.lastActivityAt
|
|
257
|
+
) {
|
|
254
258
|
return t(`page.marketing.context.${state}`, {
|
|
255
259
|
ago: formatRelativeTime(site.lastActivityAt, now.value, locale.value),
|
|
256
260
|
})
|
|
@@ -41,7 +41,7 @@ const GHOST_TILES = 3
|
|
|
41
41
|
color="neutral"
|
|
42
42
|
variant="ghost"
|
|
43
43
|
:label="t('page.marketing.first_run.how')"
|
|
44
|
-
to="https://
|
|
44
|
+
to="https://dms.antelopejs.com/docs/modules/marketing/usage/tracking"
|
|
45
45
|
target="_blank"
|
|
46
46
|
/>
|
|
47
47
|
</div>
|
|
@@ -276,6 +276,7 @@ const snapshotRead = usePageSnapshot({
|
|
|
276
276
|
path: () => props.path,
|
|
277
277
|
layout,
|
|
278
278
|
enabled: snapshotsEnabled,
|
|
279
|
+
maskText: () => props.website?.snapshotMaskText === true,
|
|
279
280
|
})
|
|
280
281
|
const isSnapshotAsked = computed(
|
|
281
282
|
() => snapshotsEnabled.value && !!props.website?._id && !!props.path,
|
|
@@ -75,8 +75,11 @@ const now = useMarketingNow(CLOCK_TICK_MS)
|
|
|
75
75
|
const readerZone = readerTimeZone()
|
|
76
76
|
|
|
77
77
|
function stateLine(card: MarketingWebsiteCard): string {
|
|
78
|
-
if (
|
|
79
|
-
|
|
78
|
+
if (
|
|
79
|
+
(card.state === 'live' || card.state === 'imported') &&
|
|
80
|
+
card.lastActivityAt
|
|
81
|
+
) {
|
|
82
|
+
return t(`page.marketing.context.${card.state}`, {
|
|
80
83
|
ago: formatRelativeTime(card.lastActivityAt, now.value, locale.value),
|
|
81
84
|
})
|
|
82
85
|
}
|
|
@@ -85,6 +88,7 @@ function stateLine(card: MarketingWebsiteCard): string {
|
|
|
85
88
|
|
|
86
89
|
const STATE_DOTS = {
|
|
87
90
|
live: 'bg-success',
|
|
91
|
+
imported: 'bg-info',
|
|
88
92
|
waiting: 'bg-warning',
|
|
89
93
|
paused: 'bg-(--ui-text-dimmed)',
|
|
90
94
|
} as const
|
|
@@ -392,6 +392,11 @@ export interface MarketingScrollDepth {
|
|
|
392
392
|
truncated: boolean
|
|
393
393
|
}
|
|
394
394
|
|
|
395
|
+
/** The website the dashboard's own pages report to (dashboardTracking). */
|
|
396
|
+
export interface MarketingDashboardTracking {
|
|
397
|
+
websiteId: string
|
|
398
|
+
}
|
|
399
|
+
|
|
395
400
|
// --- Facade -----------------------------------------------------------------
|
|
396
401
|
|
|
397
402
|
export function useMarketingApi() {
|
|
@@ -468,5 +473,12 @@ export function useMarketingApi() {
|
|
|
468
473
|
method: 'DELETE',
|
|
469
474
|
query: { id },
|
|
470
475
|
}),
|
|
476
|
+
|
|
477
|
+
// Provisions the signed-in workspace's dashboard website on first use.
|
|
478
|
+
dashboardTracking: () =>
|
|
479
|
+
$authFetch<MarketingDashboardTracking>(
|
|
480
|
+
'/api/marketing/dashboard-tracking',
|
|
481
|
+
{ method: 'POST' },
|
|
482
|
+
),
|
|
471
483
|
}
|
|
472
484
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { computed, inject, type ComputedRef, type InjectionKey } from 'vue'
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/** `imported`: data, but only from an import — nothing proves the tag collects. */
|
|
4
|
+
export type WebsiteState = 'live' | 'imported' | 'waiting' | 'paused'
|
|
4
5
|
|
|
5
6
|
export interface ContextWebsite {
|
|
6
7
|
id: string
|
|
@@ -212,6 +212,11 @@ export interface PageSnapshotRequest {
|
|
|
212
212
|
path: MaybeRefOrGetter<string>
|
|
213
213
|
layout: MaybeRefOrGetter<MarketingSnapshotLayout>
|
|
214
214
|
enabled: MaybeRefOrGetter<boolean>
|
|
215
|
+
/**
|
|
216
|
+
* The site's text mask. Changing it discards the site's captures on the
|
|
217
|
+
* backend while the request stays the same, so the capture is read again.
|
|
218
|
+
*/
|
|
219
|
+
maskText: MaybeRefOrGetter<boolean>
|
|
215
220
|
}
|
|
216
221
|
|
|
217
222
|
/** A page's capture in a layout, drawn in the browser. */
|
|
@@ -231,6 +236,7 @@ export function usePageSnapshot(
|
|
|
231
236
|
path: toValue(request.path),
|
|
232
237
|
layout: toValue(request.layout),
|
|
233
238
|
}),
|
|
239
|
+
watch: [() => toValue(request.maskText)],
|
|
234
240
|
server: false,
|
|
235
241
|
},
|
|
236
242
|
)
|
|
@@ -13,6 +13,12 @@ export const MS_PER_DAY = 86_400_000
|
|
|
13
13
|
export const MAX_WEBSITE_NAME_LENGTH = 100
|
|
14
14
|
export const MAX_WEBSITE_DOMAIN_LENGTH = 253
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Where the backend puts this module's public options (FRONTEND_CONFIG_KEY
|
|
18
|
+
* on the server): `sdk.options.public[MODULE_CONFIG_KEY]`.
|
|
19
|
+
*/
|
|
20
|
+
export const MODULE_CONFIG_KEY = 'dmsMarketing'
|
|
21
|
+
|
|
16
22
|
/** Longest period the routes read (MAX_QUERY_PERIOD_DAYS on the server). */
|
|
17
23
|
export const MAX_PERIOD_DAYS = 90
|
|
18
24
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineDmsPlugin,
|
|
3
|
+
useDmsRuntimeConfig,
|
|
4
|
+
useDmsUserSession,
|
|
5
|
+
} from '#dms/frontend-module'
|
|
6
|
+
import { useMarketingApi } from '../composables/useMarketingApi'
|
|
7
|
+
import {
|
|
8
|
+
applyDashboardTracker,
|
|
9
|
+
type DashboardTrackingOptions,
|
|
10
|
+
followDashboardSession,
|
|
11
|
+
} from '../utils/dashboard-tracker'
|
|
12
|
+
import { trackerUrl } from '../utils/snippet'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Loads the tracker on every dashboard page while someone is signed in, on
|
|
16
|
+
* the website of their workspace. Registered by `dms.frontend.ts` only when
|
|
17
|
+
* the backend enables `dashboardTracking`; client-only, since the tag is the
|
|
18
|
+
* browser's business and the server render has no session to follow.
|
|
19
|
+
*/
|
|
20
|
+
export function dashboardTrackingPlugin(options: DashboardTrackingOptions) {
|
|
21
|
+
return defineDmsPlugin(() => {
|
|
22
|
+
const { loggedIn, session } = useDmsUserSession()
|
|
23
|
+
const config = useDmsRuntimeConfig()
|
|
24
|
+
const api = useMarketingApi()
|
|
25
|
+
// The public backend URL, where client sites load the tracker from too.
|
|
26
|
+
const src = trackerUrl(
|
|
27
|
+
String(
|
|
28
|
+
(config.public as { dms?: { baseURL?: string } }).dms?.baseURL ?? '',
|
|
29
|
+
),
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
followDashboardSession({
|
|
33
|
+
workspace: () =>
|
|
34
|
+
loggedIn.value ? (session.value?.activeTenantId ?? '') : null,
|
|
35
|
+
provision: async () => (await api.dashboardTracking()).websiteId,
|
|
36
|
+
apply: (websiteId) =>
|
|
37
|
+
applyDashboardTracker(document, {
|
|
38
|
+
src,
|
|
39
|
+
websiteId,
|
|
40
|
+
excludePaths: options.excludePaths,
|
|
41
|
+
}),
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tag that measures the dashboard itself, when the backend enables
|
|
3
|
+
* `dashboardTracking`: the same tracker client sites embed, inserted by the
|
|
4
|
+
* module instead of pasted. It is kept and retargeted, never reinserted: the
|
|
5
|
+
* tracker follows its `data-website-id` (see static/tracker.js), and a
|
|
6
|
+
* second copy would only be refused.
|
|
7
|
+
*/
|
|
8
|
+
import { watch, type WatchStopHandle } from 'vue'
|
|
9
|
+
import { MODULE_CONFIG_KEY } from '../constants'
|
|
10
|
+
|
|
11
|
+
export const DASHBOARD_TRACKER_ELEMENT_ID = 'dms-marketing-dashboard-tracker'
|
|
12
|
+
|
|
13
|
+
/** What the module's public options carry when the backend turns it on. */
|
|
14
|
+
export interface DashboardTrackingOptions {
|
|
15
|
+
/** Route patterns left uncounted, the sign-in pages included. */
|
|
16
|
+
excludePaths: string[]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The dashboard tag's options among the module's public ones, or null while
|
|
21
|
+
* the backend leaves it off — the default, under which the module registers
|
|
22
|
+
* nothing for it.
|
|
23
|
+
*/
|
|
24
|
+
export function dashboardTrackingOptions(
|
|
25
|
+
publicOptions: Record<string, unknown>,
|
|
26
|
+
): DashboardTrackingOptions | null {
|
|
27
|
+
const tracking = (
|
|
28
|
+
publicOptions[MODULE_CONFIG_KEY] as
|
|
29
|
+
{ dashboardTracking?: { excludePaths?: unknown } } | undefined
|
|
30
|
+
)?.dashboardTracking
|
|
31
|
+
if (!tracking || typeof tracking !== 'object') {
|
|
32
|
+
return null
|
|
33
|
+
}
|
|
34
|
+
const excludePaths = Array.isArray(tracking.excludePaths)
|
|
35
|
+
? tracking.excludePaths.filter(
|
|
36
|
+
(path): path is string => typeof path === 'string',
|
|
37
|
+
)
|
|
38
|
+
: []
|
|
39
|
+
return { excludePaths }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface DashboardTrackerTag {
|
|
43
|
+
src: string
|
|
44
|
+
/** Null pauses collection without removing the tracker. */
|
|
45
|
+
websiteId: string | null
|
|
46
|
+
excludePaths: string[]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** The DOM this helper touches, so it runs against a stand-in in tests. */
|
|
50
|
+
type TagDocument = Pick<Document, 'getElementById' | 'createElement'> & {
|
|
51
|
+
head: Pick<HTMLHeadElement, 'appendChild'>
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Points the tag at `websiteId`, inserting it the first time there is one.
|
|
56
|
+
* A null id before any insertion inserts nothing: a dashboard nobody signed
|
|
57
|
+
* into never loads the tracker.
|
|
58
|
+
*/
|
|
59
|
+
export function applyDashboardTracker(
|
|
60
|
+
doc: TagDocument,
|
|
61
|
+
tag: DashboardTrackerTag,
|
|
62
|
+
): void {
|
|
63
|
+
const existing = doc.getElementById(DASHBOARD_TRACKER_ELEMENT_ID)
|
|
64
|
+
if (existing) {
|
|
65
|
+
if (tag.websiteId) {
|
|
66
|
+
existing.setAttribute('data-website-id', tag.websiteId)
|
|
67
|
+
} else {
|
|
68
|
+
existing.removeAttribute('data-website-id')
|
|
69
|
+
}
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
if (!tag.websiteId) {
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
const script = doc.createElement('script')
|
|
76
|
+
script.id = DASHBOARD_TRACKER_ELEMENT_ID
|
|
77
|
+
script.async = true
|
|
78
|
+
script.src = tag.src
|
|
79
|
+
script.setAttribute('data-website-id', tag.websiteId)
|
|
80
|
+
if (tag.excludePaths.length > 0) {
|
|
81
|
+
script.setAttribute('data-exclude-paths', tag.excludePaths.join(','))
|
|
82
|
+
}
|
|
83
|
+
doc.head.appendChild(script)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface DashboardSessionBinding {
|
|
87
|
+
/** The signed-in workspace (tenant id, '' when none is named), or null
|
|
88
|
+
* while nobody is signed in. */
|
|
89
|
+
workspace: () => string | null
|
|
90
|
+
/** The website of the signed-in workspace, provisioned on first use. */
|
|
91
|
+
provision: () => Promise<string>
|
|
92
|
+
apply: (websiteId: string | null) => void
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Keeps the tag on the website of whoever is signed in. Any change pauses it
|
|
97
|
+
* synchronously — before the navigation that follows a sign-out or a
|
|
98
|
+
* workspace switch — so no page of one workspace reaches another's site;
|
|
99
|
+
* the new website is set once the backend answers, if nothing changed
|
|
100
|
+
* meanwhile. A failure leaves it paused: measuring the dashboard must never
|
|
101
|
+
* get in its way.
|
|
102
|
+
*/
|
|
103
|
+
export function followDashboardSession(
|
|
104
|
+
binding: DashboardSessionBinding,
|
|
105
|
+
): WatchStopHandle {
|
|
106
|
+
let generation = 0
|
|
107
|
+
return watch(
|
|
108
|
+
binding.workspace,
|
|
109
|
+
async (workspace) => {
|
|
110
|
+
const current = ++generation
|
|
111
|
+
binding.apply(null)
|
|
112
|
+
if (workspace === null) {
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
let websiteId: string
|
|
116
|
+
try {
|
|
117
|
+
websiteId = await binding.provision()
|
|
118
|
+
} catch {
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
if (current === generation) {
|
|
122
|
+
binding.apply(websiteId)
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
{ immediate: true, flush: 'sync' },
|
|
126
|
+
)
|
|
127
|
+
}
|
|
@@ -144,12 +144,15 @@ export function formatShortDate(
|
|
|
144
144
|
}).format(at)
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
/**
|
|
147
|
+
/**
|
|
148
|
+
* "AS" for "Acme shop": the avatar of a website. Built from letters and
|
|
149
|
+
* digits only, so a dash or a dot in the name never becomes an initial.
|
|
150
|
+
*/
|
|
148
151
|
export function initials(name: string): string {
|
|
149
|
-
const words = name.
|
|
152
|
+
const words = name.split(/[^\p{L}\p{N}]+/u).filter(Boolean)
|
|
150
153
|
const letters =
|
|
151
154
|
words.length > 1
|
|
152
|
-
? words.slice(0, INITIALS_LENGTH).map((word) => word[0])
|
|
155
|
+
? words.slice(0, INITIALS_LENGTH).map((word) => [...word][0])
|
|
153
156
|
: [...(words[0] ?? '?')].slice(0, INITIALS_LENGTH)
|
|
154
157
|
return letters.join('').toUpperCase()
|
|
155
158
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { defineAsyncComponent, type Component } from 'vue'
|
|
2
2
|
import type { DmsFrontendModule } from '#dms/frontend-module'
|
|
3
|
+
import { dashboardTrackingPlugin } from './app/plugins/dashboard-tracking.client'
|
|
3
4
|
import funnelStepsDataType from './app/plugins/funnel-steps-data-type.client'
|
|
5
|
+
import { dashboardTrackingOptions } from './app/utils/dashboard-tracker'
|
|
4
6
|
|
|
5
7
|
interface VueModule {
|
|
6
8
|
default: Component
|
|
@@ -30,6 +32,14 @@ const frontendModule: DmsFrontendModule = {
|
|
|
30
32
|
// Client-only: the display it registers carries callables, which the SSR
|
|
31
33
|
// payload strips.
|
|
32
34
|
sdk.registerPlugin(funnelStepsDataType, { clientOnly: true })
|
|
35
|
+
// Only when the backend enables `dashboardTracking`: off, the dashboard
|
|
36
|
+
// loads no tracker and calls nothing.
|
|
37
|
+
const tracking = dashboardTrackingOptions(sdk.options.public)
|
|
38
|
+
if (tracking) {
|
|
39
|
+
sdk.registerPlugin(dashboardTrackingPlugin(tracking), {
|
|
40
|
+
clientOnly: true,
|
|
41
|
+
})
|
|
42
|
+
}
|
|
33
43
|
},
|
|
34
44
|
}
|
|
35
45
|
|
|
@@ -386,6 +386,7 @@
|
|
|
386
386
|
"title": "Couldn't load your websites"
|
|
387
387
|
},
|
|
388
388
|
"idle": "Last event {ago}",
|
|
389
|
+
"imported": "Imported history · last event {ago}",
|
|
389
390
|
"label": "Website and period",
|
|
390
391
|
"live": "Live · last event {ago}",
|
|
391
392
|
"no_website": "No website yet",
|
|
@@ -399,6 +400,7 @@
|
|
|
399
400
|
"range_cut": "Only the last {days} days of this range are shown.",
|
|
400
401
|
"refresh": "Refresh",
|
|
401
402
|
"state": {
|
|
403
|
+
"imported": "Imported history only",
|
|
402
404
|
"live": "Live",
|
|
403
405
|
"paused": "Tracking paused for this website",
|
|
404
406
|
"waiting": "Waiting for the first event"
|
|
@@ -386,6 +386,7 @@
|
|
|
386
386
|
"title": "Impossible de charger vos sites"
|
|
387
387
|
},
|
|
388
388
|
"idle": "Dernier événement {ago}",
|
|
389
|
+
"imported": "Historique importé · dernier événement {ago}",
|
|
389
390
|
"label": "Site et période",
|
|
390
391
|
"live": "En direct · dernier événement {ago}",
|
|
391
392
|
"no_website": "Aucun site pour l'instant",
|
|
@@ -399,6 +400,7 @@
|
|
|
399
400
|
"range_cut": "Seuls les {days} derniers jours de cette plage sont affichés.",
|
|
400
401
|
"refresh": "Actualiser",
|
|
401
402
|
"state": {
|
|
403
|
+
"imported": "Historique importé uniquement",
|
|
402
404
|
"live": "En direct",
|
|
403
405
|
"paused": "Suivi en pause pour ce site",
|
|
404
406
|
"waiting": "En attente du premier événement"
|