@skhema/web-component 0.0.16 → 0.0.18
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/components/SkhemaElement.d.ts +10 -0
- package/dist/components/SkhemaElement.d.ts.map +1 -1
- package/dist/index.cjs +380 -59
- package/dist/index.cjs.map +1 -1
- package/dist/index.es.js +380 -59
- package/dist/index.es.js.map +1 -1
- package/dist/utils/analytics.d.ts.map +1 -1
- package/dist/web-component.min.js +1 -1
- package/dist/web-component.min.js.map +1 -1
- package/package.json +1 -1
package/dist/index.es.js
CHANGED
|
@@ -8,53 +8,202 @@ function toUrlSafeBase64(str) {
|
|
|
8
8
|
);
|
|
9
9
|
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
10
10
|
}
|
|
11
|
-
|
|
11
|
+
const TRACKING_COOKIE_NAME = "_sk";
|
|
12
|
+
const TRACKING_EXPIRY_HOURS = 24;
|
|
13
|
+
const MAX_TRACKED_ITEMS = 50;
|
|
14
|
+
function getTrackedEmbeds() {
|
|
12
15
|
try {
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
|
|
16
|
+
const cookie = document.cookie.split("; ").find((row) => row.startsWith(`${TRACKING_COOKIE_NAME}=`));
|
|
17
|
+
if (!cookie) return [];
|
|
18
|
+
const data = JSON.parse(decodeURIComponent(cookie.split("=")[1]));
|
|
19
|
+
const now = Date.now();
|
|
20
|
+
const cutoff = now - TRACKING_EXPIRY_HOURS * 60 * 60 * 1e3;
|
|
21
|
+
return data.filter((item) => item.timestamp > cutoff);
|
|
22
|
+
} catch {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function setTrackedEmbeds(tracked) {
|
|
27
|
+
try {
|
|
28
|
+
const limited = tracked.slice(-MAX_TRACKED_ITEMS);
|
|
29
|
+
const expires = new Date(
|
|
30
|
+
Date.now() + TRACKING_EXPIRY_HOURS * 60 * 60 * 1e3
|
|
31
|
+
);
|
|
32
|
+
document.cookie = `${TRACKING_COOKIE_NAME}=${encodeURIComponent(
|
|
33
|
+
JSON.stringify(limited)
|
|
34
|
+
)}; expires=${expires.toUTCString()}; path=/; SameSite=Lax`;
|
|
35
|
+
} catch {
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function hasBeenTracked(contentHash) {
|
|
39
|
+
const tracked = getTrackedEmbeds();
|
|
40
|
+
return tracked.some((item) => item.contentHash === contentHash);
|
|
41
|
+
}
|
|
42
|
+
function markAsTracked(contentHash) {
|
|
43
|
+
const tracked = getTrackedEmbeds();
|
|
44
|
+
tracked.push({
|
|
45
|
+
contentHash,
|
|
46
|
+
timestamp: Date.now()
|
|
47
|
+
});
|
|
48
|
+
setTrackedEmbeds(tracked);
|
|
49
|
+
}
|
|
50
|
+
class AnalyticsBatcher {
|
|
51
|
+
constructor() {
|
|
52
|
+
this.batch = { embeds: [], clicks: [] };
|
|
53
|
+
this.batchTimeout = null;
|
|
54
|
+
this.BATCH_DELAY = 2e3;
|
|
55
|
+
this.MAX_BATCH_SIZE = 10;
|
|
56
|
+
}
|
|
57
|
+
addEmbedLoad(analytics) {
|
|
58
|
+
this.batch.embeds.push(analytics);
|
|
59
|
+
this.scheduleBatchSend();
|
|
60
|
+
}
|
|
61
|
+
addClick(contentData) {
|
|
62
|
+
this.batch.clicks.push(contentData);
|
|
63
|
+
this.scheduleBatchSend();
|
|
64
|
+
}
|
|
65
|
+
scheduleBatchSend() {
|
|
66
|
+
if (this.batchTimeout) {
|
|
67
|
+
clearTimeout(this.batchTimeout);
|
|
68
|
+
}
|
|
69
|
+
if (this.batch.embeds.length >= this.MAX_BATCH_SIZE || this.batch.clicks.length >= this.MAX_BATCH_SIZE) {
|
|
70
|
+
this.sendBatch();
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
this.batchTimeout = window.setTimeout(() => {
|
|
74
|
+
this.sendBatch();
|
|
75
|
+
}, this.BATCH_DELAY);
|
|
76
|
+
}
|
|
77
|
+
async sendBatch() {
|
|
78
|
+
if (this.batchTimeout) {
|
|
79
|
+
clearTimeout(this.batchTimeout);
|
|
80
|
+
this.batchTimeout = null;
|
|
81
|
+
}
|
|
82
|
+
const currentBatch = { ...this.batch };
|
|
83
|
+
this.batch = { embeds: [], clicks: [] };
|
|
84
|
+
if (currentBatch.embeds.length === 0 && currentBatch.clicks.length === 0) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (currentBatch.embeds.length > 0) {
|
|
88
|
+
await this.sendEmbeds(currentBatch.embeds);
|
|
89
|
+
}
|
|
90
|
+
if (currentBatch.clicks.length > 0) {
|
|
91
|
+
await this.sendClicks(currentBatch.clicks);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
async sendEmbeds(embeds) {
|
|
95
|
+
for (const embed of embeds) {
|
|
96
|
+
try {
|
|
97
|
+
const data = new URLSearchParams({
|
|
98
|
+
contributor_id: embed.contributorId,
|
|
99
|
+
element_type: embed.elementType,
|
|
100
|
+
content_hash: embed.contentHash,
|
|
101
|
+
content: toUrlSafeBase64(embed.content),
|
|
102
|
+
page_url: embed.pageUrl,
|
|
103
|
+
page_title: embed.pageTitle || "",
|
|
104
|
+
timestamp: embed.timestamp.toString(),
|
|
105
|
+
user_agent: embed.userAgent || ""
|
|
106
|
+
});
|
|
107
|
+
if (navigator.sendBeacon) {
|
|
108
|
+
navigator.sendBeacon(
|
|
109
|
+
"https://api.skhema.com/api:XGdoUqHx/component/embed",
|
|
110
|
+
data
|
|
111
|
+
);
|
|
112
|
+
} else {
|
|
113
|
+
await sendWithRetry(
|
|
114
|
+
"https://api.skhema.com/api:XGdoUqHx/component/embed",
|
|
115
|
+
data,
|
|
116
|
+
"urlencoded"
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
} catch (error) {
|
|
120
|
+
console.debug("Embed tracking failed:", error);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async sendClicks(clicks) {
|
|
125
|
+
for (const click of clicks) {
|
|
126
|
+
try {
|
|
127
|
+
const data = {
|
|
128
|
+
contributor_id: click.contributor_id,
|
|
129
|
+
element_type: click.element_type,
|
|
130
|
+
content_hash: click.content_hash,
|
|
131
|
+
source_url: click.source_url,
|
|
132
|
+
timestamp: click.timestamp
|
|
133
|
+
};
|
|
134
|
+
await sendWithRetry(
|
|
135
|
+
"https://api.skhema.com/api:XGdoUqHx/component/click",
|
|
136
|
+
data,
|
|
137
|
+
"json"
|
|
138
|
+
);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.debug("Click tracking failed:", error);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Ensure batch is sent when page unloads
|
|
145
|
+
flush() {
|
|
146
|
+
if (this.batch.embeds.length > 0 || this.batch.clicks.length > 0) {
|
|
147
|
+
this.sendBatch();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const analyticsBatcher = new AnalyticsBatcher();
|
|
152
|
+
if (typeof window !== "undefined") {
|
|
153
|
+
window.addEventListener("beforeunload", () => {
|
|
154
|
+
analyticsBatcher.flush();
|
|
155
|
+
});
|
|
156
|
+
document.addEventListener("visibilitychange", () => {
|
|
157
|
+
if (document.visibilityState === "hidden") {
|
|
158
|
+
analyticsBatcher.flush();
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
async function sendWithRetry(url, data, contentType = "json", maxRetries = 3) {
|
|
163
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
164
|
+
try {
|
|
165
|
+
const options = {
|
|
30
166
|
method: "POST",
|
|
31
|
-
body: data,
|
|
32
167
|
credentials: "omit",
|
|
33
168
|
keepalive: true
|
|
34
|
-
}
|
|
35
|
-
|
|
169
|
+
};
|
|
170
|
+
if (contentType === "json") {
|
|
171
|
+
options.headers = { "Content-Type": "application/json" };
|
|
172
|
+
options.body = JSON.stringify(data);
|
|
173
|
+
} else {
|
|
174
|
+
options.body = data;
|
|
175
|
+
}
|
|
176
|
+
const response = await fetch(url, options);
|
|
177
|
+
if (response.ok) return;
|
|
178
|
+
if (response.status >= 400 && response.status < 500) {
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
} catch (error) {
|
|
182
|
+
if (attempt === maxRetries - 1) {
|
|
183
|
+
console.debug("Analytics failed after retries:", error);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
await new Promise(
|
|
188
|
+
(resolve) => setTimeout(resolve, Math.pow(2, attempt) * 1e3)
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
async function trackEmbedLoad(analytics) {
|
|
193
|
+
try {
|
|
194
|
+
if (hasBeenTracked(analytics.contentHash)) {
|
|
195
|
+
console.debug("Embed already tracked, skipping:", analytics.contentHash);
|
|
196
|
+
return;
|
|
36
197
|
}
|
|
198
|
+
markAsTracked(analytics.contentHash);
|
|
199
|
+
analyticsBatcher.addEmbedLoad(analytics);
|
|
37
200
|
} catch (error) {
|
|
38
201
|
console.debug("Analytics tracking failed:", error);
|
|
39
202
|
}
|
|
40
203
|
}
|
|
41
204
|
async function trackClick(contentData) {
|
|
42
205
|
try {
|
|
43
|
-
|
|
44
|
-
contributor_id: contentData.contributor_id,
|
|
45
|
-
element_type: contentData.element_type,
|
|
46
|
-
content_hash: contentData.content_hash,
|
|
47
|
-
source_url: contentData.source_url,
|
|
48
|
-
timestamp: contentData.timestamp
|
|
49
|
-
};
|
|
50
|
-
fetch("https://api.skhema.com/api:XGdoUqHx/component/click", {
|
|
51
|
-
method: "POST",
|
|
52
|
-
headers: { "Content-Type": "application/json" },
|
|
53
|
-
body: JSON.stringify(data),
|
|
54
|
-
credentials: "omit",
|
|
55
|
-
keepalive: true
|
|
56
|
-
}).catch(() => {
|
|
57
|
-
});
|
|
206
|
+
analyticsBatcher.addClick(contentData);
|
|
58
207
|
} catch (error) {
|
|
59
208
|
console.debug("Click tracking failed:", error);
|
|
60
209
|
}
|
|
@@ -264,8 +413,9 @@ const styles = `
|
|
|
264
413
|
color: var(--skhema-text);
|
|
265
414
|
}
|
|
266
415
|
|
|
267
|
-
|
|
268
|
-
|
|
416
|
+
/* Dark mode styles - applied via data-theme attribute */
|
|
417
|
+
.skhema-insight-card[data-theme="dark"],
|
|
418
|
+
.skhema-skeleton[data-theme="dark"] {
|
|
269
419
|
--skhema-bg: hsl(222.2 84% 4.9%);
|
|
270
420
|
--skhema-card: hsl(222.2 84% 4.9%);
|
|
271
421
|
--skhema-border: hsl(217.2 32.6% 17.5%);
|
|
@@ -484,26 +634,81 @@ const styles = `
|
|
|
484
634
|
padding-left: 16px;
|
|
485
635
|
}
|
|
486
636
|
|
|
487
|
-
/*
|
|
488
|
-
.skhema-
|
|
489
|
-
background: var(--skhema-
|
|
637
|
+
/* Skeleton loading state */
|
|
638
|
+
.skhema-skeleton {
|
|
639
|
+
background: var(--skhema-card);
|
|
490
640
|
border: 1px solid var(--skhema-border);
|
|
491
|
-
|
|
641
|
+
border-radius: calc(var(--skhema-radius) * 2);
|
|
642
|
+
padding: 16px;
|
|
643
|
+
box-shadow: var(--skhema-shadow);
|
|
644
|
+
max-width: 600px;
|
|
645
|
+
margin: 8px 0;
|
|
646
|
+
animation: skeletonPulse 1.5s ease-in-out infinite;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
.skhema-skeleton-header {
|
|
650
|
+
display: flex;
|
|
651
|
+
align-items: center;
|
|
652
|
+
gap: 12px;
|
|
653
|
+
margin-bottom: 12px;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
.skhema-skeleton-avatar {
|
|
657
|
+
width: 32px;
|
|
658
|
+
height: 32px;
|
|
659
|
+
border-radius: 50%;
|
|
660
|
+
background: linear-gradient(90deg,
|
|
661
|
+
var(--skhema-border) 25%,
|
|
662
|
+
var(--skhema-accent) 50%,
|
|
663
|
+
var(--skhema-border) 75%);
|
|
664
|
+
background-size: 200% 100%;
|
|
665
|
+
animation: shimmer 1.5s infinite;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
.skhema-skeleton-text {
|
|
669
|
+
flex: 1;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
.skhema-skeleton-line {
|
|
673
|
+
height: 12px;
|
|
674
|
+
background: linear-gradient(90deg,
|
|
675
|
+
var(--skhema-border) 25%,
|
|
676
|
+
var(--skhema-accent) 50%,
|
|
677
|
+
var(--skhema-border) 75%);
|
|
678
|
+
background-size: 200% 100%;
|
|
679
|
+
animation: shimmer 1.5s infinite;
|
|
492
680
|
border-radius: var(--skhema-radius);
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
681
|
+
margin: 6px 0;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
.skhema-skeleton-line.short {
|
|
685
|
+
width: 40%;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
.skhema-skeleton-line.medium {
|
|
689
|
+
width: 70%;
|
|
496
690
|
}
|
|
497
691
|
|
|
498
|
-
.skhema-
|
|
499
|
-
|
|
500
|
-
animation: loading 1.5s infinite;
|
|
692
|
+
.skhema-skeleton-content {
|
|
693
|
+
margin: 16px 0;
|
|
501
694
|
}
|
|
502
695
|
|
|
503
|
-
@keyframes
|
|
504
|
-
0%,
|
|
505
|
-
|
|
506
|
-
|
|
696
|
+
@keyframes skeletonPulse {
|
|
697
|
+
0%, 100% {
|
|
698
|
+
opacity: 1;
|
|
699
|
+
}
|
|
700
|
+
50% {
|
|
701
|
+
opacity: 0.8;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
@keyframes shimmer {
|
|
706
|
+
0% {
|
|
707
|
+
background-position: -200% 0;
|
|
708
|
+
}
|
|
709
|
+
100% {
|
|
710
|
+
background-position: 200% 0;
|
|
711
|
+
}
|
|
507
712
|
}
|
|
508
713
|
|
|
509
714
|
/* Responsive design */
|
|
@@ -555,7 +760,11 @@ class SkhemaElement extends HTMLElement {
|
|
|
555
760
|
super();
|
|
556
761
|
this.contentData = null;
|
|
557
762
|
this.componentConnected = false;
|
|
763
|
+
this.hasTrackedLoad = false;
|
|
764
|
+
this.themeObserver = null;
|
|
765
|
+
this.mediaQueryListener = null;
|
|
558
766
|
this.shadow = this.attachShadow({ mode: "closed" });
|
|
767
|
+
this.renderSkeleton();
|
|
559
768
|
}
|
|
560
769
|
static get observedAttributes() {
|
|
561
770
|
return [
|
|
@@ -571,12 +780,19 @@ class SkhemaElement extends HTMLElement {
|
|
|
571
780
|
if (this.componentConnected) return;
|
|
572
781
|
this.componentConnected = true;
|
|
573
782
|
try {
|
|
574
|
-
this.
|
|
575
|
-
|
|
783
|
+
this.addPreconnectHints();
|
|
784
|
+
requestAnimationFrame(() => {
|
|
785
|
+
this.render();
|
|
786
|
+
this.trackLoad();
|
|
787
|
+
this.setupThemeListeners();
|
|
788
|
+
});
|
|
576
789
|
} catch (error) {
|
|
577
790
|
this.renderError("Failed to initialize component", error);
|
|
578
791
|
}
|
|
579
792
|
}
|
|
793
|
+
disconnectedCallback() {
|
|
794
|
+
this.cleanupThemeListeners();
|
|
795
|
+
}
|
|
580
796
|
attributeChangedCallback(_name, oldValue, newValue) {
|
|
581
797
|
if (oldValue !== newValue && this.componentConnected) {
|
|
582
798
|
this.render();
|
|
@@ -627,7 +843,8 @@ class SkhemaElement extends HTMLElement {
|
|
|
627
843
|
element_type,
|
|
628
844
|
contributor_id
|
|
629
845
|
);
|
|
630
|
-
const
|
|
846
|
+
const themeAttribute = this.getAttribute("theme") || "auto";
|
|
847
|
+
const actualTheme = this.getActualTheme(themeAttribute);
|
|
631
848
|
const displayName = this.formatContributorName(contributor_id);
|
|
632
849
|
const initials = this.getInitials(displayName);
|
|
633
850
|
const ariaAttrs = createAriaAttributes(element_type);
|
|
@@ -636,8 +853,8 @@ class SkhemaElement extends HTMLElement {
|
|
|
636
853
|
});
|
|
637
854
|
this.shadow.innerHTML = `
|
|
638
855
|
<style>${styles}</style>
|
|
639
|
-
|
|
640
|
-
<div class="skhema-insight-card" data-theme="${
|
|
856
|
+
|
|
857
|
+
<div class="skhema-insight-card" data-theme="${actualTheme}">
|
|
641
858
|
<div class="skhema-header">
|
|
642
859
|
<div class="skhema-contributor">
|
|
643
860
|
<div class="skhema-avatar" title="${displayName}">
|
|
@@ -680,17 +897,78 @@ class SkhemaElement extends HTMLElement {
|
|
|
680
897
|
});
|
|
681
898
|
}
|
|
682
899
|
}
|
|
900
|
+
getActualTheme(themeAttribute) {
|
|
901
|
+
if (themeAttribute === "light" || themeAttribute === "dark") {
|
|
902
|
+
return themeAttribute;
|
|
903
|
+
}
|
|
904
|
+
const htmlElement = document.documentElement;
|
|
905
|
+
const bodyElement = document.body;
|
|
906
|
+
const htmlTheme = htmlElement.getAttribute("data-theme") || htmlElement.getAttribute("theme") || htmlElement.className.match(/theme-(\w+)/)?.[1];
|
|
907
|
+
const bodyTheme = bodyElement.getAttribute("data-theme") || bodyElement.getAttribute("theme") || bodyElement.className.match(/theme-(\w+)/)?.[1];
|
|
908
|
+
const hasDarkClass = htmlElement.classList.contains("dark") || bodyElement.classList.contains("dark") || htmlElement.classList.contains("dark-mode") || bodyElement.classList.contains("dark-mode");
|
|
909
|
+
if (hasDarkClass || htmlTheme === "dark" || bodyTheme === "dark") {
|
|
910
|
+
return "dark";
|
|
911
|
+
}
|
|
912
|
+
const computedStyles = window.getComputedStyle(htmlElement);
|
|
913
|
+
const colorScheme = computedStyles.getPropertyValue("color-scheme");
|
|
914
|
+
if (colorScheme && colorScheme.includes("dark")) {
|
|
915
|
+
return "dark";
|
|
916
|
+
}
|
|
917
|
+
if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
|
918
|
+
return "dark";
|
|
919
|
+
}
|
|
920
|
+
return "light";
|
|
921
|
+
}
|
|
683
922
|
formatContributorName(contributorId) {
|
|
684
923
|
return contributorId.split(/[_-]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
|
|
685
924
|
}
|
|
686
925
|
getInitials(name) {
|
|
687
926
|
return name.split(" ").map((word) => word.charAt(0)).join("").toUpperCase().substring(0, 2);
|
|
688
927
|
}
|
|
928
|
+
addPreconnectHints() {
|
|
929
|
+
if (document.querySelector('link[rel="preconnect"][href*="api.skhema.com"]')) {
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
try {
|
|
933
|
+
const preconnectApi = document.createElement("link");
|
|
934
|
+
preconnectApi.rel = "preconnect";
|
|
935
|
+
preconnectApi.href = "https://api.skhema.com";
|
|
936
|
+
document.head.appendChild(preconnectApi);
|
|
937
|
+
const dnsPrefetch = document.createElement("link");
|
|
938
|
+
dnsPrefetch.rel = "dns-prefetch";
|
|
939
|
+
dnsPrefetch.href = "https://skhema.com";
|
|
940
|
+
document.head.appendChild(dnsPrefetch);
|
|
941
|
+
} catch (error) {
|
|
942
|
+
console.debug("Failed to add preconnect hints:", error);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
renderSkeleton() {
|
|
946
|
+
const themeAttribute = this.getAttribute("theme") || "auto";
|
|
947
|
+
const actualTheme = this.getActualTheme(themeAttribute);
|
|
948
|
+
this.shadow.innerHTML = `
|
|
949
|
+
<style>${styles}</style>
|
|
950
|
+
|
|
951
|
+
<div class="skhema-skeleton" data-theme="${actualTheme}">
|
|
952
|
+
<div class="skhema-skeleton-header">
|
|
953
|
+
<div class="skhema-skeleton-avatar"></div>
|
|
954
|
+
<div class="skhema-skeleton-text">
|
|
955
|
+
<div class="skhema-skeleton-line medium"></div>
|
|
956
|
+
<div class="skhema-skeleton-line short"></div>
|
|
957
|
+
</div>
|
|
958
|
+
</div>
|
|
959
|
+
<div class="skhema-skeleton-content">
|
|
960
|
+
<div class="skhema-skeleton-line"></div>
|
|
961
|
+
<div class="skhema-skeleton-line"></div>
|
|
962
|
+
<div class="skhema-skeleton-line medium"></div>
|
|
963
|
+
</div>
|
|
964
|
+
</div>
|
|
965
|
+
`;
|
|
966
|
+
}
|
|
689
967
|
renderError(title, errors) {
|
|
690
968
|
const errorList = Array.isArray(errors) ? errors : [String(errors)];
|
|
691
969
|
this.shadow.innerHTML = `
|
|
692
970
|
<style>${styles}</style>
|
|
693
|
-
|
|
971
|
+
|
|
694
972
|
<div class="skhema-insight-card">
|
|
695
973
|
<div class="skhema-error">
|
|
696
974
|
<div class="skhema-error-title">Skhema Component Error: ${title}</div>
|
|
@@ -727,7 +1005,10 @@ class SkhemaElement extends HTMLElement {
|
|
|
727
1005
|
document.body.appendChild(metaDiv);
|
|
728
1006
|
}
|
|
729
1007
|
async trackLoad() {
|
|
730
|
-
if (!shouldTrackAnalytics(this) || !this.contentData)
|
|
1008
|
+
if (!shouldTrackAnalytics(this) || !this.contentData || this.hasTrackedLoad) {
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
1011
|
+
this.hasTrackedLoad = true;
|
|
731
1012
|
const analytics = {
|
|
732
1013
|
contributorId: this.contentData.contributor_id,
|
|
733
1014
|
elementType: this.contentData.element_type,
|
|
@@ -765,6 +1046,46 @@ class SkhemaElement extends HTMLElement {
|
|
|
765
1046
|
refresh() {
|
|
766
1047
|
this.render();
|
|
767
1048
|
}
|
|
1049
|
+
setupThemeListeners() {
|
|
1050
|
+
const themeAttribute = this.getAttribute("theme");
|
|
1051
|
+
if (themeAttribute === "auto" || !themeAttribute) {
|
|
1052
|
+
if (window.matchMedia) {
|
|
1053
|
+
this.mediaQueryListener = window.matchMedia(
|
|
1054
|
+
"(prefers-color-scheme: dark)"
|
|
1055
|
+
);
|
|
1056
|
+
const handleThemeChange = () => this.updateTheme();
|
|
1057
|
+
this.mediaQueryListener.addEventListener("change", handleThemeChange);
|
|
1058
|
+
}
|
|
1059
|
+
this.themeObserver = new MutationObserver(() => this.updateTheme());
|
|
1060
|
+
this.themeObserver.observe(document.documentElement, {
|
|
1061
|
+
attributes: true,
|
|
1062
|
+
attributeFilter: ["class", "data-theme", "theme"]
|
|
1063
|
+
});
|
|
1064
|
+
this.themeObserver.observe(document.body, {
|
|
1065
|
+
attributes: true,
|
|
1066
|
+
attributeFilter: ["class", "data-theme", "theme"]
|
|
1067
|
+
});
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
cleanupThemeListeners() {
|
|
1071
|
+
if (this.themeObserver) {
|
|
1072
|
+
this.themeObserver.disconnect();
|
|
1073
|
+
this.themeObserver = null;
|
|
1074
|
+
}
|
|
1075
|
+
if (this.mediaQueryListener) {
|
|
1076
|
+
this.mediaQueryListener = null;
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
updateTheme() {
|
|
1080
|
+
const themeAttribute = this.getAttribute("theme") || "auto";
|
|
1081
|
+
if (themeAttribute === "auto") {
|
|
1082
|
+
const card = this.shadow.querySelector(".skhema-insight-card");
|
|
1083
|
+
if (card) {
|
|
1084
|
+
const newTheme = this.getActualTheme("auto");
|
|
1085
|
+
card.setAttribute("data-theme", newTheme);
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
768
1089
|
}
|
|
769
1090
|
function registerSkhemaElement() {
|
|
770
1091
|
if (typeof window !== "undefined" && !customElements.get("skhema-element")) {
|