@duffcloudservices/cms 0.12.0 → 0.13.1
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 +244 -8
- package/dist/chunk-A5F4C72F.js +500 -0
- package/dist/chunk-A5F4C72F.js.map +1 -0
- package/dist/{chunk-F3EIWEZD.js → chunk-HVSF23P7.js} +971 -73
- package/dist/chunk-HVSF23P7.js.map +1 -0
- package/dist/editor/editorBridge.d.ts +13 -1
- package/dist/editor/editorBridge.js +75 -5
- package/dist/editor/editorBridge.js.map +1 -1
- package/dist/headHonesty-OzxvLuwd.d.ts +222 -0
- package/dist/index.d.ts +365 -22
- package/dist/index.js +421 -21
- package/dist/index.js.map +1 -1
- package/dist/installSeoHead-kWQwObez.d.ts +627 -0
- package/dist/plugins/index.d.ts +90 -6
- package/dist/plugins/index.js +530 -49
- package/dist/plugins/index.js.map +1 -1
- package/dist/seo/index.d.ts +763 -4
- package/dist/seo/index.js +2 -2
- package/dist/{vitepressTransform-DfmABXmK.d.ts → vitepressTransform-JG_zlaux.d.ts} +99 -6
- package/package.json +17 -6
- package/src/components/DcsCallButton.test.ts +58 -0
- package/src/components/DcsCallButton.vue +19 -4
- package/src/components/LiteMediaEmbed.vue +3 -3
- package/src/components/ManagedImage.test.ts +34 -0
- package/src/components/ManagedImage.vue +5 -0
- package/src/components/PreviewRibbon.vue +25 -6
- package/src/components/raw-source-imports.test.ts +44 -0
- package/src/composables/useConversionTracking.test.ts +492 -0
- package/src/composables/useConversionTracking.ts +770 -0
- package/src/composables/useReleaseNotes.ts +7 -1
- package/src/composables/useSEO.applyHead.test.ts +150 -0
- package/src/composables/useSEO.ts +63 -17
- package/src/composables/useSiteVersion.ts +4 -1
- package/src/composables/useSiteVisitorSession.test.ts +56 -0
- package/src/composables/useSiteVisitorSession.ts +39 -3
- package/src/composables/useTextContent.ts +9 -1
- package/dist/chunk-DAYLLSEE.js +0 -3
- package/dist/chunk-DAYLLSEE.js.map +0 -1
- package/dist/chunk-F3EIWEZD.js.map +0 -1
- package/dist/spliceHeadHtml-CsBEucGy.d.ts +0 -254
package/dist/index.js
CHANGED
|
@@ -1,11 +1,398 @@
|
|
|
1
|
-
import './chunk-
|
|
2
|
-
import { resolvePageSeo, generateJsonLd, buildHeadTags } from './chunk-
|
|
3
|
-
export { AI_BOTS, absolutizeUrl, breadcrumbTrailFromRoute, buildBlogPosting, buildBreadcrumbList, buildFaqPage, buildGlobalGraph, buildHeadTags, buildLlmsTxt, buildReviewSchemaParts, buildRobotsTxt, buildSitemapXml, buildVitePressSeoHead, createSeoTransformPageData, defaultRelativePathToRoute, escapeJsonLd, filterRealFaq, filterRealReviews, findReviewItemsForPage, generateJsonLd, generateOpenGraphMeta, generateTwitterMeta, isRouteIndexable, renderHeadTags, resolvePageSeo, slugToTitle, spliceHeadHtml, stripManagedHeadTags } from './chunk-
|
|
4
|
-
import { shallowRef, ref, computed,
|
|
5
|
-
import {
|
|
6
|
-
import { isCdnAssetUrl, resolveResponsiveImage } from '@duffcloudservices/cms-core';
|
|
1
|
+
import './chunk-A5F4C72F.js';
|
|
2
|
+
import { resolvePageSeo, generateJsonLd, buildHeadTags } from './chunk-HVSF23P7.js';
|
|
3
|
+
export { AI_BOTS, absolutizeUrl, breadcrumbTrailFromRoute, buildBlogPosting, buildBreadcrumbList, buildFaqPage, buildGlobalGraph, buildHeadTags, buildLlmsTxt, buildReviewSchemaParts, buildRobotsTxt, buildSeoHeadRouteMap, buildSitemapXml, buildVitePressSeoHead, createSeoTransformPageData, defaultRelativePathToRoute, escapeJsonLd, filterRealFaq, filterRealReviews, findReviewItemsForPage, generateJsonLd, generateOpenGraphMeta, generateTwitterMeta, installSeoHead, isRouteIndexable, normalizeSeoHeadPath, renderHeadTags, resolvePageSeo, slugToTitle, spliceHeadHtml, stripManagedHeadTags } from './chunk-HVSF23P7.js';
|
|
4
|
+
import { shallowRef, getCurrentInstance, onMounted, onBeforeUnmount, ref, computed, readonly, toValue, onUnmounted } from 'vue';
|
|
5
|
+
import { isCdnAssetUrl, resolveResponsiveImage, platformFetch, isPlatformFetchError } from '@duffcloudservices/cms-core';
|
|
7
6
|
export { isCdnAssetUrl, resolveResponsiveImage } from '@duffcloudservices/cms-core';
|
|
7
|
+
import { useHead } from '@unhead/vue';
|
|
8
8
|
|
|
9
|
+
var CONVERSION_INTERACTION_TYPES = [
|
|
10
|
+
"booking",
|
|
11
|
+
"phone",
|
|
12
|
+
"email",
|
|
13
|
+
"form_submit"
|
|
14
|
+
];
|
|
15
|
+
function isConversionType(type) {
|
|
16
|
+
return CONVERSION_INTERACTION_TYPES.includes(type);
|
|
17
|
+
}
|
|
18
|
+
var CONVERSION_CAPTURE_VERSION = "2";
|
|
19
|
+
var DEFAULT_BOOKING_HOSTS = [
|
|
20
|
+
"stridethera.com",
|
|
21
|
+
// KEPT — physical therapy
|
|
22
|
+
"momence.com",
|
|
23
|
+
// KEPT — gym / recovery
|
|
24
|
+
"vagaro.com",
|
|
25
|
+
// Just Posh
|
|
26
|
+
"acuityscheduling.com",
|
|
27
|
+
"booksy.com",
|
|
28
|
+
"calendly.com",
|
|
29
|
+
"fresha.com",
|
|
30
|
+
"janeapp.com",
|
|
31
|
+
"mindbodyonline.com",
|
|
32
|
+
"schedulicity.com",
|
|
33
|
+
"setmore.com",
|
|
34
|
+
"simplepractice.com",
|
|
35
|
+
"square.site",
|
|
36
|
+
"squareup.com"
|
|
37
|
+
];
|
|
38
|
+
var DEFAULT_SOCIAL_HOSTS = [
|
|
39
|
+
"facebook.com",
|
|
40
|
+
"instagram.com",
|
|
41
|
+
"linkedin.com",
|
|
42
|
+
"pinterest.com",
|
|
43
|
+
"threads.net",
|
|
44
|
+
"tiktok.com",
|
|
45
|
+
"x.com",
|
|
46
|
+
"youtube.com"
|
|
47
|
+
];
|
|
48
|
+
var LABEL_MAX_LENGTH = 120;
|
|
49
|
+
var DEFAULT_BUFFER_LIMIT = 50;
|
|
50
|
+
var DEDUPE_WINDOW_MS = 400;
|
|
51
|
+
function hostMatches(host, patterns) {
|
|
52
|
+
const h = host.toLowerCase();
|
|
53
|
+
return patterns.some((p) => {
|
|
54
|
+
const needle = p.toLowerCase();
|
|
55
|
+
return h === needle || h.endsWith(`.${needle}`);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function sanitizeUrl(raw) {
|
|
59
|
+
const trimmed = raw.trim();
|
|
60
|
+
if (!trimmed) return "";
|
|
61
|
+
const cut = trimmed.split("#")[0].split("?")[0];
|
|
62
|
+
return cut.slice(0, 300);
|
|
63
|
+
}
|
|
64
|
+
var CONTACT_SCHEMES = ["tel:", "sms:", "mailto:"];
|
|
65
|
+
function hashTarget(value) {
|
|
66
|
+
let h = 2166136261;
|
|
67
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
68
|
+
h ^= value.charCodeAt(i);
|
|
69
|
+
h = Math.imul(h, 16777619) >>> 0;
|
|
70
|
+
}
|
|
71
|
+
return h.toString(36);
|
|
72
|
+
}
|
|
73
|
+
function hrefScheme(href, pageHost) {
|
|
74
|
+
const raw = href.trim();
|
|
75
|
+
if (!raw) return "";
|
|
76
|
+
const lower = raw.toLowerCase();
|
|
77
|
+
const contact = CONTACT_SCHEMES.find((s) => lower.startsWith(s));
|
|
78
|
+
if (contact) return contact;
|
|
79
|
+
if (lower.startsWith("#")) return "";
|
|
80
|
+
try {
|
|
81
|
+
return new URL(raw, `https://${pageHost || "localhost"}/`).protocol;
|
|
82
|
+
} catch {
|
|
83
|
+
return "";
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function redactHref(href, pageHost) {
|
|
87
|
+
const raw = href.trim();
|
|
88
|
+
if (!raw) return { href: "", scheme: "", hash: "" };
|
|
89
|
+
const lower = raw.toLowerCase();
|
|
90
|
+
const contact = CONTACT_SCHEMES.find((s) => lower.startsWith(s));
|
|
91
|
+
if (contact) {
|
|
92
|
+
const target = raw.slice(contact.length).split("?")[0].trim().toLowerCase();
|
|
93
|
+
const normalized = contact === "mailto:" ? target : target.replace(/[^0-9+]/gu, "");
|
|
94
|
+
const hash = normalized ? hashTarget(normalized) : "";
|
|
95
|
+
return { href: `${contact}#${hash}`, scheme: contact, hash };
|
|
96
|
+
}
|
|
97
|
+
return { href: sanitizeUrl(raw), scheme: hrefScheme(raw, pageHost), hash: "" };
|
|
98
|
+
}
|
|
99
|
+
function isSubmitControl(el) {
|
|
100
|
+
const tag = el.tagName.toLowerCase();
|
|
101
|
+
if (tag !== "button" && tag !== "input") return false;
|
|
102
|
+
const type = (el.getAttribute("type") ?? "").toLowerCase();
|
|
103
|
+
if (type === "submit") return true;
|
|
104
|
+
if (type) return false;
|
|
105
|
+
return tag === "button" && !!el.closest("form");
|
|
106
|
+
}
|
|
107
|
+
function doNotTrackEnabled() {
|
|
108
|
+
if (typeof navigator === "undefined" && typeof window === "undefined") return false;
|
|
109
|
+
const nav = typeof navigator !== "undefined" ? navigator : void 0;
|
|
110
|
+
const win = typeof window !== "undefined" ? window : void 0;
|
|
111
|
+
const signal = nav?.doNotTrack ?? win?.doNotTrack ?? nav?.msDoNotTrack;
|
|
112
|
+
return signal === "1" || signal === "yes";
|
|
113
|
+
}
|
|
114
|
+
function labelFor(element) {
|
|
115
|
+
const aria = element.getAttribute("aria-label");
|
|
116
|
+
const source = aria && aria.trim() || (element.textContent ?? "").replace(/\s+/gu, " ").trim() || element.getAttribute("title") || element.getAttribute("href") || element.tagName.toLowerCase();
|
|
117
|
+
return source.slice(0, LABEL_MAX_LENGTH);
|
|
118
|
+
}
|
|
119
|
+
function classifyHref(href, opts) {
|
|
120
|
+
const raw = href.trim();
|
|
121
|
+
if (!raw) return "button";
|
|
122
|
+
const lower = raw.toLowerCase();
|
|
123
|
+
if (lower.startsWith("tel:") || lower.startsWith("sms:")) return "phone";
|
|
124
|
+
if (lower.startsWith("mailto:")) return "email";
|
|
125
|
+
if (lower.startsWith("#") || lower.startsWith("javascript:")) return "button";
|
|
126
|
+
let url;
|
|
127
|
+
try {
|
|
128
|
+
url = new URL(raw, `https://${opts.pageHost || "localhost"}/`);
|
|
129
|
+
} catch {
|
|
130
|
+
return "internal";
|
|
131
|
+
}
|
|
132
|
+
if (hostMatches(url.hostname, opts.bookingHosts)) return "booking";
|
|
133
|
+
const sameOrigin = !!opts.pageHost && url.hostname.toLowerCase() === opts.pageHost.toLowerCase();
|
|
134
|
+
if (sameOrigin && opts.bookingPaths.some((p) => url.pathname.startsWith(p))) {
|
|
135
|
+
return "booking";
|
|
136
|
+
}
|
|
137
|
+
if (hostMatches(url.hostname, opts.socialHosts)) return "social";
|
|
138
|
+
if (sameOrigin) return "internal";
|
|
139
|
+
if (url.protocol === "http:" || url.protocol === "https:") return "external";
|
|
140
|
+
return "external";
|
|
141
|
+
}
|
|
142
|
+
var registeredTrackers = /* @__PURE__ */ new Set();
|
|
143
|
+
var boundDocuments = /* @__PURE__ */ new WeakSet();
|
|
144
|
+
var warnedDoubleBind = false;
|
|
145
|
+
function attachConversionSink(sink) {
|
|
146
|
+
let count = 0;
|
|
147
|
+
for (const tracker of registeredTrackers) {
|
|
148
|
+
tracker.attachSink(sink);
|
|
149
|
+
count += 1;
|
|
150
|
+
}
|
|
151
|
+
return count;
|
|
152
|
+
}
|
|
153
|
+
function createConversionTracker(options = {}) {
|
|
154
|
+
const eventName = options.eventName ?? "site_interaction";
|
|
155
|
+
const bookingHosts = [...DEFAULT_BOOKING_HOSTS, ...options.bookingHosts ?? []];
|
|
156
|
+
const socialHosts = [...DEFAULT_SOCIAL_HOSTS, ...options.socialHosts ?? []];
|
|
157
|
+
const bookingPaths = options.bookingPaths ?? [];
|
|
158
|
+
const bufferLimit = Math.max(0, options.bufferLimit ?? DEFAULT_BUFFER_LIMIT);
|
|
159
|
+
const requireTrusted = options.requireTrusted ?? false;
|
|
160
|
+
const captureFormSubmits = options.captureFormSubmits ?? true;
|
|
161
|
+
const respectDoNotTrack = options.respectDoNotTrack ?? true;
|
|
162
|
+
const force = options.force ?? false;
|
|
163
|
+
const mirrors = options.mirrors ?? [];
|
|
164
|
+
let sink = options.sink ?? null;
|
|
165
|
+
let buffer = [];
|
|
166
|
+
let listening = false;
|
|
167
|
+
let lastKey = "";
|
|
168
|
+
let lastAt = 0;
|
|
169
|
+
const doc = () => options.target ?? (typeof document !== "undefined" ? document : null);
|
|
170
|
+
function pageHost() {
|
|
171
|
+
return typeof window !== "undefined" ? window.location.hostname : "";
|
|
172
|
+
}
|
|
173
|
+
function pagePath() {
|
|
174
|
+
return typeof window !== "undefined" ? window.location.pathname : "";
|
|
175
|
+
}
|
|
176
|
+
function emit(event) {
|
|
177
|
+
for (const mirror of mirrors) {
|
|
178
|
+
try {
|
|
179
|
+
mirror(event);
|
|
180
|
+
} catch {
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (sink) {
|
|
184
|
+
try {
|
|
185
|
+
sink(event);
|
|
186
|
+
} catch {
|
|
187
|
+
}
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (bufferLimit === 0) return;
|
|
191
|
+
buffer.push(event);
|
|
192
|
+
if (buffer.length > bufferLimit) buffer.shift();
|
|
193
|
+
}
|
|
194
|
+
function compose(actionable, interactionType, rawHref) {
|
|
195
|
+
const host = pageHost();
|
|
196
|
+
const { href, scheme, hash } = redactHref(rawHref, host);
|
|
197
|
+
let hrefHost = "";
|
|
198
|
+
if (href && !hash) {
|
|
199
|
+
try {
|
|
200
|
+
hrefHost = new URL(href, `https://${host || "localhost"}/`).hostname;
|
|
201
|
+
} catch {
|
|
202
|
+
hrefHost = "";
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
name: eventName,
|
|
207
|
+
properties: {
|
|
208
|
+
interaction_type: interactionType,
|
|
209
|
+
is_conversion: isConversionType(interactionType) ? "true" : "false",
|
|
210
|
+
label: labelFor(actionable),
|
|
211
|
+
href,
|
|
212
|
+
href_scheme: scheme,
|
|
213
|
+
href_host: hrefHost,
|
|
214
|
+
href_hash: hash,
|
|
215
|
+
page_path: pagePath(),
|
|
216
|
+
host,
|
|
217
|
+
capture_version: CONVERSION_CAPTURE_VERSION
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
function build(element) {
|
|
222
|
+
const anchor = element.closest("a");
|
|
223
|
+
const actionable = anchor ?? element.closest("button");
|
|
224
|
+
if (!actionable) return null;
|
|
225
|
+
if (captureFormSubmits && !anchor && isSubmitControl(actionable)) return null;
|
|
226
|
+
const rawHref = anchor?.getAttribute("href") ?? "";
|
|
227
|
+
const interactionType = anchor ? classifyHref(rawHref, { pageHost: pageHost(), bookingHosts, bookingPaths, socialHosts }) : "button";
|
|
228
|
+
const resolved = anchor && !CONTACT_SCHEMES.some((s) => rawHref.trim().toLowerCase().startsWith(s)) ? anchor.href || rawHref : rawHref;
|
|
229
|
+
return compose(actionable, interactionType, resolved);
|
|
230
|
+
}
|
|
231
|
+
function buildSubmit(form) {
|
|
232
|
+
const control = form.querySelector('button[type="submit"], input[type="submit"]') ?? form.querySelector("button") ?? form;
|
|
233
|
+
return compose(control, "form_submit", "");
|
|
234
|
+
}
|
|
235
|
+
function emitDeduped(event) {
|
|
236
|
+
const key = `${event.properties.interaction_type}|${event.properties.href}|${event.properties.label}`;
|
|
237
|
+
const now = Date.now();
|
|
238
|
+
if (key === lastKey && now - lastAt < DEDUPE_WINDOW_MS) return null;
|
|
239
|
+
lastKey = key;
|
|
240
|
+
lastAt = now;
|
|
241
|
+
emit(event);
|
|
242
|
+
return event;
|
|
243
|
+
}
|
|
244
|
+
function capture(element) {
|
|
245
|
+
const event = build(element);
|
|
246
|
+
if (!event) return null;
|
|
247
|
+
return emitDeduped(event);
|
|
248
|
+
}
|
|
249
|
+
const onClick = (event) => {
|
|
250
|
+
if (requireTrusted && !event.isTrusted) return;
|
|
251
|
+
const target = event.target;
|
|
252
|
+
if (!target || !target.closest) return;
|
|
253
|
+
try {
|
|
254
|
+
capture(target);
|
|
255
|
+
} catch {
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
const onSubmit = (event) => {
|
|
259
|
+
if (requireTrusted && !event.isTrusted) return;
|
|
260
|
+
const form = event.target;
|
|
261
|
+
if (!form || !form.tagName) return;
|
|
262
|
+
try {
|
|
263
|
+
emitDeduped(buildSubmit(form));
|
|
264
|
+
} catch {
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
const tracker = {
|
|
268
|
+
start() {
|
|
269
|
+
if (listening) return;
|
|
270
|
+
const d = doc();
|
|
271
|
+
if (!d) return;
|
|
272
|
+
if (respectDoNotTrack && doNotTrackEnabled()) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (boundDocuments.has(d) && !force) {
|
|
276
|
+
if (!warnedDoubleBind) {
|
|
277
|
+
warnedDoubleBind = true;
|
|
278
|
+
console.warn(
|
|
279
|
+
"[dcs-conversion] start() REFUSED: a delegated conversion listener is already bound to this document. Binding a second one counts every click twice \u2014 the defect class that put 52% duplicate page views in a live owner report (C-288). Since C-326 the tracker auto-installs with @duffcloudservices/cms, so a manual useConversionTracking() in App.vue is no longer needed; remove it, or pass { force: true } if you have genuinely torn the first one down."
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
d.addEventListener("click", onClick, { capture: true });
|
|
285
|
+
if (captureFormSubmits) d.addEventListener("submit", onSubmit, { capture: true });
|
|
286
|
+
listening = true;
|
|
287
|
+
boundDocuments.add(d);
|
|
288
|
+
registeredTrackers.add(tracker);
|
|
289
|
+
},
|
|
290
|
+
stop() {
|
|
291
|
+
const d = doc();
|
|
292
|
+
if (d && listening) {
|
|
293
|
+
d.removeEventListener("click", onClick, { capture: true });
|
|
294
|
+
if (captureFormSubmits) d.removeEventListener("submit", onSubmit, { capture: true });
|
|
295
|
+
boundDocuments.delete(d);
|
|
296
|
+
}
|
|
297
|
+
listening = false;
|
|
298
|
+
buffer = [];
|
|
299
|
+
registeredTrackers.delete(tracker);
|
|
300
|
+
},
|
|
301
|
+
attachSink(next) {
|
|
302
|
+
sink = next;
|
|
303
|
+
const pending = buffer;
|
|
304
|
+
buffer = [];
|
|
305
|
+
for (const event of pending) {
|
|
306
|
+
try {
|
|
307
|
+
next(event);
|
|
308
|
+
} catch {
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
buffered() {
|
|
313
|
+
return [...buffer];
|
|
314
|
+
},
|
|
315
|
+
capture
|
|
316
|
+
};
|
|
317
|
+
return tracker;
|
|
318
|
+
}
|
|
319
|
+
function useConversionTracking(options = {}) {
|
|
320
|
+
const tracker = createConversionTracker(options);
|
|
321
|
+
if (getCurrentInstance()) {
|
|
322
|
+
onMounted(() => tracker.start());
|
|
323
|
+
onBeforeUnmount(() => tracker.stop());
|
|
324
|
+
} else {
|
|
325
|
+
tracker.start();
|
|
326
|
+
}
|
|
327
|
+
return tracker;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// src/conversionAutoInstall.ts
|
|
331
|
+
var installed = null;
|
|
332
|
+
function conversionWindow() {
|
|
333
|
+
return typeof window !== "undefined" ? window : null;
|
|
334
|
+
}
|
|
335
|
+
function conversionOptedOut() {
|
|
336
|
+
const w = conversionWindow();
|
|
337
|
+
if (w?.__dcsConversionOptOut === true) return true;
|
|
338
|
+
if (typeof document !== "undefined") {
|
|
339
|
+
const flag = document.documentElement?.getAttribute("data-dcs-analytics");
|
|
340
|
+
if (flag === "off" || flag === "false") return true;
|
|
341
|
+
}
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
function inFrame() {
|
|
345
|
+
const w = conversionWindow();
|
|
346
|
+
if (!w) return false;
|
|
347
|
+
try {
|
|
348
|
+
return w.parent !== w || w.top !== w.self;
|
|
349
|
+
} catch {
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
function gtagSink(event) {
|
|
354
|
+
const w = conversionWindow();
|
|
355
|
+
if (typeof w?.gtag !== "function") return;
|
|
356
|
+
w.gtag("event", event.name, { ...event.properties });
|
|
357
|
+
}
|
|
358
|
+
function installConversionCapture(options = {}) {
|
|
359
|
+
const skip = (reason) => ({
|
|
360
|
+
installed: false,
|
|
361
|
+
reason,
|
|
362
|
+
tracker: null
|
|
363
|
+
});
|
|
364
|
+
if (typeof document === "undefined") return skip("no-document");
|
|
365
|
+
const w = conversionWindow();
|
|
366
|
+
if (installed || w?.__dcsConversionInstalled === true) return skip("already-installed");
|
|
367
|
+
if (inFrame()) return skip("in-frame");
|
|
368
|
+
if ((options.respectDoNotTrack ?? true) && doNotTrackEnabled()) return skip("do-not-track");
|
|
369
|
+
if (conversionOptedOut()) return skip("opted-out");
|
|
370
|
+
const useGtag = options.gtag ?? true;
|
|
371
|
+
const tracker = createConversionTracker({
|
|
372
|
+
...options,
|
|
373
|
+
sink: void 0,
|
|
374
|
+
mirrors: useGtag ? [gtagSink] : []
|
|
375
|
+
});
|
|
376
|
+
tracker.start();
|
|
377
|
+
installed = tracker;
|
|
378
|
+
if (w) {
|
|
379
|
+
w.__dcsConversionInstalled = true;
|
|
380
|
+
w.__dcsConversionAttach = (sink) => attachConversionSink(sink);
|
|
381
|
+
}
|
|
382
|
+
return { installed: true, reason: null, tracker };
|
|
383
|
+
}
|
|
384
|
+
function installedConversionTracker() {
|
|
385
|
+
return installed;
|
|
386
|
+
}
|
|
387
|
+
function resetConversionCapture() {
|
|
388
|
+
installed?.stop();
|
|
389
|
+
installed = null;
|
|
390
|
+
const w = conversionWindow();
|
|
391
|
+
if (w) {
|
|
392
|
+
delete w.__dcsConversionInstalled;
|
|
393
|
+
delete w.__dcsConversionAttach;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
9
396
|
var fetchCache = /* @__PURE__ */ new Map();
|
|
10
397
|
function getBuildTimeContent() {
|
|
11
398
|
try {
|
|
@@ -102,7 +489,7 @@ function useTextContent(config) {
|
|
|
102
489
|
error.value = null;
|
|
103
490
|
try {
|
|
104
491
|
const url = `${apiBaseUrl}/api/v1/pages/${pageSlug}/text`;
|
|
105
|
-
const response = await
|
|
492
|
+
const response = await platformFetch(url, {
|
|
106
493
|
headers: {
|
|
107
494
|
Accept: "application/json"
|
|
108
495
|
}
|
|
@@ -163,6 +550,15 @@ function getBuildTimeSeo() {
|
|
|
163
550
|
}
|
|
164
551
|
return void 0;
|
|
165
552
|
}
|
|
553
|
+
var HEAD_OVERRIDE_REFUSED_MESSAGE = "[dcs-seo] applyHead() was called WITH AN ARGUMENT and the argument was IGNORED. .dcs/seo.yaml is the only writer of title/description/keywords/canonical/OG/Twitter/JSON-LD (head-authority contract, C-356). A hardcoded value here overwrites the owner-approved baked head for Google and every human while non-JS crawlers keep receiving the approved one. Move the value into .dcs/seo.yaml and call applyHead() with no arguments.";
|
|
554
|
+
function reportHeadOverrideRefused(pageSlug, refused) {
|
|
555
|
+
const keys = refused && typeof refused === "object" ? Object.keys(refused).join(", ") : String(refused);
|
|
556
|
+
console.error(
|
|
557
|
+
`${HEAD_OVERRIDE_REFUSED_MESSAGE}
|
|
558
|
+
page slug: ${pageSlug}
|
|
559
|
+
ignored keys: ${keys}`
|
|
560
|
+
);
|
|
561
|
+
}
|
|
166
562
|
function useSEO(pageSlug, pagePath) {
|
|
167
563
|
const seoConfig = getBuildTimeSeo();
|
|
168
564
|
const hasBuildTimeSeo = seoConfig !== void 0;
|
|
@@ -175,14 +571,11 @@ function useSEO(pageSlug, pagePath) {
|
|
|
175
571
|
function getCanonical() {
|
|
176
572
|
return config.value.canonical;
|
|
177
573
|
}
|
|
178
|
-
function
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
schemas: overrides?.schemas,
|
|
184
|
-
meta: overrides?.meta
|
|
185
|
-
});
|
|
574
|
+
function applyHeadImpl(...refused) {
|
|
575
|
+
if (refused.length > 0 && refused[0] !== void 0 && refused[0] !== null) {
|
|
576
|
+
reportHeadOverrideRefused(pageSlug, refused[0]);
|
|
577
|
+
}
|
|
578
|
+
const { title, meta, link, script } = buildHeadTags(pageSlug, pagePath, seoConfig);
|
|
186
579
|
useHead({
|
|
187
580
|
title,
|
|
188
581
|
meta,
|
|
@@ -190,6 +583,7 @@ function useSEO(pageSlug, pagePath) {
|
|
|
190
583
|
script
|
|
191
584
|
});
|
|
192
585
|
}
|
|
586
|
+
const applyHead = applyHeadImpl;
|
|
193
587
|
return {
|
|
194
588
|
config,
|
|
195
589
|
applyHead,
|
|
@@ -233,7 +627,7 @@ function useReleaseNotes(version, options = {}) {
|
|
|
233
627
|
error.value = null;
|
|
234
628
|
try {
|
|
235
629
|
const url = `${apiBaseUrl}/api/v1/release-notes/${version}`;
|
|
236
|
-
const response = await
|
|
630
|
+
const response = await platformFetch(url, {
|
|
237
631
|
headers: {
|
|
238
632
|
Accept: "application/json"
|
|
239
633
|
}
|
|
@@ -313,7 +707,7 @@ function useSiteVersion(options = {}) {
|
|
|
313
707
|
isLoading.value = true;
|
|
314
708
|
try {
|
|
315
709
|
const url = `${apiBaseUrl}/api/v1/release-notes/latest`;
|
|
316
|
-
const response = await
|
|
710
|
+
const response = await platformFetch(url, {
|
|
317
711
|
headers: {
|
|
318
712
|
Accept: "application/json"
|
|
319
713
|
}
|
|
@@ -540,13 +934,16 @@ async function fetchSiteVisitorSession(options = {}) {
|
|
|
540
934
|
const base = apiBase(options.apiBaseUrl);
|
|
541
935
|
let response;
|
|
542
936
|
try {
|
|
543
|
-
response = await
|
|
937
|
+
response = await platformFetch(`${base}/site-auth/session`, {
|
|
544
938
|
method: "GET",
|
|
545
939
|
credentials: "include",
|
|
546
940
|
headers: { Accept: "application/json" },
|
|
547
941
|
signal: options.signal
|
|
548
942
|
});
|
|
549
|
-
} catch {
|
|
943
|
+
} catch (e) {
|
|
944
|
+
if (isPlatformFetchError(e)) {
|
|
945
|
+
return { visitor: null, authenticated: false, apiUnreachable: true };
|
|
946
|
+
}
|
|
550
947
|
return SIGNED_OUT;
|
|
551
948
|
}
|
|
552
949
|
if (!response.ok) {
|
|
@@ -565,12 +962,14 @@ function useSiteVisitorSession(options = {}) {
|
|
|
565
962
|
const { fetchOnMount = true, ...fetchOptions } = options;
|
|
566
963
|
const visitor = ref(null);
|
|
567
964
|
const isLoading = ref(false);
|
|
965
|
+
const apiUnreachable = ref(false);
|
|
568
966
|
const isAuthenticated = computed(() => visitor.value !== null);
|
|
569
967
|
async function refresh() {
|
|
570
968
|
isLoading.value = true;
|
|
571
969
|
try {
|
|
572
970
|
const result = await fetchSiteVisitorSession(fetchOptions);
|
|
573
971
|
visitor.value = result.visitor;
|
|
972
|
+
apiUnreachable.value = result.apiUnreachable === true;
|
|
574
973
|
} finally {
|
|
575
974
|
isLoading.value = false;
|
|
576
975
|
}
|
|
@@ -580,9 +979,10 @@ function useSiteVisitorSession(options = {}) {
|
|
|
580
979
|
void refresh();
|
|
581
980
|
});
|
|
582
981
|
}
|
|
583
|
-
return { visitor, isAuthenticated, isLoading, refresh };
|
|
982
|
+
return { visitor, isAuthenticated, isLoading, apiUnreachable, refresh };
|
|
584
983
|
}
|
|
984
|
+
installConversionCapture();
|
|
585
985
|
|
|
586
|
-
export { createSiteSEO, fetchSiteVisitorSession, useMediaCarousel, useReleaseNotes, useResponsiveImage, useReviewContent, useSEO, useSiteVersion, useSiteVisitorSession, useTextContent };
|
|
986
|
+
export { CONVERSION_CAPTURE_VERSION, CONVERSION_INTERACTION_TYPES, DEFAULT_BOOKING_HOSTS, DEFAULT_SOCIAL_HOSTS, attachConversionSink, classifyHref, conversionOptedOut, createConversionTracker, createSiteSEO, doNotTrackEnabled, fetchSiteVisitorSession, hashTarget, hrefScheme, inFrame, installConversionCapture, installedConversionTracker, isConversionType, isSubmitControl, redactHref, resetConversionCapture, useConversionTracking, useMediaCarousel, useReleaseNotes, useResponsiveImage, useReviewContent, useSEO, useSiteVersion, useSiteVisitorSession, useTextContent };
|
|
587
987
|
//# sourceMappingURL=index.js.map
|
|
588
988
|
//# sourceMappingURL=index.js.map
|