@skhema/embed 0.1.8 → 0.1.10
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 +55 -0
- package/dist/components/SkhemaComponent.d.ts +0 -1
- package/dist/components/SkhemaComponent.d.ts.map +1 -1
- package/dist/components/SkhemaElement.d.ts.map +1 -1
- package/dist/embed.min.js +1 -1
- package/dist/embed.min.js.map +1 -1
- package/dist/index-DBeyneZT.cjs +215 -0
- package/dist/index-DBeyneZT.cjs.map +1 -0
- package/dist/index-jwx5CXLB.js +216 -0
- package/dist/index-jwx5CXLB.js.map +1 -0
- package/dist/index.cjs +113 -668
- package/dist/index.cjs.map +1 -1
- package/dist/index.es.js +83 -637
- package/dist/index.es.js.map +1 -1
- package/dist/render/index.d.ts +55 -0
- package/dist/render/index.d.ts.map +1 -0
- package/dist/render.cjs +7 -0
- package/dist/render.cjs.map +1 -0
- package/dist/render.d.ts +2 -0
- package/dist/render.es.js +7 -0
- package/dist/render.es.js.map +1 -0
- package/dist/styles/design-tokens.d.ts +32 -4
- package/dist/styles/design-tokens.d.ts.map +1 -1
- package/dist/tokens.cjs +16 -0
- package/dist/tokens.cjs.map +1 -0
- package/dist/tokens.d.ts +17 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.es.js +16 -0
- package/dist/tokens.es.js.map +1 -0
- package/dist/utils/color.d.ts +25 -0
- package/dist/utils/color.d.ts.map +1 -0
- package/dist/utils/sanitization.d.ts +7 -1
- package/dist/utils/sanitization.d.ts.map +1 -1
- package/dist/validation-DCw9rQeH.cjs +176 -0
- package/dist/validation-DCw9rQeH.cjs.map +1 -0
- package/dist/validation-Dt4L_df8.js +177 -0
- package/dist/validation-Dt4L_df8.js.map +1 -0
- package/package.json +20 -7
- package/dist/utils/author-attribution.d.ts +0 -16
- package/dist/utils/author-attribution.d.ts.map +0 -1
package/dist/index.es.js
CHANGED
|
@@ -1,159 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
bg: "oklch(0.65 0.06 25 / 0.15)",
|
|
5
|
-
text: "oklch(0.65 0.06 25)",
|
|
6
|
-
border: "oklch(0.65 0.06 25 / 0.3)"
|
|
7
|
-
},
|
|
8
|
-
method: {
|
|
9
|
-
bg: "oklch(0.6 0.06 250 / 0.15)",
|
|
10
|
-
text: "oklch(0.6 0.06 250)",
|
|
11
|
-
border: "oklch(0.6 0.06 250 / 0.3)"
|
|
12
|
-
},
|
|
13
|
-
initiatives: {
|
|
14
|
-
bg: "oklch(0.6 0.06 155 / 0.15)",
|
|
15
|
-
text: "oklch(0.6 0.06 155)",
|
|
16
|
-
border: "oklch(0.6 0.06 155 / 0.3)"
|
|
17
|
-
},
|
|
18
|
-
measures: {
|
|
19
|
-
bg: "oklch(0.6 0.06 300 / 0.15)",
|
|
20
|
-
text: "oklch(0.6 0.06 300)",
|
|
21
|
-
border: "oklch(0.6 0.06 300 / 0.3)"
|
|
22
|
-
},
|
|
23
|
-
support: {
|
|
24
|
-
bg: "oklch(0.65 0.06 65 / 0.15)",
|
|
25
|
-
text: "oklch(0.65 0.06 65)",
|
|
26
|
-
border: "oklch(0.65 0.06 65 / 0.3)"
|
|
27
|
-
}
|
|
28
|
-
};
|
|
29
|
-
const SHARED_CARD_STYLES = `
|
|
30
|
-
/* Monospace acronym badge */
|
|
31
|
-
.skhema-acronym-badge {
|
|
32
|
-
display: inline-flex;
|
|
33
|
-
align-items: center;
|
|
34
|
-
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
|
|
35
|
-
font-size: 10px;
|
|
36
|
-
font-weight: 600;
|
|
37
|
-
padding: 2px 6px;
|
|
38
|
-
border-radius: 2px;
|
|
39
|
-
letter-spacing: 0.02em;
|
|
40
|
-
flex-shrink: 0;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/* Footer attribution tagline */
|
|
44
|
-
.skhema-footer-attribution {
|
|
45
|
-
font-size: 11px;
|
|
46
|
-
color: var(--skhema-text-muted);
|
|
47
|
-
line-height: 1.4;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
.skhema-footer-attribution a {
|
|
51
|
-
color: var(--skhema-text-muted);
|
|
52
|
-
text-decoration: underline;
|
|
53
|
-
text-decoration-color: var(--skhema-border);
|
|
54
|
-
text-underline-offset: 2px;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
.skhema-footer-attribution a:hover {
|
|
58
|
-
color: var(--skhema-text);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/* Contributor line in footer */
|
|
62
|
-
.skhema-contributor-line {
|
|
63
|
-
display: flex;
|
|
64
|
-
align-items: center;
|
|
65
|
-
gap: 6px;
|
|
66
|
-
font-size: 12px;
|
|
67
|
-
color: var(--skhema-text-muted);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
.skhema-contributor-line svg {
|
|
71
|
-
width: 14px;
|
|
72
|
-
height: 14px;
|
|
73
|
-
flex-shrink: 0;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
.skhema-author-link {
|
|
77
|
-
color: inherit;
|
|
78
|
-
text-decoration: none;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
.skhema-author-link:hover {
|
|
82
|
-
color: var(--skhema-text);
|
|
83
|
-
text-decoration: underline;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/* Save button — primary brand color */
|
|
87
|
-
.skhema-save-btn {
|
|
88
|
-
display: inline-flex;
|
|
89
|
-
align-items: center;
|
|
90
|
-
gap: 6px;
|
|
91
|
-
background: var(--skhema-primary);
|
|
92
|
-
color: white;
|
|
93
|
-
border: none;
|
|
94
|
-
padding: 6px 14px;
|
|
95
|
-
border-radius: calc(var(--skhema-radius) + 2px);
|
|
96
|
-
font-size: 12px;
|
|
97
|
-
font-weight: 500;
|
|
98
|
-
text-decoration: none;
|
|
99
|
-
cursor: pointer;
|
|
100
|
-
transition: background 0.15s ease;
|
|
101
|
-
white-space: nowrap;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
.skhema-save-btn:hover {
|
|
105
|
-
background: var(--skhema-primary-hover);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
.skhema-save-btn:active {
|
|
109
|
-
background: var(--skhema-primary-pressed);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
.skhema-save-btn:focus {
|
|
113
|
-
outline: 2px solid var(--skhema-primary);
|
|
114
|
-
outline-offset: 2px;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
.skhema-save-btn::after {
|
|
118
|
-
content: '\\2192';
|
|
119
|
-
font-size: 13px;
|
|
120
|
-
}
|
|
121
|
-
`;
|
|
122
|
-
const USER_ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>`;
|
|
123
|
-
function readAuthorAttribution(element) {
|
|
124
|
-
const authorName = element.getAttribute("author-name") || element.getAttribute("contributor-name");
|
|
125
|
-
if (!authorName?.trim()) {
|
|
126
|
-
return null;
|
|
127
|
-
}
|
|
128
|
-
const authorSlug = element.getAttribute("author-slug")?.trim();
|
|
129
|
-
return {
|
|
130
|
-
authorName: authorName.trim(),
|
|
131
|
-
authorSlug: authorSlug || void 0
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
function escapeHtml(text) {
|
|
135
|
-
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
136
|
-
}
|
|
137
|
-
function formatAuthorAttributionHtml(authorName, authorSlug) {
|
|
138
|
-
const label = `By ${escapeHtml(authorName)}`;
|
|
139
|
-
if (authorSlug) {
|
|
140
|
-
return `<a href="https://skhema.com/contributors/${encodeURIComponent(authorSlug)}" class="skhema-author-link" target="_blank" rel="noopener noreferrer">${label}</a>`;
|
|
141
|
-
}
|
|
142
|
-
return `<span class="skhema-author-text">${label}</span>`;
|
|
143
|
-
}
|
|
144
|
-
function formatContributorIdFallback(contributorId) {
|
|
145
|
-
return contributorId.split(/[_-]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
|
|
146
|
-
}
|
|
147
|
-
function resolveAuthorFooterHtml(element, contributorId) {
|
|
148
|
-
const attribution = readAuthorAttribution(element);
|
|
149
|
-
if (attribution) {
|
|
150
|
-
return formatAuthorAttributionHtml(
|
|
151
|
-
attribution.authorName,
|
|
152
|
-
attribution.authorSlug
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
return `<span class="skhema-author-text">By ${escapeHtml(formatContributorIdFallback(contributorId))}</span>`;
|
|
156
|
-
}
|
|
1
|
+
import { r as renderComponentCardHtml, a as renderElementCardHtml, v as validateContentSecurity } from "./index-jwx5CXLB.js";
|
|
2
|
+
import { d as getElementTypeLabel, b as getComponentTypeLabel, i as isValidComponentType, v as validateElementBelongsToComponent, e as getElementTypesForComponent, l as CARD_SHADOW_LG, m as PRIMARY_HOVER_HEX, n as PRIMARY_PRESSED_HEX, o as validateAttributes, P as PRIMARY_HEX } from "./validation-Dt4L_df8.js";
|
|
3
|
+
import { g, c, f, r } from "./validation-Dt4L_df8.js";
|
|
157
4
|
const ANALYTICS_ENDPOINT = "https://analytics.skhema.com/functions/v1/embed-manage";
|
|
158
5
|
const TRACKING_COOKIE_NAME = "_sk";
|
|
159
6
|
const TRACKING_EXPIRY_HOURS = 24;
|
|
@@ -527,43 +374,6 @@ function shouldTrackAnalytics(element) {
|
|
|
527
374
|
const trackAnalytics = element.getAttribute("track-analytics");
|
|
528
375
|
return trackAnalytics !== "false";
|
|
529
376
|
}
|
|
530
|
-
function isValidComponentType(componentType) {
|
|
531
|
-
const validTypes = Object.values(COMPONENT_TYPES).map((t) => t.value);
|
|
532
|
-
return validTypes.includes(componentType);
|
|
533
|
-
}
|
|
534
|
-
function validateElementBelongsToComponent(elementType, componentType) {
|
|
535
|
-
if (!isValidComponentType(componentType)) return false;
|
|
536
|
-
const validElements = SKHEMA_MAPPING.elementFlow[componentType];
|
|
537
|
-
if (!validElements) return false;
|
|
538
|
-
return validElements.some((e) => e.value === elementType);
|
|
539
|
-
}
|
|
540
|
-
function getComponentTypeLabel(componentType) {
|
|
541
|
-
const type = Object.values(COMPONENT_TYPES).find(
|
|
542
|
-
(t) => t.value === componentType
|
|
543
|
-
);
|
|
544
|
-
return type?.label || componentType;
|
|
545
|
-
}
|
|
546
|
-
function getComponentTypeAcronym(componentType) {
|
|
547
|
-
const type = Object.values(COMPONENT_TYPES).find(
|
|
548
|
-
(t) => t.value === componentType
|
|
549
|
-
);
|
|
550
|
-
return type?.acronym || componentType.substring(0, 2).toUpperCase();
|
|
551
|
-
}
|
|
552
|
-
function resolveComponentType(elementType) {
|
|
553
|
-
for (const [componentValue, elements] of Object.entries(
|
|
554
|
-
SKHEMA_MAPPING.elementFlow
|
|
555
|
-
)) {
|
|
556
|
-
if (elements.some((e) => e.value === elementType)) {
|
|
557
|
-
return componentValue;
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
return "diagnosis";
|
|
561
|
-
}
|
|
562
|
-
function getElementTypesForComponent(componentType) {
|
|
563
|
-
if (!isValidComponentType(componentType)) return [];
|
|
564
|
-
const elements = SKHEMA_MAPPING.elementFlow[componentType];
|
|
565
|
-
return elements?.map((e) => e.value) || [];
|
|
566
|
-
}
|
|
567
377
|
function generateContentHash(content) {
|
|
568
378
|
let hash = 0;
|
|
569
379
|
const cleanContent = content.trim().substring(0, 200);
|
|
@@ -583,111 +393,6 @@ function generateComponentHash(elements) {
|
|
|
583
393
|
const combined = sorted.map((e) => `${e.elementType}:${e.content}`).join("|");
|
|
584
394
|
return generateContentHash(combined);
|
|
585
395
|
}
|
|
586
|
-
function sanitizeContent(content) {
|
|
587
|
-
const htmlEncode = (str) => {
|
|
588
|
-
const div = document.createElement("div");
|
|
589
|
-
div.textContent = str;
|
|
590
|
-
return div.innerHTML;
|
|
591
|
-
};
|
|
592
|
-
let sanitized = stripUrls(content);
|
|
593
|
-
sanitized = htmlEncode(sanitized);
|
|
594
|
-
sanitized = sanitized.replace(/\n/g, "<br>");
|
|
595
|
-
sanitized = applyTextWrapping(sanitized);
|
|
596
|
-
return sanitized;
|
|
597
|
-
}
|
|
598
|
-
function stripUrls(text) {
|
|
599
|
-
const patterns = [
|
|
600
|
-
// Standard URLs with protocols
|
|
601
|
-
/https?:\/\/[^\s<>"{}|\\^`[\]]+/gi,
|
|
602
|
-
// FTP URLs
|
|
603
|
-
/ftp:\/\/[^\s<>"{}|\\^`[\]]+/gi,
|
|
604
|
-
// URLs without protocol but with www
|
|
605
|
-
/www\.[^\s<>"{}|\\^`[\]]+/gi,
|
|
606
|
-
// Email-like patterns
|
|
607
|
-
/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/gi,
|
|
608
|
-
// Common domain patterns (anything.com, anything.org, etc.)
|
|
609
|
-
/(?:^|\s)([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(?:\/[^\s]*)?/gi
|
|
610
|
-
];
|
|
611
|
-
let stripped = text;
|
|
612
|
-
patterns.forEach((pattern) => {
|
|
613
|
-
stripped = stripped.replace(pattern, "");
|
|
614
|
-
});
|
|
615
|
-
stripped = stripped.replace(/\s+/g, " ").trim();
|
|
616
|
-
return stripped;
|
|
617
|
-
}
|
|
618
|
-
function applyTextWrapping(text) {
|
|
619
|
-
const words = text.split(/(\s+)/);
|
|
620
|
-
return words.map((word) => {
|
|
621
|
-
if (/^\s+$/.test(word) || word.includes("<")) {
|
|
622
|
-
return word;
|
|
623
|
-
}
|
|
624
|
-
if (word.length > 30) {
|
|
625
|
-
return word.replace(/(.{10})/g, "$1");
|
|
626
|
-
}
|
|
627
|
-
return word;
|
|
628
|
-
}).join("");
|
|
629
|
-
}
|
|
630
|
-
function validateContentSecurity(content) {
|
|
631
|
-
const issues = [];
|
|
632
|
-
if (/<script[\s>]/i.test(content)) {
|
|
633
|
-
issues.push("Script tags detected");
|
|
634
|
-
}
|
|
635
|
-
if (/on\w+\s*=/i.test(content)) {
|
|
636
|
-
issues.push("Event handlers detected");
|
|
637
|
-
}
|
|
638
|
-
if (/javascript:/i.test(content)) {
|
|
639
|
-
issues.push("JavaScript protocol detected");
|
|
640
|
-
}
|
|
641
|
-
if (/data:[^,]*script/i.test(content)) {
|
|
642
|
-
issues.push("Data URL with script detected");
|
|
643
|
-
}
|
|
644
|
-
if (/<iframe[\s>]/i.test(content)) {
|
|
645
|
-
issues.push("Iframe tags detected");
|
|
646
|
-
}
|
|
647
|
-
if (/https?:\/\//i.test(content) || /www\./i.test(content)) {
|
|
648
|
-
issues.push("URLs detected in content");
|
|
649
|
-
}
|
|
650
|
-
return {
|
|
651
|
-
isSecure: issues.length === 0,
|
|
652
|
-
issues
|
|
653
|
-
};
|
|
654
|
-
}
|
|
655
|
-
function isValidElementType(elementType) {
|
|
656
|
-
const validTypes = Object.values(ELEMENT_TYPES).map((type) => type.value);
|
|
657
|
-
return validTypes.includes(elementType);
|
|
658
|
-
}
|
|
659
|
-
function validateAttributes(element) {
|
|
660
|
-
const errors = [];
|
|
661
|
-
const elementType = element.getAttribute("element-type");
|
|
662
|
-
const contributorId = element.getAttribute("contributor-id");
|
|
663
|
-
if (!elementType) {
|
|
664
|
-
errors.push("Missing required attribute: element-type");
|
|
665
|
-
} else if (!isValidElementType(elementType)) {
|
|
666
|
-
const validTypes = Object.values(ELEMENT_TYPES).map((t) => t.value).join(", ");
|
|
667
|
-
errors.push(
|
|
668
|
-
`Invalid element-type "${elementType}". Valid types: ${validTypes}`
|
|
669
|
-
);
|
|
670
|
-
}
|
|
671
|
-
if (!contributorId) {
|
|
672
|
-
errors.push("Missing required attribute: contributor-id");
|
|
673
|
-
} else if (contributorId.trim().length === 0) {
|
|
674
|
-
errors.push("contributor-id cannot be empty");
|
|
675
|
-
}
|
|
676
|
-
return {
|
|
677
|
-
isValid: errors.length === 0,
|
|
678
|
-
errors,
|
|
679
|
-
elementType: isValidElementType(elementType || "") ? elementType : void 0,
|
|
680
|
-
contributorId: contributorId || void 0
|
|
681
|
-
};
|
|
682
|
-
}
|
|
683
|
-
function getElementTypeLabel(elementType) {
|
|
684
|
-
const type = Object.values(ELEMENT_TYPES).find((t) => t.value === elementType);
|
|
685
|
-
return type?.label || elementType;
|
|
686
|
-
}
|
|
687
|
-
function getElementTypeAcronym(elementType) {
|
|
688
|
-
const type = Object.values(ELEMENT_TYPES).find((t) => t.value === elementType);
|
|
689
|
-
return type?.acronym || elementType.substring(0, 2).toUpperCase();
|
|
690
|
-
}
|
|
691
396
|
function generateStructuredData(content, elementType, contributorId, sourceUrl) {
|
|
692
397
|
return {
|
|
693
398
|
"@context": "https://schema.org",
|
|
@@ -819,7 +524,7 @@ const styles$1 = `
|
|
|
819
524
|
color: var(--skhema-text);
|
|
820
525
|
}
|
|
821
526
|
|
|
822
|
-
/* Dark mode */
|
|
527
|
+
/* Dark mode (skeleton/error only) */
|
|
823
528
|
.skhema-component-card[data-theme="dark"],
|
|
824
529
|
.skhema-skeleton[data-theme="dark"] {
|
|
825
530
|
--skhema-bg: hsl(222.2 84% 4.9%);
|
|
@@ -831,106 +536,40 @@ const styles$1 = `
|
|
|
831
536
|
--skhema-surface-subtle: hsl(217.2 32.6% 12%);
|
|
832
537
|
}
|
|
833
538
|
|
|
834
|
-
/*
|
|
835
|
-
.
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
border: 1px solid var(--skhema-border);
|
|
839
|
-
border-radius: calc(var(--skhema-radius) + 2px);
|
|
840
|
-
padding: 0;
|
|
841
|
-
box-shadow: var(--skhema-shadow);
|
|
842
|
-
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
843
|
-
max-width: 600px;
|
|
844
|
-
margin: 8px 0;
|
|
845
|
-
overflow: hidden;
|
|
539
|
+
/* Progressive enhancement over the inline-styled, rendered card. Inline styles
|
|
540
|
+
win on specificity, so the changed properties use !important to layer on. */
|
|
541
|
+
.skhema-card {
|
|
542
|
+
transition: box-shadow 0.2s cubic-bezier(0.4, 0, 0.2, 1), transform 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
846
543
|
}
|
|
847
544
|
|
|
848
|
-
.skhema-
|
|
849
|
-
box-shadow:
|
|
545
|
+
.skhema-card:hover {
|
|
546
|
+
box-shadow: ${CARD_SHADOW_LG} !important;
|
|
850
547
|
transform: translateY(-1px);
|
|
851
548
|
}
|
|
852
549
|
|
|
853
|
-
|
|
854
|
-
.
|
|
855
|
-
height: 2px;
|
|
856
|
-
width: 100%;
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
/* Header */
|
|
860
|
-
.skhema-header {
|
|
861
|
-
display: flex;
|
|
862
|
-
align-items: center;
|
|
863
|
-
gap: 8px;
|
|
864
|
-
padding: 12px 16px;
|
|
865
|
-
border-bottom: 1px solid var(--skhema-border);
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
.skhema-type-label {
|
|
869
|
-
font-size: 13px;
|
|
870
|
-
font-weight: 500;
|
|
871
|
-
color: var(--skhema-text-muted);
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
.skhema-title-separator {
|
|
875
|
-
color: var(--skhema-text-muted);
|
|
876
|
-
font-size: 13px;
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
.skhema-title {
|
|
880
|
-
font-size: 13px;
|
|
881
|
-
font-weight: 600;
|
|
882
|
-
color: var(--skhema-text);
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
/* Elements section */
|
|
886
|
-
.skhema-elements {
|
|
887
|
-
padding: 16px;
|
|
888
|
-
display: flex;
|
|
889
|
-
flex-direction: column;
|
|
890
|
-
gap: 16px;
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
/* Element group */
|
|
894
|
-
.skhema-element-group {
|
|
895
|
-
display: flex;
|
|
896
|
-
flex-direction: column;
|
|
897
|
-
gap: 4px;
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
.skhema-element-label {
|
|
901
|
-
text-transform: uppercase;
|
|
902
|
-
letter-spacing: 0.05em;
|
|
903
|
-
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
|
|
904
|
-
font-size: 10px;
|
|
905
|
-
font-weight: 600;
|
|
906
|
-
color: var(--skhema-text-muted);
|
|
550
|
+
.skhema-save-btn {
|
|
551
|
+
transition: background 0.15s ease;
|
|
907
552
|
}
|
|
908
553
|
|
|
909
|
-
.skhema-
|
|
910
|
-
|
|
911
|
-
line-height: 1.6;
|
|
912
|
-
color: var(--skhema-text);
|
|
913
|
-
padding-left: 8px;
|
|
914
|
-
border-left: 2px solid var(--skhema-border);
|
|
915
|
-
word-wrap: break-word;
|
|
916
|
-
overflow-wrap: break-word;
|
|
554
|
+
.skhema-save-btn:hover {
|
|
555
|
+
background: ${PRIMARY_HOVER_HEX} !important;
|
|
917
556
|
}
|
|
918
557
|
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
padding: 12px 16px;
|
|
922
|
-
border-top: 1px solid var(--skhema-border);
|
|
558
|
+
.skhema-save-btn:active {
|
|
559
|
+
background: ${PRIMARY_PRESSED_HEX} !important;
|
|
923
560
|
}
|
|
924
561
|
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
562
|
+
/* Error card wrapper (browser-only state) */
|
|
563
|
+
.skhema-component-card {
|
|
564
|
+
background: var(--skhema-card);
|
|
565
|
+
border: 1px solid var(--skhema-border);
|
|
566
|
+
border-radius: calc(var(--skhema-radius) + 2px);
|
|
567
|
+
box-shadow: var(--skhema-shadow);
|
|
568
|
+
max-width: 600px;
|
|
569
|
+
margin: 8px 0;
|
|
570
|
+
overflow: hidden;
|
|
930
571
|
}
|
|
931
572
|
|
|
932
|
-
${SHARED_CARD_STYLES}
|
|
933
|
-
|
|
934
573
|
/* Error state */
|
|
935
574
|
.skhema-error {
|
|
936
575
|
background: hsl(0 93% 94%);
|
|
@@ -1015,39 +654,9 @@ ${SHARED_CARD_STYLES}
|
|
|
1015
654
|
100% { background-position: 200% 0; }
|
|
1016
655
|
}
|
|
1017
656
|
|
|
1018
|
-
/* Responsive */
|
|
1019
|
-
@media (max-width: 640px) {
|
|
1020
|
-
.skhema-component-card {
|
|
1021
|
-
margin: 4px 0;
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
.skhema-header {
|
|
1025
|
-
padding: 10px 12px;
|
|
1026
|
-
flex-wrap: wrap;
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
.skhema-elements {
|
|
1030
|
-
padding: 12px;
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
|
-
.skhema-footer {
|
|
1034
|
-
padding: 10px 12px;
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
.skhema-footer-row {
|
|
1038
|
-
flex-direction: column;
|
|
1039
|
-
align-items: stretch;
|
|
1040
|
-
gap: 8px;
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
.skhema-save-btn {
|
|
1044
|
-
justify-content: center;
|
|
1045
|
-
}
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
657
|
/* Accessibility */
|
|
1049
658
|
@media (prefers-reduced-motion: reduce) {
|
|
1050
|
-
.skhema-
|
|
659
|
+
.skhema-card,
|
|
1051
660
|
.skhema-save-btn {
|
|
1052
661
|
transition: none;
|
|
1053
662
|
}
|
|
@@ -1213,9 +822,6 @@ class SkhemaComponent extends HTMLElement {
|
|
|
1213
822
|
if (!this.contentData) return;
|
|
1214
823
|
const { component_type, contributor_id, title } = this.contentData;
|
|
1215
824
|
const componentLabel = getComponentTypeLabel(component_type);
|
|
1216
|
-
const acronym = getComponentTypeAcronym(component_type);
|
|
1217
|
-
const colors = COMPONENT_COLORS[component_type];
|
|
1218
|
-
const authorFooterHtml = resolveAuthorFooterHtml(this, contributor_id);
|
|
1219
825
|
const themeAttribute = this.getAttribute("theme") || "auto";
|
|
1220
826
|
const actualTheme = this.getActualTheme(themeAttribute);
|
|
1221
827
|
const redirectUrl = generateComponentRedirectUrl(
|
|
@@ -1223,65 +829,25 @@ class SkhemaComponent extends HTMLElement {
|
|
|
1223
829
|
component_type,
|
|
1224
830
|
contributor_id
|
|
1225
831
|
);
|
|
1226
|
-
const badgeStyle = colors ? `background: ${colors.bg}; color: ${colors.text}; border: 1px solid ${colors.border};` : "";
|
|
1227
|
-
const topBorderColor = colors ? colors.text : "var(--skhema-primary)";
|
|
1228
|
-
const groupedElements = this.groupElementsByType(elements);
|
|
1229
|
-
const elementSectionsHtml = groupedElements.map(
|
|
1230
|
-
(group) => `
|
|
1231
|
-
<div class="skhema-element-group">
|
|
1232
|
-
<div class="skhema-element-label">${getElementTypeLabel(group.elementType)}</div>
|
|
1233
|
-
${group.items.map(
|
|
1234
|
-
(item) => `<div class="skhema-element-content">${sanitizeContent(item.content)}</div>`
|
|
1235
|
-
).join("")}
|
|
1236
|
-
</div>
|
|
1237
|
-
`
|
|
1238
|
-
).join("");
|
|
1239
832
|
this.setAttribute("role", "article");
|
|
1240
833
|
this.setAttribute(
|
|
1241
834
|
"aria-label",
|
|
1242
835
|
`${componentLabel} component${title ? ` — ${title}` : ""}`
|
|
1243
836
|
);
|
|
1244
|
-
const
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
<div class="skhema-elements">
|
|
1260
|
-
${elementSectionsHtml}
|
|
1261
|
-
</div>
|
|
1262
|
-
|
|
1263
|
-
<div class="skhema-footer">
|
|
1264
|
-
<div class="skhema-footer-row">
|
|
1265
|
-
<span class="skhema-contributor-line">
|
|
1266
|
-
${USER_ICON_SVG}
|
|
1267
|
-
${authorFooterHtml}
|
|
1268
|
-
</span>
|
|
1269
|
-
<a href="${redirectUrl}"
|
|
1270
|
-
class="skhema-save-btn"
|
|
1271
|
-
target="_blank"
|
|
1272
|
-
rel="noopener noreferrer"
|
|
1273
|
-
title="Save this component to Skhema">
|
|
1274
|
-
Save to Skhema
|
|
1275
|
-
</a>
|
|
1276
|
-
</div>
|
|
1277
|
-
<div class="skhema-footer-attribution">
|
|
1278
|
-
Strategy powered by <a href="https://skhema.com" target="_blank" rel="noopener noreferrer">Skhema</a>
|
|
1279
|
-
</div>
|
|
1280
|
-
</div>
|
|
1281
|
-
</div>
|
|
1282
|
-
|
|
1283
|
-
<slot style="display:none;"></slot>
|
|
1284
|
-
`;
|
|
837
|
+
const cardHtml = renderComponentCardHtml({
|
|
838
|
+
componentType: component_type,
|
|
839
|
+
title,
|
|
840
|
+
elements: elements.map((el) => ({
|
|
841
|
+
elementType: el.elementType,
|
|
842
|
+
content: el.content
|
|
843
|
+
})),
|
|
844
|
+
saveUrl: redirectUrl,
|
|
845
|
+
authorName: this.getAttribute("author-name") || this.getAttribute("contributor-name"),
|
|
846
|
+
authorSlug: this.getAttribute("author-slug"),
|
|
847
|
+
contributorId: contributor_id,
|
|
848
|
+
theme: actualTheme
|
|
849
|
+
});
|
|
850
|
+
this.shadow.innerHTML = `<style>${styles$1}</style>${cardHtml}<slot style="display:none;"></slot>`;
|
|
1285
851
|
const saveBtn = this.shadow.querySelector(
|
|
1286
852
|
".skhema-save-btn"
|
|
1287
853
|
);
|
|
@@ -1291,21 +857,6 @@ class SkhemaComponent extends HTMLElement {
|
|
|
1291
857
|
});
|
|
1292
858
|
}
|
|
1293
859
|
}
|
|
1294
|
-
groupElementsByType(elements) {
|
|
1295
|
-
const groups = /* @__PURE__ */ new Map();
|
|
1296
|
-
for (const el of elements) {
|
|
1297
|
-
const existing = groups.get(el.elementType);
|
|
1298
|
-
if (existing) {
|
|
1299
|
-
existing.push(el);
|
|
1300
|
-
} else {
|
|
1301
|
-
groups.set(el.elementType, [el]);
|
|
1302
|
-
}
|
|
1303
|
-
}
|
|
1304
|
-
return Array.from(groups.entries()).map(([elementType, items]) => ({
|
|
1305
|
-
elementType,
|
|
1306
|
-
items
|
|
1307
|
-
}));
|
|
1308
|
-
}
|
|
1309
860
|
getActualTheme(themeAttribute) {
|
|
1310
861
|
if (themeAttribute === "light" || themeAttribute === "dark") {
|
|
1311
862
|
return themeAttribute;
|
|
@@ -1493,12 +1044,8 @@ class SkhemaComponent extends HTMLElement {
|
|
|
1493
1044
|
}
|
|
1494
1045
|
updateTheme() {
|
|
1495
1046
|
const themeAttribute = this.getAttribute("theme") || "auto";
|
|
1496
|
-
if (themeAttribute === "auto") {
|
|
1497
|
-
|
|
1498
|
-
if (card) {
|
|
1499
|
-
const newTheme = this.getActualTheme("auto");
|
|
1500
|
-
card.setAttribute("data-theme", newTheme);
|
|
1501
|
-
}
|
|
1047
|
+
if (themeAttribute === "auto" && this.contentData) {
|
|
1048
|
+
this.scheduleRender();
|
|
1502
1049
|
}
|
|
1503
1050
|
}
|
|
1504
1051
|
}
|
|
@@ -1530,7 +1077,7 @@ const styles = `
|
|
|
1530
1077
|
color: var(--skhema-text);
|
|
1531
1078
|
}
|
|
1532
1079
|
|
|
1533
|
-
/* Dark mode styles - applied via data-theme attribute */
|
|
1080
|
+
/* Dark mode styles - applied via data-theme attribute (skeleton/error only) */
|
|
1534
1081
|
.skhema-element-card[data-theme="dark"],
|
|
1535
1082
|
.skhema-skeleton[data-theme="dark"] {
|
|
1536
1083
|
--skhema-bg: hsl(222.2 84% 4.9%);
|
|
@@ -1541,71 +1088,42 @@ const styles = `
|
|
|
1541
1088
|
--skhema-accent: hsl(217.2 32.6% 17.5%);
|
|
1542
1089
|
}
|
|
1543
1090
|
|
|
1544
|
-
/*
|
|
1545
|
-
.
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
border: 1px solid var(--skhema-border);
|
|
1549
|
-
border-radius: calc(var(--skhema-radius) + 2px);
|
|
1550
|
-
padding: 0;
|
|
1551
|
-
box-shadow: var(--skhema-shadow);
|
|
1552
|
-
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
1553
|
-
max-width: 600px;
|
|
1554
|
-
margin: 8px 0;
|
|
1555
|
-
overflow: hidden;
|
|
1091
|
+
/* Progressive enhancement over the inline-styled, rendered card. Inline styles
|
|
1092
|
+
win on specificity, so the changed properties use !important to layer on. */
|
|
1093
|
+
.skhema-card {
|
|
1094
|
+
transition: box-shadow 0.2s cubic-bezier(0.4, 0, 0.2, 1), transform 0.2s cubic-bezier(0.4, 0, 0.2, 1), border-color 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
1556
1095
|
}
|
|
1557
1096
|
|
|
1558
|
-
.skhema-
|
|
1559
|
-
box-shadow:
|
|
1560
|
-
border-color: var(--skhema-primary);
|
|
1097
|
+
.skhema-card:hover {
|
|
1098
|
+
box-shadow: ${CARD_SHADOW_LG} !important;
|
|
1561
1099
|
transform: translateY(-1px);
|
|
1562
1100
|
}
|
|
1563
1101
|
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
display: flex;
|
|
1567
|
-
align-items: center;
|
|
1568
|
-
gap: 8px;
|
|
1569
|
-
padding: 12px 16px;
|
|
1570
|
-
border-bottom: 1px solid var(--skhema-border);
|
|
1571
|
-
}
|
|
1572
|
-
|
|
1573
|
-
/* Content section */
|
|
1574
|
-
.skhema-content {
|
|
1575
|
-
padding: 16px;
|
|
1102
|
+
.skhema-card[data-skhema-kind="element"]:hover {
|
|
1103
|
+
border-color: ${PRIMARY_HEX} !important;
|
|
1576
1104
|
}
|
|
1577
1105
|
|
|
1578
|
-
.skhema-
|
|
1579
|
-
|
|
1580
|
-
line-height: 1.6;
|
|
1581
|
-
color: var(--skhema-text);
|
|
1582
|
-
margin: 0;
|
|
1583
|
-
word-wrap: break-word;
|
|
1584
|
-
overflow-wrap: break-word;
|
|
1585
|
-
hyphens: auto;
|
|
1586
|
-
max-width: 100%;
|
|
1106
|
+
.skhema-save-btn {
|
|
1107
|
+
transition: background 0.15s ease;
|
|
1587
1108
|
}
|
|
1588
1109
|
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
padding: 12px 16px;
|
|
1592
|
-
border-top: 1px solid var(--skhema-border);
|
|
1110
|
+
.skhema-save-btn:hover {
|
|
1111
|
+
background: ${PRIMARY_HOVER_HEX} !important;
|
|
1593
1112
|
}
|
|
1594
1113
|
|
|
1595
|
-
.skhema-
|
|
1596
|
-
|
|
1597
|
-
align-items: center;
|
|
1598
|
-
justify-content: space-between;
|
|
1599
|
-
gap: 12px;
|
|
1114
|
+
.skhema-save-btn:active {
|
|
1115
|
+
background: ${PRIMARY_PRESSED_HEX} !important;
|
|
1600
1116
|
}
|
|
1601
1117
|
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1118
|
+
/* Error card wrapper (browser-only state) */
|
|
1119
|
+
.skhema-element-card {
|
|
1120
|
+
background: var(--skhema-card);
|
|
1121
|
+
border: 1px solid var(--skhema-border);
|
|
1122
|
+
border-radius: calc(var(--skhema-radius) + 2px);
|
|
1123
|
+
box-shadow: var(--skhema-shadow);
|
|
1124
|
+
max-width: 600px;
|
|
1125
|
+
margin: 8px 0;
|
|
1126
|
+
overflow: hidden;
|
|
1609
1127
|
}
|
|
1610
1128
|
|
|
1611
1129
|
/* Error state */
|
|
@@ -1705,49 +1223,12 @@ ${SHARED_CARD_STYLES}
|
|
|
1705
1223
|
}
|
|
1706
1224
|
}
|
|
1707
1225
|
|
|
1708
|
-
/* Responsive design */
|
|
1709
|
-
@media (max-width: 640px) {
|
|
1710
|
-
.skhema-element-card {
|
|
1711
|
-
margin: 4px 0;
|
|
1712
|
-
}
|
|
1713
|
-
|
|
1714
|
-
.skhema-header {
|
|
1715
|
-
padding: 10px 12px;
|
|
1716
|
-
}
|
|
1717
|
-
|
|
1718
|
-
.skhema-content {
|
|
1719
|
-
padding: 12px;
|
|
1720
|
-
}
|
|
1721
|
-
|
|
1722
|
-
.skhema-footer {
|
|
1723
|
-
padding: 10px 12px;
|
|
1724
|
-
}
|
|
1725
|
-
|
|
1726
|
-
.skhema-footer-row {
|
|
1727
|
-
flex-direction: column;
|
|
1728
|
-
align-items: stretch;
|
|
1729
|
-
gap: 8px;
|
|
1730
|
-
}
|
|
1731
|
-
|
|
1732
|
-
.skhema-save-btn {
|
|
1733
|
-
justify-content: center;
|
|
1734
|
-
}
|
|
1735
|
-
}
|
|
1736
|
-
|
|
1737
1226
|
/* Accessibility */
|
|
1738
1227
|
@media (prefers-reduced-motion: reduce) {
|
|
1739
|
-
.skhema-
|
|
1228
|
+
.skhema-card,
|
|
1740
1229
|
.skhema-save-btn {
|
|
1741
1230
|
transition: none;
|
|
1742
1231
|
}
|
|
1743
|
-
|
|
1744
|
-
.skhema-save-btn::after {
|
|
1745
|
-
transition: none;
|
|
1746
|
-
}
|
|
1747
|
-
|
|
1748
|
-
.skhema-save-btn:hover::after {
|
|
1749
|
-
transform: none;
|
|
1750
|
-
}
|
|
1751
1232
|
}
|
|
1752
1233
|
|
|
1753
1234
|
.skhema-structured-data {
|
|
@@ -1885,7 +1366,6 @@ class SkhemaElement extends HTMLElement {
|
|
|
1885
1366
|
renderContent() {
|
|
1886
1367
|
if (!this.contentData) return;
|
|
1887
1368
|
const { element_type, contributor_id, content } = this.contentData;
|
|
1888
|
-
const label = getElementTypeLabel(element_type);
|
|
1889
1369
|
const redirectUrl = generateRedirectUrl(
|
|
1890
1370
|
content,
|
|
1891
1371
|
element_type,
|
|
@@ -1893,50 +1373,20 @@ class SkhemaElement extends HTMLElement {
|
|
|
1893
1373
|
);
|
|
1894
1374
|
const themeAttribute = this.getAttribute("theme") || "auto";
|
|
1895
1375
|
const actualTheme = this.getActualTheme(themeAttribute);
|
|
1896
|
-
const authorFooterHtml = resolveAuthorFooterHtml(this, contributor_id);
|
|
1897
|
-
const componentType = resolveComponentType(element_type);
|
|
1898
|
-
const acronym = getComponentTypeAcronym(componentType);
|
|
1899
|
-
const colors = COMPONENT_COLORS[componentType];
|
|
1900
1376
|
const ariaAttrs = createAriaAttributes(element_type);
|
|
1901
1377
|
Object.entries(ariaAttrs).forEach(([key, value]) => {
|
|
1902
1378
|
this.setAttribute(key, value);
|
|
1903
1379
|
});
|
|
1904
|
-
const
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
</div>
|
|
1915
|
-
|
|
1916
|
-
<div class="skhema-content">
|
|
1917
|
-
<div class="skhema-content-text">${sanitizeContent(content)}</div>
|
|
1918
|
-
</div>
|
|
1919
|
-
|
|
1920
|
-
<div class="skhema-footer">
|
|
1921
|
-
<div class="skhema-footer-row">
|
|
1922
|
-
<span class="skhema-contributor-line">
|
|
1923
|
-
${USER_ICON_SVG}
|
|
1924
|
-
${authorFooterHtml}
|
|
1925
|
-
</span>
|
|
1926
|
-
<a href="${redirectUrl}"
|
|
1927
|
-
class="skhema-save-btn"
|
|
1928
|
-
target="_blank"
|
|
1929
|
-
rel="noopener noreferrer"
|
|
1930
|
-
title="Save this element to Skhema">
|
|
1931
|
-
Save to Skhema
|
|
1932
|
-
</a>
|
|
1933
|
-
</div>
|
|
1934
|
-
<div class="skhema-footer-attribution">
|
|
1935
|
-
Strategy powered by <a href="https://skhema.com" target="_blank" rel="noopener noreferrer">Skhema</a>
|
|
1936
|
-
</div>
|
|
1937
|
-
</div>
|
|
1938
|
-
</div>
|
|
1939
|
-
`;
|
|
1380
|
+
const cardHtml = renderElementCardHtml({
|
|
1381
|
+
elementType: element_type,
|
|
1382
|
+
content,
|
|
1383
|
+
saveUrl: redirectUrl,
|
|
1384
|
+
authorName: this.getAttribute("author-name") || this.getAttribute("contributor-name"),
|
|
1385
|
+
authorSlug: this.getAttribute("author-slug"),
|
|
1386
|
+
contributorId: contributor_id,
|
|
1387
|
+
theme: actualTheme
|
|
1388
|
+
});
|
|
1389
|
+
this.shadow.innerHTML = `<style>${styles}</style>${cardHtml}`;
|
|
1940
1390
|
const saveBtn = this.shadow.querySelector(
|
|
1941
1391
|
".skhema-save-btn"
|
|
1942
1392
|
);
|
|
@@ -2124,12 +1574,8 @@ class SkhemaElement extends HTMLElement {
|
|
|
2124
1574
|
}
|
|
2125
1575
|
updateTheme() {
|
|
2126
1576
|
const themeAttribute = this.getAttribute("theme") || "auto";
|
|
2127
|
-
if (themeAttribute === "auto") {
|
|
2128
|
-
|
|
2129
|
-
if (card) {
|
|
2130
|
-
const newTheme = this.getActualTheme("auto");
|
|
2131
|
-
card.setAttribute("data-theme", newTheme);
|
|
2132
|
-
}
|
|
1577
|
+
if (themeAttribute === "auto" && this.contentData && !this.nestedMode) {
|
|
1578
|
+
this.renderContent();
|
|
2133
1579
|
}
|
|
2134
1580
|
}
|
|
2135
1581
|
}
|
|
@@ -2172,15 +1618,15 @@ export {
|
|
|
2172
1618
|
generateComponentStructuredData,
|
|
2173
1619
|
generateRedirectUrl,
|
|
2174
1620
|
generateStructuredData,
|
|
2175
|
-
getComponentTypeAcronym,
|
|
1621
|
+
g as getComponentTypeAcronym,
|
|
2176
1622
|
getComponentTypeLabel,
|
|
2177
|
-
getElementTypeAcronym,
|
|
1623
|
+
c as getElementTypeAcronym,
|
|
2178
1624
|
getElementTypeLabel,
|
|
2179
1625
|
isValidComponentType,
|
|
2180
|
-
isValidElementType,
|
|
1626
|
+
f as isValidElementType,
|
|
2181
1627
|
registerSkhemaComponent,
|
|
2182
1628
|
registerSkhemaElement,
|
|
2183
|
-
resolveComponentType,
|
|
1629
|
+
r as resolveComponentType,
|
|
2184
1630
|
shouldTrackAnalytics,
|
|
2185
1631
|
trackComponentClick,
|
|
2186
1632
|
trackComponentEmbedLoad,
|