@antelopejs/dms-marketing 0.6.1 → 0.7.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/config.d.ts +3 -0
- package/dist/config.js +11 -0
- package/dist/config.js.map +1 -1
- package/dist/data-api/websites.d.ts +10 -4
- package/dist/data-api/websites.js +79 -3
- package/dist/data-api/websites.js.map +1 -1
- package/dist/db/models/page_snapshots.model.d.ts +2 -0
- package/dist/db/models/page_snapshots.model.js +6 -0
- package/dist/db/models/page_snapshots.model.js.map +1 -1
- package/dist/db/models/websites.model.d.ts +1 -0
- package/dist/db/models/websites.model.js +9 -0
- package/dist/db/models/websites.model.js.map +1 -1
- 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 +2 -0
- package/dist/db/tables/websites.table.js +4 -0
- package/dist/db/tables/websites.table.js.map +1 -1
- package/dist/frontend-module.d.ts +6 -0
- package/dist/frontend-module.js +46 -0
- package/dist/frontend-module.js.map +1 -0
- package/dist/index.js +3 -11
- package/dist/index.js.map +1 -1
- package/dist/pages/acquisition/page.d.ts +1 -0
- package/dist/pages/acquisition/page.js +9 -4
- package/dist/pages/acquisition/page.js.map +1 -1
- package/dist/pages/blocks.d.ts +11 -2
- package/dist/pages/blocks.js +43 -5
- package/dist/pages/blocks.js.map +1 -1
- package/dist/pages/funnel/page.d.ts +1 -0
- package/dist/pages/funnel/page.js +1 -0
- package/dist/pages/funnel/page.js.map +1 -1
- package/dist/pages/funnels/page.d.ts +1 -0
- package/dist/pages/funnels/page.js +4 -2
- package/dist/pages/funnels/page.js.map +1 -1
- package/dist/pages/overview/page.d.ts +1 -0
- package/dist/pages/overview/page.js +5 -6
- package/dist/pages/overview/page.js.map +1 -1
- package/dist/pages/pages/page.d.ts +1 -0
- package/dist/pages/pages/page.js +1 -0
- package/dist/pages/pages/page.js.map +1 -1
- package/dist/pages/settings/form.js +1 -0
- package/dist/pages/settings/form.js.map +1 -1
- package/dist/pages/websites/page.d.ts +2 -3
- package/dist/pages/websites/page.js +63 -5
- package/dist/pages/websites/page.js.map +1 -1
- package/dist/routes/dashboard-tracking.js +5 -1
- package/dist/routes/dashboard-tracking.js.map +1 -1
- package/dist/routes/funnels.d.ts +2 -0
- package/dist/routes/funnels.js +6 -5
- package/dist/routes/funnels.js.map +1 -1
- package/dist/routes/public-endpoints.js +1 -1
- package/dist/routes/public-endpoints.js.map +1 -1
- package/dist/routes/snapshot.d.ts +1 -1
- package/dist/routes/snapshot.js +30 -13
- package/dist/routes/snapshot.js.map +1 -1
- package/dist/routes/stats.d.ts +10 -4
- package/dist/routes/stats.js +77 -23
- package/dist/routes/stats.js.map +1 -1
- package/dist/routes/websites.d.ts +5 -1
- package/dist/routes/websites.js +31 -13
- package/dist/routes/websites.js.map +1 -1
- package/dist/services/acquisition-report.d.ts +1 -0
- package/dist/services/acquisition-report.js +1 -0
- package/dist/services/acquisition-report.js.map +1 -1
- package/dist/services/collect.js +7 -2
- package/dist/services/collect.js.map +1 -1
- package/dist/services/dashboard-tracking.d.ts +7 -14
- package/dist/services/dashboard-tracking.js +42 -26
- package/dist/services/dashboard-tracking.js.map +1 -1
- package/dist/services/funnels-table.d.ts +3 -2
- package/dist/services/funnels-table.js +9 -9
- package/dist/services/funnels-table.js.map +1 -1
- package/dist/services/heatmap.d.ts +6 -0
- package/dist/services/heatmap.js +82 -17
- package/dist/services/heatmap.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/prune.js +6 -1
- package/dist/services/prune.js.map +1 -1
- package/dist/services/realtime.d.ts +3 -0
- package/dist/services/realtime.js +49 -0
- package/dist/services/realtime.js.map +1 -0
- package/dist/services/request-body.d.ts +1 -0
- package/dist/services/request-body.js +18 -0
- package/dist/services/request-body.js.map +1 -1
- package/dist/services/sessionize.d.ts +1 -0
- package/dist/services/sessionize.js +9 -0
- package/dist/services/sessionize.js.map +1 -1
- package/dist/services/snapshots.d.ts +28 -1
- package/dist/services/snapshots.js +147 -26
- package/dist/services/snapshots.js.map +1 -1
- package/dist/services/tenant-website.d.ts +11 -0
- package/dist/services/tenant-website.js +27 -5
- package/dist/services/tenant-website.js.map +1 -1
- package/dist/services/website-report.d.ts +5 -0
- package/dist/services/website-report.js +13 -1
- package/dist/services/website-report.js.map +1 -1
- package/dist/services/workspace-scope.d.ts +31 -0
- package/dist/services/workspace-scope.js +72 -0
- package/dist/services/workspace-scope.js.map +1 -0
- package/dist/types/constants.d.ts +7 -0
- package/dist/types/constants.js +8 -1
- package/dist/types/constants.js.map +1 -1
- package/dist/types/events.d.ts +25 -25
- package/dist/types/snapshots.d.ts +29 -29
- package/dist/types/snapshots.js +1 -1
- package/dist/types/snapshots.js.map +1 -1
- package/dist/utils/funnel-steps-type.d.ts +13 -13
- package/frontend-vue/app/components/ChannelsCard.vue +19 -2
- package/frontend-vue/app/components/Context.vue +50 -376
- package/frontend-vue/app/components/FunnelBuilder.vue +28 -22
- package/frontend-vue/app/components/FunnelFigure.vue +15 -2
- package/frontend-vue/app/components/FunnelReport.vue +3 -0
- package/frontend-vue/app/components/FunnelTemplates.vue +15 -13
- package/frontend-vue/app/components/InstallGuide.vue +1 -1
- package/frontend-vue/app/components/PagePreview.vue +330 -74
- package/frontend-vue/app/components/PagesExplorer.vue +230 -85
- package/frontend-vue/app/components/TopListTabs.vue +51 -18
- package/frontend-vue/app/components/WebsiteCard.vue +394 -0
- package/frontend-vue/app/components/WebsiteCreate.vue +159 -0
- package/frontend-vue/app/components/WebsitePicker.vue +210 -0
- package/frontend-vue/app/components/WebsiteSettings.vue +539 -0
- package/frontend-vue/app/components/WorkspaceScope.vue +119 -0
- package/frontend-vue/app/composables/useContextWebsite.ts +105 -0
- package/frontend-vue/app/composables/useCopyDraft.ts +47 -0
- package/frontend-vue/app/composables/useMarketingApi.ts +92 -4
- package/frontend-vue/app/composables/useMarketingReads.ts +32 -23
- package/frontend-vue/app/composables/useMarketingRoutes.ts +25 -2
- package/frontend-vue/app/composables/useRecordCrumb.ts +17 -0
- package/frontend-vue/app/composables/useSnapshotFrame.ts +35 -1
- package/frontend-vue/app/utils/anchor-drift.ts +58 -0
- package/frontend-vue/app/utils/drift-dismissals.ts +20 -0
- package/frontend-vue/app/utils/fill-height.ts +141 -0
- package/frontend-vue/app/utils/format.ts +21 -2
- package/frontend-vue/app/utils/funnel-draft.ts +22 -0
- package/frontend-vue/app/utils/heatmap.ts +17 -0
- package/frontend-vue/app/utils/page-snapshot.ts +75 -0
- package/frontend-vue/app/utils/snapshot-options.ts +148 -0
- package/frontend-vue/app/utils/snapshotScrollRoot.ts +93 -0
- package/frontend-vue/app/utils/website-card.ts +93 -0
- package/frontend-vue/app/utils/website-purge.ts +95 -0
- package/frontend-vue/app/utils/website-settings.ts +121 -0
- package/frontend-vue/app/utils/website-state.ts +93 -0
- package/frontend-vue/app/utils/workspace-scope.ts +51 -0
- package/frontend-vue/i18n/locales/marketing-en-GB.json +112 -36
- package/frontend-vue/i18n/locales/marketing-fr-FR.json +112 -36
- package/frontend-vue/package.json +1 -1
- package/frontend-vue/tests/anchor-drift.test.ts +59 -0
- package/frontend-vue/tests/build-flags.test.ts +38 -0
- package/frontend-vue/tests/context-bar.test.ts +226 -0
- package/frontend-vue/tests/copy-draft.test.ts +64 -0
- package/frontend-vue/tests/drift-dismissals.test.ts +12 -0
- package/frontend-vue/tests/fill-height.test.ts +110 -0
- package/frontend-vue/tests/format.test.ts +14 -0
- package/frontend-vue/tests/funnel-draft.test.ts +37 -0
- package/frontend-vue/tests/heatmap.test.ts +25 -0
- package/frontend-vue/tests/install-guide.test.ts +25 -0
- package/frontend-vue/tests/marketing-reads.test.ts +20 -6
- package/frontend-vue/tests/page-snapshot.test.ts +62 -0
- package/frontend-vue/tests/record-crumb.test.ts +43 -0
- package/frontend-vue/tests/registration.test.ts +4 -1
- package/frontend-vue/tests/routes.test.ts +21 -0
- package/frontend-vue/tests/save-funnel.test.ts +47 -0
- package/frontend-vue/tests/server-parity.test.ts +3 -0
- package/frontend-vue/tests/snapshot-options.test.ts +148 -0
- package/frontend-vue/tests/snapshot-scroll-root.test.ts +84 -0
- package/frontend-vue/tests/stubs/frontend-module.ts +5 -1
- package/frontend-vue/tests/top-list-tabs.test.ts +158 -0
- package/frontend-vue/tests/website-card.test.ts +119 -0
- package/frontend-vue/tests/website-purge.test.ts +70 -0
- package/frontend-vue/tests/website-settings.test.ts +133 -0
- package/frontend-vue/tests/website-state.test.ts +96 -0
- package/frontend-vue/tests/workspace-scope-control.test.ts +113 -0
- package/frontend-vue/tests/workspace-scope.test.ts +55 -0
- package/package.json +5 -4
- package/skills/dms-marketing/SKILL.md +47 -6
- package/static/snapshot.js +539 -48
- package/static/tracker.js +154 -5
- package/frontend-vue/app/components/WebsitesGrid.vue +0 -812
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
MarketingPurgeScope,
|
|
3
|
+
MarketingWebsiteDeletionImpact,
|
|
4
|
+
MarketingWebsitePurge,
|
|
5
|
+
} from '../composables/useMarketingApi'
|
|
6
|
+
import { formatNumber, formatShortDate } from './format'
|
|
7
|
+
|
|
8
|
+
/** The `t` of useI18n, as far as these helpers call it. */
|
|
9
|
+
type Translate = (
|
|
10
|
+
key: string,
|
|
11
|
+
named?: Record<string, unknown>,
|
|
12
|
+
plural?: number,
|
|
13
|
+
) => string
|
|
14
|
+
|
|
15
|
+
const PREFIX = 'page.marketing.websites.purge'
|
|
16
|
+
|
|
17
|
+
/** One line of a confirmation's impact list. */
|
|
18
|
+
interface ImpactLine {
|
|
19
|
+
icon: string
|
|
20
|
+
label: string
|
|
21
|
+
count?: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The impact list of a purge confirmation: what its scope deletes, counted
|
|
26
|
+
* on every period, then what it keeps. The heatmaps scope deletes clicks,
|
|
27
|
+
* scroll depths and snapshots; the all scope deletes the traffic too.
|
|
28
|
+
*/
|
|
29
|
+
export function websitePurgeImpact(
|
|
30
|
+
scope: MarketingPurgeScope,
|
|
31
|
+
impact: MarketingWebsiteDeletionImpact,
|
|
32
|
+
t: Translate,
|
|
33
|
+
locale: string,
|
|
34
|
+
): ImpactLine[] {
|
|
35
|
+
const count = (value: number) => formatNumber(value, locale)
|
|
36
|
+
const heatmaps: ImpactLine[] = [
|
|
37
|
+
{
|
|
38
|
+
icon: 'i-ph-cursor-click',
|
|
39
|
+
label: t(`${PREFIX}.heatmap_events`),
|
|
40
|
+
count: count(impact.heatmapEvents),
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
icon: 'i-ph-camera',
|
|
44
|
+
label: t('page.marketing.websites.delete.snapshots'),
|
|
45
|
+
count: count(impact.snapshots),
|
|
46
|
+
},
|
|
47
|
+
]
|
|
48
|
+
if (scope === 'heatmaps') {
|
|
49
|
+
return [
|
|
50
|
+
...heatmaps,
|
|
51
|
+
{ icon: 'i-ph-check', label: t(`${PREFIX}.kept_traffic`) },
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
// Every stored day goes, so the counts read "since the oldest one".
|
|
55
|
+
const since =
|
|
56
|
+
impact.since === null ? null : formatShortDate(impact.since, locale)
|
|
57
|
+
const sinceLabel = (key: 'sessions' | 'pageviews') =>
|
|
58
|
+
since === null
|
|
59
|
+
? t(`page.marketing.websites.delete.${key}`)
|
|
60
|
+
: t(`page.marketing.websites.delete.${key}_since`, { date: since })
|
|
61
|
+
return [
|
|
62
|
+
{
|
|
63
|
+
icon: 'i-ph-users',
|
|
64
|
+
label: sinceLabel('sessions'),
|
|
65
|
+
count: count(impact.sessions),
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
icon: 'i-ph-eye',
|
|
69
|
+
label: sinceLabel('pageviews'),
|
|
70
|
+
count: count(impact.pageviews),
|
|
71
|
+
},
|
|
72
|
+
...heatmaps,
|
|
73
|
+
{ icon: 'i-ph-check', label: t(`${PREFIX}.kept`) },
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** The result toast's line: what the purge deleted. */
|
|
78
|
+
export function websitePurgeResult(
|
|
79
|
+
answer: MarketingWebsitePurge,
|
|
80
|
+
t: Translate,
|
|
81
|
+
locale: string,
|
|
82
|
+
): string {
|
|
83
|
+
const { deleted } = answer
|
|
84
|
+
if (answer.scope === 'heatmaps') {
|
|
85
|
+
return t(`${PREFIX}.done_heatmaps_counts`, {
|
|
86
|
+
events: formatNumber(deleted.events, locale),
|
|
87
|
+
snapshots: formatNumber(deleted.snapshots, locale),
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
return t(`${PREFIX}.done_all_counts`, {
|
|
91
|
+
events: formatNumber(deleted.events, locale),
|
|
92
|
+
sessions: formatNumber(deleted.sessions, locale),
|
|
93
|
+
snapshots: formatNumber(deleted.snapshots, locale),
|
|
94
|
+
})
|
|
95
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The website settings modal's draft: what it starts from, what saving it
|
|
3
|
+
* sends, and when it asks first. Pure, so the modal's rules are testable
|
|
4
|
+
* without it.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
MarketingWebsiteCard,
|
|
9
|
+
MarketingWebsitePatch,
|
|
10
|
+
} from '../composables/useMarketingApi'
|
|
11
|
+
import type { WebsiteSetting } from '../composables/useMarketingRoutes'
|
|
12
|
+
import { asksBeforeSnapshotChange } from './snapshot-options'
|
|
13
|
+
|
|
14
|
+
/** The modal's two tabs. */
|
|
15
|
+
export type WebsiteSettingsTab = 'general' | 'danger'
|
|
16
|
+
|
|
17
|
+
/** The fields of the General tab, as typed. */
|
|
18
|
+
export interface WebsiteDraft {
|
|
19
|
+
name: string
|
|
20
|
+
domain: string
|
|
21
|
+
hosts: string[]
|
|
22
|
+
tracking: boolean
|
|
23
|
+
snapshots: boolean
|
|
24
|
+
mask: boolean
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** The fields the modal requires, in the order they are checked. */
|
|
28
|
+
export type WebsiteRequiredField = 'domain' | 'name'
|
|
29
|
+
|
|
30
|
+
/** The draft of a site, as it stands. */
|
|
31
|
+
export function websiteDraftOf(card: MarketingWebsiteCard): WebsiteDraft {
|
|
32
|
+
return {
|
|
33
|
+
name: card.name,
|
|
34
|
+
domain: card.domain,
|
|
35
|
+
hosts: [...card.extraDomains],
|
|
36
|
+
tracking: card.state !== 'paused',
|
|
37
|
+
snapshots: card.snapshotsEnabled,
|
|
38
|
+
mask: card.snapshotMaskText,
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** What saving the draft sends: trimmed, empty hosts dropped. */
|
|
43
|
+
export function websitePatchOf(draft: WebsiteDraft): MarketingWebsitePatch {
|
|
44
|
+
return {
|
|
45
|
+
name: draft.name.trim(),
|
|
46
|
+
domain: draft.domain.trim(),
|
|
47
|
+
extraDomains: draft.hosts.map((host) => host.trim()).filter(Boolean),
|
|
48
|
+
trackingEnabled: draft.tracking,
|
|
49
|
+
snapshotsEnabled: draft.snapshots,
|
|
50
|
+
snapshotMaskText: draft.mask,
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The required fields left blank. */
|
|
55
|
+
export function blankRequiredFields(
|
|
56
|
+
draft: Pick<WebsiteDraft, 'name' | 'domain'>,
|
|
57
|
+
): WebsiteRequiredField[] {
|
|
58
|
+
return (['domain', 'name'] as const).filter((field) => !draft[field].trim())
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Whether the draft differs from the site, for the unsaved-changes guard. */
|
|
62
|
+
export function isWebsiteDraftDirty(
|
|
63
|
+
card: MarketingWebsiteCard,
|
|
64
|
+
draft: WebsiteDraft,
|
|
65
|
+
): boolean {
|
|
66
|
+
return (
|
|
67
|
+
JSON.stringify(websitePatchOf(draft)) !==
|
|
68
|
+
JSON.stringify(websitePatchOf(websiteDraftOf(card)))
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The banner's warning: turning snapshots off, or changing the mask, of a
|
|
74
|
+
* site holding some discards them.
|
|
75
|
+
*/
|
|
76
|
+
export function draftDiscardsSnapshots(
|
|
77
|
+
card: MarketingWebsiteCard,
|
|
78
|
+
draft: WebsiteDraft,
|
|
79
|
+
): boolean {
|
|
80
|
+
return (
|
|
81
|
+
card.snapshots > 0 &&
|
|
82
|
+
(draft.snapshots !== card.snapshotsEnabled ||
|
|
83
|
+
draft.mask !== card.snapshotMaskText)
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Whether saving the draft asks before its snapshot options change. */
|
|
88
|
+
export function draftAsksBeforeSaving(
|
|
89
|
+
card: MarketingWebsiteCard,
|
|
90
|
+
draft: WebsiteDraft,
|
|
91
|
+
): boolean {
|
|
92
|
+
return asksBeforeSnapshotChange(
|
|
93
|
+
card,
|
|
94
|
+
{ snapshotsEnabled: draft.snapshots, snapshotMaskText: draft.mask },
|
|
95
|
+
card.snapshots,
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* A card's control that opens the modal on one setting (the snapshot pill)
|
|
101
|
+
* leaves it here; the modal it opens takes it once, for its own site. A link
|
|
102
|
+
* names it in the URL (`?setting=`) instead.
|
|
103
|
+
*/
|
|
104
|
+
const requested = new Map<string, WebsiteSetting>()
|
|
105
|
+
|
|
106
|
+
export function requestWebsiteSetting(
|
|
107
|
+
websiteId: string,
|
|
108
|
+
setting: WebsiteSetting,
|
|
109
|
+
): void {
|
|
110
|
+
requested.clear()
|
|
111
|
+
requested.set(websiteId, setting)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** The setting asked for this site, forgotten once read. */
|
|
115
|
+
export function takeWebsiteSetting(
|
|
116
|
+
websiteId: string,
|
|
117
|
+
): WebsiteSetting | undefined {
|
|
118
|
+
const setting = requested.get(websiteId)
|
|
119
|
+
requested.clear()
|
|
120
|
+
return setting
|
|
121
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { ContextWebsite } from '../composables/useMarketingContext'
|
|
2
|
+
import { formatRelativeTime, isJustNow } from './format'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A website reads "live" while its last event is this recent: the backend's
|
|
6
|
+
* `live` only says the tag collected once, which a site quiet for hours still
|
|
7
|
+
* is.
|
|
8
|
+
*/
|
|
9
|
+
export const WEBSITE_LIVE_WINDOW_MS = 5 * 60 * 1000
|
|
10
|
+
|
|
11
|
+
/** The state a website shows: `live` only while events are recent, `idle` after. */
|
|
12
|
+
export type WebsiteDisplayState = ContextWebsite['state'] | 'idle'
|
|
13
|
+
|
|
14
|
+
type WebsiteActivity = Pick<ContextWebsite, 'state' | 'lastActivityAt'>
|
|
15
|
+
|
|
16
|
+
export const WEBSITE_STATE_DOTS: Record<WebsiteDisplayState, string> = {
|
|
17
|
+
live: 'bg-success',
|
|
18
|
+
idle: 'bg-(--ui-text-muted)',
|
|
19
|
+
imported: 'bg-info',
|
|
20
|
+
waiting: 'bg-warning',
|
|
21
|
+
paused: 'bg-(--ui-text-dimmed)',
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function websiteDisplayState(
|
|
25
|
+
site: WebsiteActivity,
|
|
26
|
+
now: number,
|
|
27
|
+
): WebsiteDisplayState {
|
|
28
|
+
if (
|
|
29
|
+
site.state === 'live' &&
|
|
30
|
+
(site.lastActivityAt === null ||
|
|
31
|
+
now - site.lastActivityAt > WEBSITE_LIVE_WINDOW_MS)
|
|
32
|
+
) {
|
|
33
|
+
return 'idle'
|
|
34
|
+
}
|
|
35
|
+
return site.state
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
type Translate = (key: string, named?: Record<string, unknown>) => string
|
|
39
|
+
|
|
40
|
+
/** A state that tells the age of its last event, when there is one. */
|
|
41
|
+
function showsActivity(state: WebsiteDisplayState): boolean {
|
|
42
|
+
return state === 'live' || state === 'idle' || state === 'imported'
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The age of the last event: "2 minutes ago", "just now" under 5 seconds or
|
|
47
|
+
* when the server's clock runs ahead of the reader's.
|
|
48
|
+
*/
|
|
49
|
+
function eventAge(
|
|
50
|
+
at: number,
|
|
51
|
+
now: number,
|
|
52
|
+
t: Translate,
|
|
53
|
+
locale: string,
|
|
54
|
+
style?: Intl.RelativeTimeFormatStyle,
|
|
55
|
+
): string {
|
|
56
|
+
return isJustNow(at, now)
|
|
57
|
+
? t('page.marketing.context.just_now')
|
|
58
|
+
: formatRelativeTime(at, now, locale, style)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** "Live · last event 2 minutes ago", "Last event 7 hours ago", "Paused"… */
|
|
62
|
+
export function websiteStateLine(
|
|
63
|
+
site: WebsiteActivity,
|
|
64
|
+
now: number,
|
|
65
|
+
t: Translate,
|
|
66
|
+
locale: string,
|
|
67
|
+
): string {
|
|
68
|
+
const state = websiteDisplayState(site, now)
|
|
69
|
+
if (showsActivity(state) && site.lastActivityAt) {
|
|
70
|
+
return t(`page.marketing.context.${state}`, {
|
|
71
|
+
ago: eventAge(site.lastActivityAt, now, t, locale),
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
return t(`page.marketing.context.state.${site.state}`)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The state line in a toolbar, beside its dot: the age of the last event
|
|
79
|
+
* ("2 min ago"), else the state in a word ("Waiting", "Paused"). The full
|
|
80
|
+
* line goes in its tooltip.
|
|
81
|
+
*/
|
|
82
|
+
export function websiteStateShort(
|
|
83
|
+
site: WebsiteActivity,
|
|
84
|
+
now: number,
|
|
85
|
+
t: Translate,
|
|
86
|
+
locale: string,
|
|
87
|
+
): string {
|
|
88
|
+
const state = websiteDisplayState(site, now)
|
|
89
|
+
if (showsActivity(state) && site.lastActivityAt) {
|
|
90
|
+
return eventAge(site.lastActivityAt, now, t, locale, 'short')
|
|
91
|
+
}
|
|
92
|
+
return t(`page.marketing.context.state_short.${site.state}`)
|
|
93
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { MarketingWorkspace } from '../composables/useMarketingApi'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Whose dashboard the Pages & heatmaps view reads, for a platform owner on
|
|
5
|
+
* the dashboard's own website: `''` their own workspace (the default, and the
|
|
6
|
+
* only value anyone else ever has), `all` every workspace combined, or
|
|
7
|
+
* another workspace's id. Deep-linked as `?workspace=`, remembered per
|
|
8
|
+
* browser for the next visit.
|
|
9
|
+
*/
|
|
10
|
+
export const WORKSPACE_QUERY = 'workspace'
|
|
11
|
+
export const ALL_WORKSPACES = 'all'
|
|
12
|
+
|
|
13
|
+
const STORAGE_KEY = 'dms-marketing:heatmap-workspace'
|
|
14
|
+
|
|
15
|
+
/** The scope a link or the last visit asks for, applied only when the
|
|
16
|
+
* selector offers it: anything else reads the viewer's own workspace. */
|
|
17
|
+
export function effectiveWorkspace(
|
|
18
|
+
wanted: string | undefined,
|
|
19
|
+
options: MarketingWorkspace[],
|
|
20
|
+
): string {
|
|
21
|
+
if (!wanted || options.length < 2) {
|
|
22
|
+
return ''
|
|
23
|
+
}
|
|
24
|
+
if (wanted === ALL_WORKSPACES) {
|
|
25
|
+
return ALL_WORKSPACES
|
|
26
|
+
}
|
|
27
|
+
const option = options.find((workspace) => workspace.id === wanted)
|
|
28
|
+
return option && !option.current ? option.id : ''
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The last scope picked in this browser; `''` when none or unreadable. */
|
|
32
|
+
export function rememberedWorkspace(): string {
|
|
33
|
+
try {
|
|
34
|
+
return globalThis.localStorage?.getItem(STORAGE_KEY) ?? ''
|
|
35
|
+
} catch {
|
|
36
|
+
return ''
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Kept for the next visit; the own workspace clears it. */
|
|
41
|
+
export function rememberWorkspace(workspace: string): void {
|
|
42
|
+
try {
|
|
43
|
+
if (workspace) {
|
|
44
|
+
globalThis.localStorage?.setItem(STORAGE_KEY, workspace)
|
|
45
|
+
} else {
|
|
46
|
+
globalThis.localStorage?.removeItem(STORAGE_KEY)
|
|
47
|
+
}
|
|
48
|
+
} catch {
|
|
49
|
+
// Storage blocked: the URL still carries the scope.
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -105,8 +105,8 @@
|
|
|
105
105
|
"name": "Collection paused notice"
|
|
106
106
|
},
|
|
107
107
|
"context": {
|
|
108
|
-
"description": "
|
|
109
|
-
"name": "Website
|
|
108
|
+
"description": "Shows the page's blocks once a website is picked, or the first-run and install guides.",
|
|
109
|
+
"name": "Website gate"
|
|
110
110
|
},
|
|
111
111
|
"devices": {
|
|
112
112
|
"description": "Sessions by device type.",
|
|
@@ -168,6 +168,10 @@
|
|
|
168
168
|
"description": "Tracked pages with their click and scroll heatmaps.",
|
|
169
169
|
"name": "Pages and heatmaps"
|
|
170
170
|
},
|
|
171
|
+
"period": {
|
|
172
|
+
"description": "The period and comparison every block of the page reads.",
|
|
173
|
+
"name": "Period"
|
|
174
|
+
},
|
|
171
175
|
"quality": {
|
|
172
176
|
"description": "Bounce rate, average session duration and pages per session.",
|
|
173
177
|
"name": "Session quality"
|
|
@@ -196,6 +200,10 @@
|
|
|
196
200
|
"description": "The share of page loads recording clicks.",
|
|
197
201
|
"name": "Sampling"
|
|
198
202
|
},
|
|
203
|
+
"toolbar": {
|
|
204
|
+
"description": "The page toolbar: the website, Direct / Pause and the period every block of the page reads.",
|
|
205
|
+
"name": "Website and period bar"
|
|
206
|
+
},
|
|
199
207
|
"tops": {
|
|
200
208
|
"description": "Content, acquisition, audience and custom events.",
|
|
201
209
|
"name": "Top lists"
|
|
@@ -232,13 +240,17 @@
|
|
|
232
240
|
"description": "Sessions per day, or per hour over a single day.",
|
|
233
241
|
"name": "Sessions over time"
|
|
234
242
|
},
|
|
243
|
+
"website_picker": {
|
|
244
|
+
"description": "Picks the website every block of the page reads, with its tracking state.",
|
|
245
|
+
"name": "Website picker"
|
|
246
|
+
},
|
|
235
247
|
"website_waiting": {
|
|
236
248
|
"description": "Shown while the website has sent no pageview, with a link to the install guide.",
|
|
237
249
|
"name": "First pageview notice"
|
|
238
250
|
},
|
|
239
|
-
"
|
|
240
|
-
"
|
|
241
|
-
"
|
|
251
|
+
"websites_table": {
|
|
252
|
+
"name": "Websites",
|
|
253
|
+
"description": "The tracked websites as cards, each opening its settings."
|
|
242
254
|
}
|
|
243
255
|
},
|
|
244
256
|
"builder": {
|
|
@@ -332,7 +344,7 @@
|
|
|
332
344
|
"window_max": "Max {max} h",
|
|
333
345
|
"window_presets": {
|
|
334
346
|
"1": "1 h",
|
|
335
|
-
"24": "
|
|
347
|
+
"24": "24 h"
|
|
336
348
|
},
|
|
337
349
|
"step_matched_as": "Matched as {path}",
|
|
338
350
|
"step_unseen": "No raw event matches it in the last 30 days: funnels count raw events, not the daily statistics beside"
|
|
@@ -376,40 +388,34 @@
|
|
|
376
388
|
},
|
|
377
389
|
"context": {
|
|
378
390
|
"add_website": "Add a website",
|
|
379
|
-
"comparisons": {
|
|
380
|
-
"none": "No comparison",
|
|
381
|
-
"previous-period": "vs previous period",
|
|
382
|
-
"previous-year": "vs previous year"
|
|
383
|
-
},
|
|
384
391
|
"error": {
|
|
385
392
|
"description": "Tracking keeps running and nothing was lost. Try again in a moment.",
|
|
386
393
|
"title": "Couldn't load your websites"
|
|
387
394
|
},
|
|
388
395
|
"idle": "Last event {ago}",
|
|
389
396
|
"imported": "Imported history · last event {ago}",
|
|
397
|
+
"just_now": "just now",
|
|
390
398
|
"label": "Website and period",
|
|
391
399
|
"live": "Live · last event {ago}",
|
|
392
400
|
"no_website": "No website yet",
|
|
393
|
-
"presets": {
|
|
394
|
-
"custom": "Custom",
|
|
395
|
-
"last-30-days": "30D",
|
|
396
|
-
"last-7-days": "7D",
|
|
397
|
-
"last-90-days": "90D",
|
|
398
|
-
"today": "Today"
|
|
399
|
-
},
|
|
400
401
|
"range_cut": "Only the last {days} days of this range are shown.",
|
|
401
|
-
"refresh": "Refresh",
|
|
402
402
|
"state": {
|
|
403
403
|
"imported": "Imported history only",
|
|
404
404
|
"live": "Live",
|
|
405
405
|
"paused": "Tracking paused for this website",
|
|
406
406
|
"waiting": "Waiting for the first event"
|
|
407
407
|
},
|
|
408
|
+
"state_short": {
|
|
409
|
+
"imported": "Imported",
|
|
410
|
+
"live": "Live",
|
|
411
|
+
"paused": "Paused",
|
|
412
|
+
"waiting": "Waiting"
|
|
413
|
+
},
|
|
408
414
|
"switch_error": "Couldn't switch to this website",
|
|
409
415
|
"switch_website": "Switch website",
|
|
410
416
|
"utc_days": "Statistics are counted per UTC day: each day shown here starts at {time} your time.",
|
|
411
|
-
"vs_previous": "vs previous period",
|
|
412
417
|
"vs_previous_short": "vs previous",
|
|
418
|
+
"vs_previous_year_short": "vs previous year",
|
|
413
419
|
"waiting_guide": {
|
|
414
420
|
"action": "Open the install guide",
|
|
415
421
|
"description": "Add the tracker to the site, then open one of its pages: the first figures show up here within a minute.",
|
|
@@ -427,6 +433,7 @@
|
|
|
427
433
|
"xr": "XR headset"
|
|
428
434
|
},
|
|
429
435
|
"errors": {
|
|
436
|
+
"dashboard_website_kept": "This is the dashboard's own website: it cannot be deleted. Pause its tracking or purge its data instead.",
|
|
430
437
|
"duplicate_host": "Another website of this workspace already declares this host.",
|
|
431
438
|
"duplicate_host_named": "{site} already declares {host}: a host belongs to one website of the workspace.",
|
|
432
439
|
"duplicate_experiment_key": "Another A/B test on this website already uses this key.",
|
|
@@ -451,7 +458,8 @@
|
|
|
451
458
|
},
|
|
452
459
|
"snapshot_retention_above_raw": "Snapshots can't be kept longer than raw events: they would outlive every click they back.",
|
|
453
460
|
"statistics_retention_below_raw": "Statistics can't be kept shorter than raw events: funnels and A/B results would lose the sessions of their events.",
|
|
454
|
-
"website_not_found": "This website does not exist, or belongs to another workspace."
|
|
461
|
+
"website_not_found": "This website does not exist, or belongs to another workspace.",
|
|
462
|
+
"workspace_not_found": "This workspace does not exist, has no dashboard tracking, or is not yours to read."
|
|
455
463
|
},
|
|
456
464
|
"experiment": {
|
|
457
465
|
"colours_note": "Arms keep their colour everywhere: A is neutral, B is the primary colour. Green and red only mean a verdict.",
|
|
@@ -724,6 +732,7 @@
|
|
|
724
732
|
"coverage": {
|
|
725
733
|
"period_title": "Funnels cover this period from {date} only",
|
|
726
734
|
"previous_title": "No comparison with the previous period",
|
|
735
|
+
"previous_year_title": "No comparison with the previous year",
|
|
727
736
|
"description": "Funnels are computed from raw events, kept from {date} on: earlier days count nothing, and a comparison window they don't cover is left out rather than shown as a huge change."
|
|
728
737
|
}
|
|
729
738
|
},
|
|
@@ -902,7 +911,6 @@
|
|
|
902
911
|
},
|
|
903
912
|
"list_truncated": "Only the first {count} pages are listed. Filter, or type a path, to find another.",
|
|
904
913
|
"missing_hint": "Missing a page? Type its exact path above.",
|
|
905
|
-
"more_list": "More list actions",
|
|
906
914
|
"most_clicked": "Most clicked",
|
|
907
915
|
"most_clicked_empty": "No element anchored yet: clicks show on the page only.",
|
|
908
916
|
"no_match": {
|
|
@@ -923,7 +931,12 @@
|
|
|
923
931
|
"all_masked": "All text masked",
|
|
924
932
|
"approximate_layout": "Showing the {layout} snapshot at {width}px",
|
|
925
933
|
"approximate_layout_hint": "No visitor has been captured at this width yet, so the layout is reflowed. Clicks still land on their element.",
|
|
934
|
+
"captured_from": "from {url}",
|
|
935
|
+
"captured_from_hint": "The page the snapshot was taken on. Its query string and fragment are never kept: they can carry tokens or addresses.",
|
|
936
|
+
"drift": "{share} of clicks aim at elements missing from this snapshot",
|
|
937
|
+
"drift_hint": "The page may have changed since {date}. Those clicks are drawn where they fell on the page, not on their element. Resetting the page clears its clicks and snapshot and starts again from the current version.",
|
|
926
938
|
"enable_snapshots": "Enable page snapshots",
|
|
939
|
+
"end_of_page": "End of page",
|
|
927
940
|
"fields_masked": "Text visible, form fields masked",
|
|
928
941
|
"layout": "Layout",
|
|
929
942
|
"layout_desktop": "Desktop",
|
|
@@ -942,12 +955,15 @@
|
|
|
942
955
|
"overflow_hint": "The snapshot lays out at {width}px. The overlay follows the document, so it stays aligned; the backdrop may be cut off.",
|
|
943
956
|
"oversized": "Page too tall to preview accurately",
|
|
944
957
|
"oversized_hint": "The document exceeds the largest surface that can be drawn, so the overlay and the backdrop no longer line up. The click figures are unaffected.",
|
|
958
|
+
"snapshot_settings": "Snapshot settings",
|
|
945
959
|
"snapshots_enabled": "Page snapshots",
|
|
946
960
|
"snapshots_off": "Page snapshots are off for this site",
|
|
947
961
|
"snapshots_off_hint": "Clicks and scrolls are still counted, but the heatmap is only drawn over a snapshot of the page as visitors see it: its text and images are then stored, every form field masked, for the snapshot retention set in the module settings. Enable it if that fits this site's privacy commitments.",
|
|
948
962
|
"status_line": "Snapshot captured {date} · {layout} {width}px",
|
|
963
|
+
"strip_details": "Why this warning",
|
|
949
964
|
"too_heavy": "Page too heavy to capture",
|
|
950
|
-
"too_heavy_hint": "The last sampled visit
|
|
965
|
+
"too_heavy_hint": "The last sampled visit's capture came to {size} MiB to upload (compressed where the browser can), past the {limit} MiB cap, so this page has no backdrop. The click and scroll figures above are unaffected. A visit retries daily: lighter markup — or a heavy section marked data-dms-marketing-block — brings it under the cap, and the snapshotMaxBytes module option raises the cap.",
|
|
966
|
+
"workspace_masked": "Another workspace's page: all text masked"
|
|
951
967
|
},
|
|
952
968
|
"reset": {
|
|
953
969
|
"and": "{first} and {second}",
|
|
@@ -962,13 +978,16 @@
|
|
|
962
978
|
"page_description": "Deletes the clicks, scroll depths and snapshots of this page, whatever the period. Type its path to confirm.",
|
|
963
979
|
"page_done": "Heatmap of the page reset",
|
|
964
980
|
"page_title": "Reset the heatmap of this page?",
|
|
965
|
-
"site_button": "Reset every heatmap…",
|
|
966
|
-
"site_description": "Deletes every click, scroll depth and page snapshot of the website, whatever the period. Type the domain to confirm.",
|
|
967
|
-
"site_done": "Heatmaps of {name} reset",
|
|
968
|
-
"site_title": "Reset the heatmaps of {name}?",
|
|
969
981
|
"snapshots": "{count} snapshot | {count} snapshots"
|
|
970
982
|
},
|
|
971
983
|
"row_clicks": "{count} click | {count} clicks",
|
|
984
|
+
"row_snapshot": {
|
|
985
|
+
"none": "No snapshot",
|
|
986
|
+
"none_title": "No snapshot yet: the next sampled visit captures one",
|
|
987
|
+
"title": "Snapshot captured {date} · {layouts} | Snapshots captured up to {date} · {layouts}",
|
|
988
|
+
"too_heavy": "Too heavy",
|
|
989
|
+
"too_heavy_title": "Page too heavy to capture (tried {date})"
|
|
990
|
+
},
|
|
972
991
|
"row_views": "{count} view | {count} views",
|
|
973
992
|
"sampled_unit": "sampled view | sampled views",
|
|
974
993
|
"sampling_note": "Clicks and scrolls were recorded on {rate} of page loads over this period.",
|
|
@@ -1007,6 +1026,16 @@
|
|
|
1007
1026
|
"description": "Pages and heatmaps appear once the tracker is on the site and a first page is loaded.",
|
|
1008
1027
|
"title": "{website} hasn't received a visit yet"
|
|
1009
1028
|
},
|
|
1029
|
+
"workspace": {
|
|
1030
|
+
"all": "All workspaces",
|
|
1031
|
+
"all_hint": "{count} workspace combined | {count} workspaces combined",
|
|
1032
|
+
"combined_note": "Combined across {count} workspace, read-only. Snapshots show with their text masked. | Combined across {count} workspaces, read-only. Snapshots show with their text masked.",
|
|
1033
|
+
"label": "Workspace:",
|
|
1034
|
+
"note_label": "About this view",
|
|
1035
|
+
"other_note": "Another workspace's dashboard, read-only. Snapshots show with their text masked.",
|
|
1036
|
+
"switch": "Change the workspace the heatmaps read",
|
|
1037
|
+
"yours": "Your workspace"
|
|
1038
|
+
},
|
|
1010
1039
|
"quoted": "“{text}”"
|
|
1011
1040
|
},
|
|
1012
1041
|
"quick_actions": {
|
|
@@ -1069,13 +1098,17 @@
|
|
|
1069
1098
|
},
|
|
1070
1099
|
"column": {
|
|
1071
1100
|
"domain": "Domain",
|
|
1072
|
-
"name": "Name"
|
|
1101
|
+
"name": "Name",
|
|
1102
|
+
"tracking": "Tracking",
|
|
1103
|
+
"snapshots": "Page snapshots",
|
|
1104
|
+
"created": "Added"
|
|
1073
1105
|
},
|
|
1074
1106
|
"create": "Add a website",
|
|
1075
1107
|
"delete": {
|
|
1076
1108
|
"action": "Delete website…",
|
|
1077
1109
|
"confirm": "Delete website",
|
|
1078
1110
|
"description": "Everything tracked on this website goes, for every period. Type the domain to confirm.",
|
|
1111
|
+
"dashboard_kept": "This is the dashboard's own website, which the module tracks itself: it cannot be deleted. Pause its tracking or purge its data instead.",
|
|
1079
1112
|
"done": "{name} deleted",
|
|
1080
1113
|
"funnels": "Funnels and their A/B tests",
|
|
1081
1114
|
"impact_error": "Couldn't list what deleting this website would remove.",
|
|
@@ -1088,14 +1121,38 @@
|
|
|
1088
1121
|
"snapshots": "Snapshots",
|
|
1089
1122
|
"title": "Delete {name}?"
|
|
1090
1123
|
},
|
|
1124
|
+
"purge": {
|
|
1125
|
+
"all_action": "Purge all data…",
|
|
1126
|
+
"all_confirm": "Purge all data",
|
|
1127
|
+
"all_description": "Everything collected on this website goes, for every period: pageviews, sessions, daily statistics, clicks, scroll depths, snapshots, A/B exposures and imported history. The website, its settings and its funnels stay; their results start again from zero. Type the domain to confirm.",
|
|
1128
|
+
"all_done": "Data of {name} purged",
|
|
1129
|
+
"all_hint": "Everything collected: traffic, sessions, heatmaps, A/B exposures and imported history.",
|
|
1130
|
+
"all_title": "Purge all the data of {name}?",
|
|
1131
|
+
"done_all_counts": "Deleted: {events} events, {sessions} sessions and {snapshots} snapshots.",
|
|
1132
|
+
"done_heatmaps_counts": "Deleted: {events} clicks and scroll depths, and {snapshots} snapshots.",
|
|
1133
|
+
"heatmap_events": "Clicks and scroll depths, all time",
|
|
1134
|
+
"heatmaps_action": "Purge heatmaps…",
|
|
1135
|
+
"heatmaps_confirm": "Purge heatmaps",
|
|
1136
|
+
"heatmaps_description": "The clicks, scroll depths and snapshots of every page go, for every period: click counts drop to zero everywhere. Pageviews, sessions and funnels are kept. Type the domain to confirm.",
|
|
1137
|
+
"heatmaps_done": "Heatmaps of {name} purged",
|
|
1138
|
+
"heatmaps_hint": "Clicks, scroll depths and snapshots of every page. Traffic is kept.",
|
|
1139
|
+
"heatmaps_title": "Purge the heatmaps of {name}?",
|
|
1140
|
+
"kept": "The website, its settings, funnels and A/B tests are kept",
|
|
1141
|
+
"kept_traffic": "Pageviews, sessions, funnels and A/B tests are kept",
|
|
1142
|
+
"section": "Purge collected data",
|
|
1143
|
+
"section_description": "The website keeps its settings, funnels and A/B tests; their results start again from zero."
|
|
1144
|
+
},
|
|
1091
1145
|
"description": "The sites sending data to this workspace, their tracker, and what is captured on each.",
|
|
1092
|
-
"
|
|
1093
|
-
"
|
|
1094
|
-
"
|
|
1095
|
-
"
|
|
1096
|
-
"
|
|
1097
|
-
"
|
|
1098
|
-
"
|
|
1146
|
+
"discard_confirm": {
|
|
1147
|
+
"title": "Delete the site's snapshots?",
|
|
1148
|
+
"description": "Changing this option deletes the site's snapshot. Clicks and scroll depth are kept; the next sampled visits capture the pages again. | Changing this option deletes the site's {count} snapshots. Clicks and scroll depth are kept; the next sampled visits capture the pages again.",
|
|
1149
|
+
"description_unknown": "Changing this option deletes the site's existing snapshots. Clicks and scroll depth are kept; the next sampled visits capture the pages again.",
|
|
1150
|
+
"confirm": "Change and delete",
|
|
1151
|
+
"cancel": "Keep the snapshots",
|
|
1152
|
+
"cancel_none": "Cancel",
|
|
1153
|
+
"confirm_none": "Change",
|
|
1154
|
+
"description_none": "The site holds no snapshot yet. The next sampled visits capture its pages under the new setting.",
|
|
1155
|
+
"title_none": "Change the site's snapshot settings?"
|
|
1099
1156
|
},
|
|
1100
1157
|
"error": "Couldn't load the websites",
|
|
1101
1158
|
"fields": {
|
|
@@ -1124,6 +1181,7 @@
|
|
|
1124
1181
|
"save_error": "The website could not be saved.",
|
|
1125
1182
|
"saved": "Website saved",
|
|
1126
1183
|
"snapshots": "Page snapshots",
|
|
1184
|
+
"snapshots_edit": "Page snapshot settings of {name}",
|
|
1127
1185
|
"snapshots_masked": "On · text masked",
|
|
1128
1186
|
"snapshots_off": "Off",
|
|
1129
1187
|
"snapshots_on": "On · text visible",
|
|
@@ -1144,7 +1202,25 @@
|
|
|
1144
1202
|
"edit_named": "Settings of {name}",
|
|
1145
1203
|
"snippet_named": "Tracker snippet for {name}",
|
|
1146
1204
|
"open_overview_named": "Open the {name} overview",
|
|
1147
|
-
"finish_install_named": "Finish installing {name}"
|
|
1205
|
+
"finish_install_named": "Finish installing {name}",
|
|
1206
|
+
"search": "Search websites",
|
|
1207
|
+
"menu_named": "Actions for {name}",
|
|
1208
|
+
"edit": "Settings",
|
|
1209
|
+
"no_match": "No website matches these filters.",
|
|
1210
|
+
"count": "{count} website | {count} websites",
|
|
1211
|
+
"modal": {
|
|
1212
|
+
"title": "Website settings",
|
|
1213
|
+
"general": "General",
|
|
1214
|
+
"danger": "Danger zone",
|
|
1215
|
+
"added": "added {date}"
|
|
1216
|
+
},
|
|
1217
|
+
"create_modal": {
|
|
1218
|
+
"title": "Add a website",
|
|
1219
|
+
"description": "Name the site and its domain; the install guide then gives you the tag to paste."
|
|
1220
|
+
}
|
|
1221
|
+
},
|
|
1222
|
+
"live": {
|
|
1223
|
+
"new_events": "{count} new event | {count} new events"
|
|
1148
1224
|
}
|
|
1149
1225
|
}
|
|
1150
1226
|
}
|