@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.cjs
CHANGED
|
@@ -1,161 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
diagnosis: {
|
|
6
|
-
bg: "oklch(0.65 0.06 25 / 0.15)",
|
|
7
|
-
text: "oklch(0.65 0.06 25)",
|
|
8
|
-
border: "oklch(0.65 0.06 25 / 0.3)"
|
|
9
|
-
},
|
|
10
|
-
method: {
|
|
11
|
-
bg: "oklch(0.6 0.06 250 / 0.15)",
|
|
12
|
-
text: "oklch(0.6 0.06 250)",
|
|
13
|
-
border: "oklch(0.6 0.06 250 / 0.3)"
|
|
14
|
-
},
|
|
15
|
-
initiatives: {
|
|
16
|
-
bg: "oklch(0.6 0.06 155 / 0.15)",
|
|
17
|
-
text: "oklch(0.6 0.06 155)",
|
|
18
|
-
border: "oklch(0.6 0.06 155 / 0.3)"
|
|
19
|
-
},
|
|
20
|
-
measures: {
|
|
21
|
-
bg: "oklch(0.6 0.06 300 / 0.15)",
|
|
22
|
-
text: "oklch(0.6 0.06 300)",
|
|
23
|
-
border: "oklch(0.6 0.06 300 / 0.3)"
|
|
24
|
-
},
|
|
25
|
-
support: {
|
|
26
|
-
bg: "oklch(0.65 0.06 65 / 0.15)",
|
|
27
|
-
text: "oklch(0.65 0.06 65)",
|
|
28
|
-
border: "oklch(0.65 0.06 65 / 0.3)"
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
const SHARED_CARD_STYLES = `
|
|
32
|
-
/* Monospace acronym badge */
|
|
33
|
-
.skhema-acronym-badge {
|
|
34
|
-
display: inline-flex;
|
|
35
|
-
align-items: center;
|
|
36
|
-
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
|
|
37
|
-
font-size: 10px;
|
|
38
|
-
font-weight: 600;
|
|
39
|
-
padding: 2px 6px;
|
|
40
|
-
border-radius: 2px;
|
|
41
|
-
letter-spacing: 0.02em;
|
|
42
|
-
flex-shrink: 0;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/* Footer attribution tagline */
|
|
46
|
-
.skhema-footer-attribution {
|
|
47
|
-
font-size: 11px;
|
|
48
|
-
color: var(--skhema-text-muted);
|
|
49
|
-
line-height: 1.4;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
.skhema-footer-attribution a {
|
|
53
|
-
color: var(--skhema-text-muted);
|
|
54
|
-
text-decoration: underline;
|
|
55
|
-
text-decoration-color: var(--skhema-border);
|
|
56
|
-
text-underline-offset: 2px;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
.skhema-footer-attribution a:hover {
|
|
60
|
-
color: var(--skhema-text);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/* Contributor line in footer */
|
|
64
|
-
.skhema-contributor-line {
|
|
65
|
-
display: flex;
|
|
66
|
-
align-items: center;
|
|
67
|
-
gap: 6px;
|
|
68
|
-
font-size: 12px;
|
|
69
|
-
color: var(--skhema-text-muted);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
.skhema-contributor-line svg {
|
|
73
|
-
width: 14px;
|
|
74
|
-
height: 14px;
|
|
75
|
-
flex-shrink: 0;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
.skhema-author-link {
|
|
79
|
-
color: inherit;
|
|
80
|
-
text-decoration: none;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
.skhema-author-link:hover {
|
|
84
|
-
color: var(--skhema-text);
|
|
85
|
-
text-decoration: underline;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/* Save button — primary brand color */
|
|
89
|
-
.skhema-save-btn {
|
|
90
|
-
display: inline-flex;
|
|
91
|
-
align-items: center;
|
|
92
|
-
gap: 6px;
|
|
93
|
-
background: var(--skhema-primary);
|
|
94
|
-
color: white;
|
|
95
|
-
border: none;
|
|
96
|
-
padding: 6px 14px;
|
|
97
|
-
border-radius: calc(var(--skhema-radius) + 2px);
|
|
98
|
-
font-size: 12px;
|
|
99
|
-
font-weight: 500;
|
|
100
|
-
text-decoration: none;
|
|
101
|
-
cursor: pointer;
|
|
102
|
-
transition: background 0.15s ease;
|
|
103
|
-
white-space: nowrap;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
.skhema-save-btn:hover {
|
|
107
|
-
background: var(--skhema-primary-hover);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
.skhema-save-btn:active {
|
|
111
|
-
background: var(--skhema-primary-pressed);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
.skhema-save-btn:focus {
|
|
115
|
-
outline: 2px solid var(--skhema-primary);
|
|
116
|
-
outline-offset: 2px;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
.skhema-save-btn::after {
|
|
120
|
-
content: '\\2192';
|
|
121
|
-
font-size: 13px;
|
|
122
|
-
}
|
|
123
|
-
`;
|
|
124
|
-
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>`;
|
|
125
|
-
function readAuthorAttribution(element) {
|
|
126
|
-
const authorName = element.getAttribute("author-name") || element.getAttribute("contributor-name");
|
|
127
|
-
if (!authorName?.trim()) {
|
|
128
|
-
return null;
|
|
129
|
-
}
|
|
130
|
-
const authorSlug = element.getAttribute("author-slug")?.trim();
|
|
131
|
-
return {
|
|
132
|
-
authorName: authorName.trim(),
|
|
133
|
-
authorSlug: authorSlug || void 0
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
function escapeHtml(text) {
|
|
137
|
-
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
138
|
-
}
|
|
139
|
-
function formatAuthorAttributionHtml(authorName, authorSlug) {
|
|
140
|
-
const label = `By ${escapeHtml(authorName)}`;
|
|
141
|
-
if (authorSlug) {
|
|
142
|
-
return `<a href="https://skhema.com/contributors/${encodeURIComponent(authorSlug)}" class="skhema-author-link" target="_blank" rel="noopener noreferrer">${label}</a>`;
|
|
143
|
-
}
|
|
144
|
-
return `<span class="skhema-author-text">${label}</span>`;
|
|
145
|
-
}
|
|
146
|
-
function formatContributorIdFallback(contributorId) {
|
|
147
|
-
return contributorId.split(/[_-]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
|
|
148
|
-
}
|
|
149
|
-
function resolveAuthorFooterHtml(element, contributorId) {
|
|
150
|
-
const attribution = readAuthorAttribution(element);
|
|
151
|
-
if (attribution) {
|
|
152
|
-
return formatAuthorAttributionHtml(
|
|
153
|
-
attribution.authorName,
|
|
154
|
-
attribution.authorSlug
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
|
-
return `<span class="skhema-author-text">By ${escapeHtml(formatContributorIdFallback(contributorId))}</span>`;
|
|
158
|
-
}
|
|
3
|
+
const render = require("./index-DBeyneZT.cjs");
|
|
4
|
+
const validation = require("./validation-DCw9rQeH.cjs");
|
|
159
5
|
const ANALYTICS_ENDPOINT = "https://analytics.skhema.com/functions/v1/embed-manage";
|
|
160
6
|
const TRACKING_COOKIE_NAME = "_sk";
|
|
161
7
|
const TRACKING_EXPIRY_HOURS = 24;
|
|
@@ -529,43 +375,6 @@ function shouldTrackAnalytics(element) {
|
|
|
529
375
|
const trackAnalytics = element.getAttribute("track-analytics");
|
|
530
376
|
return trackAnalytics !== "false";
|
|
531
377
|
}
|
|
532
|
-
function isValidComponentType(componentType) {
|
|
533
|
-
const validTypes = Object.values(types.COMPONENT_TYPES).map((t) => t.value);
|
|
534
|
-
return validTypes.includes(componentType);
|
|
535
|
-
}
|
|
536
|
-
function validateElementBelongsToComponent(elementType, componentType) {
|
|
537
|
-
if (!isValidComponentType(componentType)) return false;
|
|
538
|
-
const validElements = types.SKHEMA_MAPPING.elementFlow[componentType];
|
|
539
|
-
if (!validElements) return false;
|
|
540
|
-
return validElements.some((e) => e.value === elementType);
|
|
541
|
-
}
|
|
542
|
-
function getComponentTypeLabel(componentType) {
|
|
543
|
-
const type = Object.values(types.COMPONENT_TYPES).find(
|
|
544
|
-
(t) => t.value === componentType
|
|
545
|
-
);
|
|
546
|
-
return type?.label || componentType;
|
|
547
|
-
}
|
|
548
|
-
function getComponentTypeAcronym(componentType) {
|
|
549
|
-
const type = Object.values(types.COMPONENT_TYPES).find(
|
|
550
|
-
(t) => t.value === componentType
|
|
551
|
-
);
|
|
552
|
-
return type?.acronym || componentType.substring(0, 2).toUpperCase();
|
|
553
|
-
}
|
|
554
|
-
function resolveComponentType(elementType) {
|
|
555
|
-
for (const [componentValue, elements] of Object.entries(
|
|
556
|
-
types.SKHEMA_MAPPING.elementFlow
|
|
557
|
-
)) {
|
|
558
|
-
if (elements.some((e) => e.value === elementType)) {
|
|
559
|
-
return componentValue;
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
return "diagnosis";
|
|
563
|
-
}
|
|
564
|
-
function getElementTypesForComponent(componentType) {
|
|
565
|
-
if (!isValidComponentType(componentType)) return [];
|
|
566
|
-
const elements = types.SKHEMA_MAPPING.elementFlow[componentType];
|
|
567
|
-
return elements?.map((e) => e.value) || [];
|
|
568
|
-
}
|
|
569
378
|
function generateContentHash(content) {
|
|
570
379
|
let hash = 0;
|
|
571
380
|
const cleanContent = content.trim().substring(0, 200);
|
|
@@ -585,118 +394,13 @@ function generateComponentHash(elements) {
|
|
|
585
394
|
const combined = sorted.map((e) => `${e.elementType}:${e.content}`).join("|");
|
|
586
395
|
return generateContentHash(combined);
|
|
587
396
|
}
|
|
588
|
-
function sanitizeContent(content) {
|
|
589
|
-
const htmlEncode = (str) => {
|
|
590
|
-
const div = document.createElement("div");
|
|
591
|
-
div.textContent = str;
|
|
592
|
-
return div.innerHTML;
|
|
593
|
-
};
|
|
594
|
-
let sanitized = stripUrls(content);
|
|
595
|
-
sanitized = htmlEncode(sanitized);
|
|
596
|
-
sanitized = sanitized.replace(/\n/g, "<br>");
|
|
597
|
-
sanitized = applyTextWrapping(sanitized);
|
|
598
|
-
return sanitized;
|
|
599
|
-
}
|
|
600
|
-
function stripUrls(text) {
|
|
601
|
-
const patterns = [
|
|
602
|
-
// Standard URLs with protocols
|
|
603
|
-
/https?:\/\/[^\s<>"{}|\\^`[\]]+/gi,
|
|
604
|
-
// FTP URLs
|
|
605
|
-
/ftp:\/\/[^\s<>"{}|\\^`[\]]+/gi,
|
|
606
|
-
// URLs without protocol but with www
|
|
607
|
-
/www\.[^\s<>"{}|\\^`[\]]+/gi,
|
|
608
|
-
// Email-like patterns
|
|
609
|
-
/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/gi,
|
|
610
|
-
// Common domain patterns (anything.com, anything.org, etc.)
|
|
611
|
-
/(?:^|\s)([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(?:\/[^\s]*)?/gi
|
|
612
|
-
];
|
|
613
|
-
let stripped = text;
|
|
614
|
-
patterns.forEach((pattern) => {
|
|
615
|
-
stripped = stripped.replace(pattern, "");
|
|
616
|
-
});
|
|
617
|
-
stripped = stripped.replace(/\s+/g, " ").trim();
|
|
618
|
-
return stripped;
|
|
619
|
-
}
|
|
620
|
-
function applyTextWrapping(text) {
|
|
621
|
-
const words = text.split(/(\s+)/);
|
|
622
|
-
return words.map((word) => {
|
|
623
|
-
if (/^\s+$/.test(word) || word.includes("<")) {
|
|
624
|
-
return word;
|
|
625
|
-
}
|
|
626
|
-
if (word.length > 30) {
|
|
627
|
-
return word.replace(/(.{10})/g, "$1");
|
|
628
|
-
}
|
|
629
|
-
return word;
|
|
630
|
-
}).join("");
|
|
631
|
-
}
|
|
632
|
-
function validateContentSecurity(content) {
|
|
633
|
-
const issues = [];
|
|
634
|
-
if (/<script[\s>]/i.test(content)) {
|
|
635
|
-
issues.push("Script tags detected");
|
|
636
|
-
}
|
|
637
|
-
if (/on\w+\s*=/i.test(content)) {
|
|
638
|
-
issues.push("Event handlers detected");
|
|
639
|
-
}
|
|
640
|
-
if (/javascript:/i.test(content)) {
|
|
641
|
-
issues.push("JavaScript protocol detected");
|
|
642
|
-
}
|
|
643
|
-
if (/data:[^,]*script/i.test(content)) {
|
|
644
|
-
issues.push("Data URL with script detected");
|
|
645
|
-
}
|
|
646
|
-
if (/<iframe[\s>]/i.test(content)) {
|
|
647
|
-
issues.push("Iframe tags detected");
|
|
648
|
-
}
|
|
649
|
-
if (/https?:\/\//i.test(content) || /www\./i.test(content)) {
|
|
650
|
-
issues.push("URLs detected in content");
|
|
651
|
-
}
|
|
652
|
-
return {
|
|
653
|
-
isSecure: issues.length === 0,
|
|
654
|
-
issues
|
|
655
|
-
};
|
|
656
|
-
}
|
|
657
|
-
function isValidElementType(elementType) {
|
|
658
|
-
const validTypes = Object.values(types.ELEMENT_TYPES).map((type) => type.value);
|
|
659
|
-
return validTypes.includes(elementType);
|
|
660
|
-
}
|
|
661
|
-
function validateAttributes(element) {
|
|
662
|
-
const errors = [];
|
|
663
|
-
const elementType = element.getAttribute("element-type");
|
|
664
|
-
const contributorId = element.getAttribute("contributor-id");
|
|
665
|
-
if (!elementType) {
|
|
666
|
-
errors.push("Missing required attribute: element-type");
|
|
667
|
-
} else if (!isValidElementType(elementType)) {
|
|
668
|
-
const validTypes = Object.values(types.ELEMENT_TYPES).map((t) => t.value).join(", ");
|
|
669
|
-
errors.push(
|
|
670
|
-
`Invalid element-type "${elementType}". Valid types: ${validTypes}`
|
|
671
|
-
);
|
|
672
|
-
}
|
|
673
|
-
if (!contributorId) {
|
|
674
|
-
errors.push("Missing required attribute: contributor-id");
|
|
675
|
-
} else if (contributorId.trim().length === 0) {
|
|
676
|
-
errors.push("contributor-id cannot be empty");
|
|
677
|
-
}
|
|
678
|
-
return {
|
|
679
|
-
isValid: errors.length === 0,
|
|
680
|
-
errors,
|
|
681
|
-
elementType: isValidElementType(elementType || "") ? elementType : void 0,
|
|
682
|
-
contributorId: contributorId || void 0
|
|
683
|
-
};
|
|
684
|
-
}
|
|
685
|
-
function getElementTypeLabel(elementType) {
|
|
686
|
-
const type = Object.values(types.ELEMENT_TYPES).find((t) => t.value === elementType);
|
|
687
|
-
return type?.label || elementType;
|
|
688
|
-
}
|
|
689
|
-
function getElementTypeAcronym(elementType) {
|
|
690
|
-
const type = Object.values(types.ELEMENT_TYPES).find((t) => t.value === elementType);
|
|
691
|
-
return type?.acronym || elementType.substring(0, 2).toUpperCase();
|
|
692
|
-
}
|
|
693
397
|
function generateStructuredData(content, elementType, contributorId, sourceUrl) {
|
|
694
398
|
return {
|
|
695
399
|
"@context": "https://schema.org",
|
|
696
400
|
"@type": "AnalysisContent",
|
|
697
401
|
text: content,
|
|
698
402
|
analysisType: elementType,
|
|
699
|
-
category: getElementTypeLabel(elementType),
|
|
403
|
+
category: validation.getElementTypeLabel(elementType),
|
|
700
404
|
contributor: contributorId,
|
|
701
405
|
url: generateRedirectUrl(content, elementType, contributorId),
|
|
702
406
|
provider: {
|
|
@@ -728,7 +432,7 @@ function generateRedirectUrl(content, elementType, contributorId, options = {})
|
|
|
728
432
|
return `${baseUrl}?type=contributor&contributor_id=${contributorId}&element_type=${elementType}&content_hash=${contentHash}&${params.toString()}`;
|
|
729
433
|
}
|
|
730
434
|
function createMetaTags(content, elementType, contributorId) {
|
|
731
|
-
const label = getElementTypeLabel(elementType);
|
|
435
|
+
const label = validation.getElementTypeLabel(elementType);
|
|
732
436
|
return `
|
|
733
437
|
<div itemscope itemtype="https://schema.org/AnalysisContent" style="display:none;">
|
|
734
438
|
<meta itemprop="analysisType" content="${elementType}">
|
|
@@ -740,7 +444,7 @@ function createMetaTags(content, elementType, contributorId) {
|
|
|
740
444
|
`;
|
|
741
445
|
}
|
|
742
446
|
function generateComponentStructuredData(title, componentType, contributorId, elements, sourceUrl) {
|
|
743
|
-
const componentLabel = getComponentTypeLabel(componentType);
|
|
447
|
+
const componentLabel = validation.getComponentTypeLabel(componentType);
|
|
744
448
|
return {
|
|
745
449
|
"@context": "https://schema.org",
|
|
746
450
|
"@type": "CreativeWork",
|
|
@@ -757,7 +461,7 @@ function generateComponentStructuredData(title, componentType, contributorId, el
|
|
|
757
461
|
"@type": "AnalysisContent",
|
|
758
462
|
text: el.content,
|
|
759
463
|
analysisType: el.elementType,
|
|
760
|
-
category: getElementTypeLabel(el.elementType)
|
|
464
|
+
category: validation.getElementTypeLabel(el.elementType)
|
|
761
465
|
})),
|
|
762
466
|
provider: {
|
|
763
467
|
"@type": "Organization",
|
|
@@ -787,7 +491,7 @@ function generateComponentRedirectUrl(componentHash, componentType, contributorI
|
|
|
787
491
|
return `${baseUrl}?type=component&component_type=${componentType}&component_hash=${componentHash}&contributor_id=${contributorId}&${params.toString()}`;
|
|
788
492
|
}
|
|
789
493
|
function createAriaAttributes(elementType) {
|
|
790
|
-
const label = getElementTypeLabel(elementType);
|
|
494
|
+
const label = validation.getElementTypeLabel(elementType);
|
|
791
495
|
return {
|
|
792
496
|
role: "article",
|
|
793
497
|
"aria-label": `${label} - Strategic element`,
|
|
@@ -821,7 +525,7 @@ const styles$1 = `
|
|
|
821
525
|
color: var(--skhema-text);
|
|
822
526
|
}
|
|
823
527
|
|
|
824
|
-
/* Dark mode */
|
|
528
|
+
/* Dark mode (skeleton/error only) */
|
|
825
529
|
.skhema-component-card[data-theme="dark"],
|
|
826
530
|
.skhema-skeleton[data-theme="dark"] {
|
|
827
531
|
--skhema-bg: hsl(222.2 84% 4.9%);
|
|
@@ -833,106 +537,40 @@ const styles$1 = `
|
|
|
833
537
|
--skhema-surface-subtle: hsl(217.2 32.6% 12%);
|
|
834
538
|
}
|
|
835
539
|
|
|
836
|
-
/*
|
|
837
|
-
.
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
border: 1px solid var(--skhema-border);
|
|
841
|
-
border-radius: calc(var(--skhema-radius) + 2px);
|
|
842
|
-
padding: 0;
|
|
843
|
-
box-shadow: var(--skhema-shadow);
|
|
844
|
-
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
845
|
-
max-width: 600px;
|
|
846
|
-
margin: 8px 0;
|
|
847
|
-
overflow: hidden;
|
|
540
|
+
/* Progressive enhancement over the inline-styled, rendered card. Inline styles
|
|
541
|
+
win on specificity, so the changed properties use !important to layer on. */
|
|
542
|
+
.skhema-card {
|
|
543
|
+
transition: box-shadow 0.2s cubic-bezier(0.4, 0, 0.2, 1), transform 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
848
544
|
}
|
|
849
545
|
|
|
850
|
-
.skhema-
|
|
851
|
-
box-shadow:
|
|
546
|
+
.skhema-card:hover {
|
|
547
|
+
box-shadow: ${validation.CARD_SHADOW_LG} !important;
|
|
852
548
|
transform: translateY(-1px);
|
|
853
549
|
}
|
|
854
550
|
|
|
855
|
-
|
|
856
|
-
.
|
|
857
|
-
height: 2px;
|
|
858
|
-
width: 100%;
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
/* Header */
|
|
862
|
-
.skhema-header {
|
|
863
|
-
display: flex;
|
|
864
|
-
align-items: center;
|
|
865
|
-
gap: 8px;
|
|
866
|
-
padding: 12px 16px;
|
|
867
|
-
border-bottom: 1px solid var(--skhema-border);
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
.skhema-type-label {
|
|
871
|
-
font-size: 13px;
|
|
872
|
-
font-weight: 500;
|
|
873
|
-
color: var(--skhema-text-muted);
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
.skhema-title-separator {
|
|
877
|
-
color: var(--skhema-text-muted);
|
|
878
|
-
font-size: 13px;
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
.skhema-title {
|
|
882
|
-
font-size: 13px;
|
|
883
|
-
font-weight: 600;
|
|
884
|
-
color: var(--skhema-text);
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
/* Elements section */
|
|
888
|
-
.skhema-elements {
|
|
889
|
-
padding: 16px;
|
|
890
|
-
display: flex;
|
|
891
|
-
flex-direction: column;
|
|
892
|
-
gap: 16px;
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
/* Element group */
|
|
896
|
-
.skhema-element-group {
|
|
897
|
-
display: flex;
|
|
898
|
-
flex-direction: column;
|
|
899
|
-
gap: 4px;
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
.skhema-element-label {
|
|
903
|
-
text-transform: uppercase;
|
|
904
|
-
letter-spacing: 0.05em;
|
|
905
|
-
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
|
|
906
|
-
font-size: 10px;
|
|
907
|
-
font-weight: 600;
|
|
908
|
-
color: var(--skhema-text-muted);
|
|
551
|
+
.skhema-save-btn {
|
|
552
|
+
transition: background 0.15s ease;
|
|
909
553
|
}
|
|
910
554
|
|
|
911
|
-
.skhema-
|
|
912
|
-
|
|
913
|
-
line-height: 1.6;
|
|
914
|
-
color: var(--skhema-text);
|
|
915
|
-
padding-left: 8px;
|
|
916
|
-
border-left: 2px solid var(--skhema-border);
|
|
917
|
-
word-wrap: break-word;
|
|
918
|
-
overflow-wrap: break-word;
|
|
555
|
+
.skhema-save-btn:hover {
|
|
556
|
+
background: ${validation.PRIMARY_HOVER_HEX} !important;
|
|
919
557
|
}
|
|
920
558
|
|
|
921
|
-
|
|
922
|
-
.
|
|
923
|
-
padding: 12px 16px;
|
|
924
|
-
border-top: 1px solid var(--skhema-border);
|
|
559
|
+
.skhema-save-btn:active {
|
|
560
|
+
background: ${validation.PRIMARY_PRESSED_HEX} !important;
|
|
925
561
|
}
|
|
926
562
|
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
563
|
+
/* Error card wrapper (browser-only state) */
|
|
564
|
+
.skhema-component-card {
|
|
565
|
+
background: var(--skhema-card);
|
|
566
|
+
border: 1px solid var(--skhema-border);
|
|
567
|
+
border-radius: calc(var(--skhema-radius) + 2px);
|
|
568
|
+
box-shadow: var(--skhema-shadow);
|
|
569
|
+
max-width: 600px;
|
|
570
|
+
margin: 8px 0;
|
|
571
|
+
overflow: hidden;
|
|
932
572
|
}
|
|
933
573
|
|
|
934
|
-
${SHARED_CARD_STYLES}
|
|
935
|
-
|
|
936
574
|
/* Error state */
|
|
937
575
|
.skhema-error {
|
|
938
576
|
background: hsl(0 93% 94%);
|
|
@@ -1017,39 +655,9 @@ ${SHARED_CARD_STYLES}
|
|
|
1017
655
|
100% { background-position: 200% 0; }
|
|
1018
656
|
}
|
|
1019
657
|
|
|
1020
|
-
/* Responsive */
|
|
1021
|
-
@media (max-width: 640px) {
|
|
1022
|
-
.skhema-component-card {
|
|
1023
|
-
margin: 4px 0;
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
|
-
.skhema-header {
|
|
1027
|
-
padding: 10px 12px;
|
|
1028
|
-
flex-wrap: wrap;
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
.skhema-elements {
|
|
1032
|
-
padding: 12px;
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
.skhema-footer {
|
|
1036
|
-
padding: 10px 12px;
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
.skhema-footer-row {
|
|
1040
|
-
flex-direction: column;
|
|
1041
|
-
align-items: stretch;
|
|
1042
|
-
gap: 8px;
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
.skhema-save-btn {
|
|
1046
|
-
justify-content: center;
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
658
|
/* Accessibility */
|
|
1051
659
|
@media (prefers-reduced-motion: reduce) {
|
|
1052
|
-
.skhema-
|
|
660
|
+
.skhema-card,
|
|
1053
661
|
.skhema-save-btn {
|
|
1054
662
|
transition: none;
|
|
1055
663
|
}
|
|
@@ -1126,13 +734,13 @@ class SkhemaComponent extends HTMLElement {
|
|
|
1126
734
|
});
|
|
1127
735
|
}
|
|
1128
736
|
render() {
|
|
1129
|
-
const
|
|
1130
|
-
if (!
|
|
1131
|
-
this.renderError("Invalid component attributes",
|
|
737
|
+
const validation2 = this.validateComponentAttributes();
|
|
738
|
+
if (!validation2.isValid) {
|
|
739
|
+
this.renderError("Invalid component attributes", validation2.errors);
|
|
1132
740
|
return;
|
|
1133
741
|
}
|
|
1134
|
-
const componentType =
|
|
1135
|
-
const contributorId =
|
|
742
|
+
const componentType = validation2.componentType;
|
|
743
|
+
const contributorId = validation2.contributorId;
|
|
1136
744
|
const title = this.getAttribute("title") || "";
|
|
1137
745
|
const childElements = this.gatherChildElements(componentType);
|
|
1138
746
|
if (childElements.length === 0) {
|
|
@@ -1171,7 +779,7 @@ class SkhemaComponent extends HTMLElement {
|
|
|
1171
779
|
const contributorId = this.getAttribute("contributor-id");
|
|
1172
780
|
if (!componentType) {
|
|
1173
781
|
errors.push("Missing required attribute: component-type");
|
|
1174
|
-
} else if (!isValidComponentType(componentType)) {
|
|
782
|
+
} else if (!validation.isValidComponentType(componentType)) {
|
|
1175
783
|
errors.push(`Invalid component-type "${componentType}"`);
|
|
1176
784
|
}
|
|
1177
785
|
if (!contributorId) {
|
|
@@ -1182,7 +790,7 @@ class SkhemaComponent extends HTMLElement {
|
|
|
1182
790
|
return {
|
|
1183
791
|
isValid: errors.length === 0,
|
|
1184
792
|
errors,
|
|
1185
|
-
componentType: isValidComponentType(componentType || "") ? componentType : void 0,
|
|
793
|
+
componentType: validation.isValidComponentType(componentType || "") ? componentType : void 0,
|
|
1186
794
|
contributorId: contributorId || void 0
|
|
1187
795
|
};
|
|
1188
796
|
}
|
|
@@ -1194,14 +802,14 @@ class SkhemaComponent extends HTMLElement {
|
|
|
1194
802
|
for (const child of children) {
|
|
1195
803
|
const data = child.getElementData?.();
|
|
1196
804
|
if (!data) continue;
|
|
1197
|
-
if (!validateElementBelongsToComponent(data.elementType, componentType)) {
|
|
805
|
+
if (!validation.validateElementBelongsToComponent(data.elementType, componentType)) {
|
|
1198
806
|
console.warn(
|
|
1199
807
|
`skhema-component: element type "${data.elementType}" does not belong to component type "${componentType}"`
|
|
1200
808
|
);
|
|
1201
809
|
}
|
|
1202
810
|
elements.push(data);
|
|
1203
811
|
}
|
|
1204
|
-
const orderedTypes = getElementTypesForComponent(componentType);
|
|
812
|
+
const orderedTypes = validation.getElementTypesForComponent(componentType);
|
|
1205
813
|
elements.sort((a, b) => {
|
|
1206
814
|
const aIdx = orderedTypes.indexOf(a.elementType);
|
|
1207
815
|
const bIdx = orderedTypes.indexOf(b.elementType);
|
|
@@ -1214,10 +822,7 @@ class SkhemaComponent extends HTMLElement {
|
|
|
1214
822
|
renderContent(elements) {
|
|
1215
823
|
if (!this.contentData) return;
|
|
1216
824
|
const { component_type, contributor_id, title } = this.contentData;
|
|
1217
|
-
const componentLabel = getComponentTypeLabel(component_type);
|
|
1218
|
-
const acronym = getComponentTypeAcronym(component_type);
|
|
1219
|
-
const colors = COMPONENT_COLORS[component_type];
|
|
1220
|
-
const authorFooterHtml = resolveAuthorFooterHtml(this, contributor_id);
|
|
825
|
+
const componentLabel = validation.getComponentTypeLabel(component_type);
|
|
1221
826
|
const themeAttribute = this.getAttribute("theme") || "auto";
|
|
1222
827
|
const actualTheme = this.getActualTheme(themeAttribute);
|
|
1223
828
|
const redirectUrl = generateComponentRedirectUrl(
|
|
@@ -1225,65 +830,25 @@ class SkhemaComponent extends HTMLElement {
|
|
|
1225
830
|
component_type,
|
|
1226
831
|
contributor_id
|
|
1227
832
|
);
|
|
1228
|
-
const badgeStyle = colors ? `background: ${colors.bg}; color: ${colors.text}; border: 1px solid ${colors.border};` : "";
|
|
1229
|
-
const topBorderColor = colors ? colors.text : "var(--skhema-primary)";
|
|
1230
|
-
const groupedElements = this.groupElementsByType(elements);
|
|
1231
|
-
const elementSectionsHtml = groupedElements.map(
|
|
1232
|
-
(group) => `
|
|
1233
|
-
<div class="skhema-element-group">
|
|
1234
|
-
<div class="skhema-element-label">${getElementTypeLabel(group.elementType)}</div>
|
|
1235
|
-
${group.items.map(
|
|
1236
|
-
(item) => `<div class="skhema-element-content">${sanitizeContent(item.content)}</div>`
|
|
1237
|
-
).join("")}
|
|
1238
|
-
</div>
|
|
1239
|
-
`
|
|
1240
|
-
).join("");
|
|
1241
833
|
this.setAttribute("role", "article");
|
|
1242
834
|
this.setAttribute(
|
|
1243
835
|
"aria-label",
|
|
1244
836
|
`${componentLabel} component${title ? ` — ${title}` : ""}`
|
|
1245
837
|
);
|
|
1246
|
-
const
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
<div class="skhema-elements">
|
|
1262
|
-
${elementSectionsHtml}
|
|
1263
|
-
</div>
|
|
1264
|
-
|
|
1265
|
-
<div class="skhema-footer">
|
|
1266
|
-
<div class="skhema-footer-row">
|
|
1267
|
-
<span class="skhema-contributor-line">
|
|
1268
|
-
${USER_ICON_SVG}
|
|
1269
|
-
${authorFooterHtml}
|
|
1270
|
-
</span>
|
|
1271
|
-
<a href="${redirectUrl}"
|
|
1272
|
-
class="skhema-save-btn"
|
|
1273
|
-
target="_blank"
|
|
1274
|
-
rel="noopener noreferrer"
|
|
1275
|
-
title="Save this component to Skhema">
|
|
1276
|
-
Save to Skhema
|
|
1277
|
-
</a>
|
|
1278
|
-
</div>
|
|
1279
|
-
<div class="skhema-footer-attribution">
|
|
1280
|
-
Strategy powered by <a href="https://skhema.com" target="_blank" rel="noopener noreferrer">Skhema</a>
|
|
1281
|
-
</div>
|
|
1282
|
-
</div>
|
|
1283
|
-
</div>
|
|
1284
|
-
|
|
1285
|
-
<slot style="display:none;"></slot>
|
|
1286
|
-
`;
|
|
838
|
+
const cardHtml = render.renderComponentCardHtml({
|
|
839
|
+
componentType: component_type,
|
|
840
|
+
title,
|
|
841
|
+
elements: elements.map((el) => ({
|
|
842
|
+
elementType: el.elementType,
|
|
843
|
+
content: el.content
|
|
844
|
+
})),
|
|
845
|
+
saveUrl: redirectUrl,
|
|
846
|
+
authorName: this.getAttribute("author-name") || this.getAttribute("contributor-name"),
|
|
847
|
+
authorSlug: this.getAttribute("author-slug"),
|
|
848
|
+
contributorId: contributor_id,
|
|
849
|
+
theme: actualTheme
|
|
850
|
+
});
|
|
851
|
+
this.shadow.innerHTML = `<style>${styles$1}</style>${cardHtml}<slot style="display:none;"></slot>`;
|
|
1287
852
|
const saveBtn = this.shadow.querySelector(
|
|
1288
853
|
".skhema-save-btn"
|
|
1289
854
|
);
|
|
@@ -1293,21 +858,6 @@ class SkhemaComponent extends HTMLElement {
|
|
|
1293
858
|
});
|
|
1294
859
|
}
|
|
1295
860
|
}
|
|
1296
|
-
groupElementsByType(elements) {
|
|
1297
|
-
const groups = /* @__PURE__ */ new Map();
|
|
1298
|
-
for (const el of elements) {
|
|
1299
|
-
const existing = groups.get(el.elementType);
|
|
1300
|
-
if (existing) {
|
|
1301
|
-
existing.push(el);
|
|
1302
|
-
} else {
|
|
1303
|
-
groups.set(el.elementType, [el]);
|
|
1304
|
-
}
|
|
1305
|
-
}
|
|
1306
|
-
return Array.from(groups.entries()).map(([elementType, items]) => ({
|
|
1307
|
-
elementType,
|
|
1308
|
-
items
|
|
1309
|
-
}));
|
|
1310
|
-
}
|
|
1311
861
|
getActualTheme(themeAttribute) {
|
|
1312
862
|
if (themeAttribute === "light" || themeAttribute === "dark") {
|
|
1313
863
|
return themeAttribute;
|
|
@@ -1495,12 +1045,8 @@ class SkhemaComponent extends HTMLElement {
|
|
|
1495
1045
|
}
|
|
1496
1046
|
updateTheme() {
|
|
1497
1047
|
const themeAttribute = this.getAttribute("theme") || "auto";
|
|
1498
|
-
if (themeAttribute === "auto") {
|
|
1499
|
-
|
|
1500
|
-
if (card) {
|
|
1501
|
-
const newTheme = this.getActualTheme("auto");
|
|
1502
|
-
card.setAttribute("data-theme", newTheme);
|
|
1503
|
-
}
|
|
1048
|
+
if (themeAttribute === "auto" && this.contentData) {
|
|
1049
|
+
this.scheduleRender();
|
|
1504
1050
|
}
|
|
1505
1051
|
}
|
|
1506
1052
|
}
|
|
@@ -1532,7 +1078,7 @@ const styles = `
|
|
|
1532
1078
|
color: var(--skhema-text);
|
|
1533
1079
|
}
|
|
1534
1080
|
|
|
1535
|
-
/* Dark mode styles - applied via data-theme attribute */
|
|
1081
|
+
/* Dark mode styles - applied via data-theme attribute (skeleton/error only) */
|
|
1536
1082
|
.skhema-element-card[data-theme="dark"],
|
|
1537
1083
|
.skhema-skeleton[data-theme="dark"] {
|
|
1538
1084
|
--skhema-bg: hsl(222.2 84% 4.9%);
|
|
@@ -1543,71 +1089,42 @@ const styles = `
|
|
|
1543
1089
|
--skhema-accent: hsl(217.2 32.6% 17.5%);
|
|
1544
1090
|
}
|
|
1545
1091
|
|
|
1546
|
-
/*
|
|
1547
|
-
.
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
border: 1px solid var(--skhema-border);
|
|
1551
|
-
border-radius: calc(var(--skhema-radius) + 2px);
|
|
1552
|
-
padding: 0;
|
|
1553
|
-
box-shadow: var(--skhema-shadow);
|
|
1554
|
-
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
1555
|
-
max-width: 600px;
|
|
1556
|
-
margin: 8px 0;
|
|
1557
|
-
overflow: hidden;
|
|
1092
|
+
/* Progressive enhancement over the inline-styled, rendered card. Inline styles
|
|
1093
|
+
win on specificity, so the changed properties use !important to layer on. */
|
|
1094
|
+
.skhema-card {
|
|
1095
|
+
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);
|
|
1558
1096
|
}
|
|
1559
1097
|
|
|
1560
|
-
.skhema-
|
|
1561
|
-
box-shadow:
|
|
1562
|
-
border-color: var(--skhema-primary);
|
|
1098
|
+
.skhema-card:hover {
|
|
1099
|
+
box-shadow: ${validation.CARD_SHADOW_LG} !important;
|
|
1563
1100
|
transform: translateY(-1px);
|
|
1564
1101
|
}
|
|
1565
1102
|
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
display: flex;
|
|
1569
|
-
align-items: center;
|
|
1570
|
-
gap: 8px;
|
|
1571
|
-
padding: 12px 16px;
|
|
1572
|
-
border-bottom: 1px solid var(--skhema-border);
|
|
1573
|
-
}
|
|
1574
|
-
|
|
1575
|
-
/* Content section */
|
|
1576
|
-
.skhema-content {
|
|
1577
|
-
padding: 16px;
|
|
1103
|
+
.skhema-card[data-skhema-kind="element"]:hover {
|
|
1104
|
+
border-color: ${validation.PRIMARY_HEX} !important;
|
|
1578
1105
|
}
|
|
1579
1106
|
|
|
1580
|
-
.skhema-
|
|
1581
|
-
|
|
1582
|
-
line-height: 1.6;
|
|
1583
|
-
color: var(--skhema-text);
|
|
1584
|
-
margin: 0;
|
|
1585
|
-
word-wrap: break-word;
|
|
1586
|
-
overflow-wrap: break-word;
|
|
1587
|
-
hyphens: auto;
|
|
1588
|
-
max-width: 100%;
|
|
1107
|
+
.skhema-save-btn {
|
|
1108
|
+
transition: background 0.15s ease;
|
|
1589
1109
|
}
|
|
1590
1110
|
|
|
1591
|
-
|
|
1592
|
-
.
|
|
1593
|
-
padding: 12px 16px;
|
|
1594
|
-
border-top: 1px solid var(--skhema-border);
|
|
1111
|
+
.skhema-save-btn:hover {
|
|
1112
|
+
background: ${validation.PRIMARY_HOVER_HEX} !important;
|
|
1595
1113
|
}
|
|
1596
1114
|
|
|
1597
|
-
.skhema-
|
|
1598
|
-
|
|
1599
|
-
align-items: center;
|
|
1600
|
-
justify-content: space-between;
|
|
1601
|
-
gap: 12px;
|
|
1115
|
+
.skhema-save-btn:active {
|
|
1116
|
+
background: ${validation.PRIMARY_PRESSED_HEX} !important;
|
|
1602
1117
|
}
|
|
1603
1118
|
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1119
|
+
/* Error card wrapper (browser-only state) */
|
|
1120
|
+
.skhema-element-card {
|
|
1121
|
+
background: var(--skhema-card);
|
|
1122
|
+
border: 1px solid var(--skhema-border);
|
|
1123
|
+
border-radius: calc(var(--skhema-radius) + 2px);
|
|
1124
|
+
box-shadow: var(--skhema-shadow);
|
|
1125
|
+
max-width: 600px;
|
|
1126
|
+
margin: 8px 0;
|
|
1127
|
+
overflow: hidden;
|
|
1611
1128
|
}
|
|
1612
1129
|
|
|
1613
1130
|
/* Error state */
|
|
@@ -1707,49 +1224,12 @@ ${SHARED_CARD_STYLES}
|
|
|
1707
1224
|
}
|
|
1708
1225
|
}
|
|
1709
1226
|
|
|
1710
|
-
/* Responsive design */
|
|
1711
|
-
@media (max-width: 640px) {
|
|
1712
|
-
.skhema-element-card {
|
|
1713
|
-
margin: 4px 0;
|
|
1714
|
-
}
|
|
1715
|
-
|
|
1716
|
-
.skhema-header {
|
|
1717
|
-
padding: 10px 12px;
|
|
1718
|
-
}
|
|
1719
|
-
|
|
1720
|
-
.skhema-content {
|
|
1721
|
-
padding: 12px;
|
|
1722
|
-
}
|
|
1723
|
-
|
|
1724
|
-
.skhema-footer {
|
|
1725
|
-
padding: 10px 12px;
|
|
1726
|
-
}
|
|
1727
|
-
|
|
1728
|
-
.skhema-footer-row {
|
|
1729
|
-
flex-direction: column;
|
|
1730
|
-
align-items: stretch;
|
|
1731
|
-
gap: 8px;
|
|
1732
|
-
}
|
|
1733
|
-
|
|
1734
|
-
.skhema-save-btn {
|
|
1735
|
-
justify-content: center;
|
|
1736
|
-
}
|
|
1737
|
-
}
|
|
1738
|
-
|
|
1739
1227
|
/* Accessibility */
|
|
1740
1228
|
@media (prefers-reduced-motion: reduce) {
|
|
1741
|
-
.skhema-
|
|
1229
|
+
.skhema-card,
|
|
1742
1230
|
.skhema-save-btn {
|
|
1743
1231
|
transition: none;
|
|
1744
1232
|
}
|
|
1745
|
-
|
|
1746
|
-
.skhema-save-btn::after {
|
|
1747
|
-
transition: none;
|
|
1748
|
-
}
|
|
1749
|
-
|
|
1750
|
-
.skhema-save-btn:hover::after {
|
|
1751
|
-
transform: none;
|
|
1752
|
-
}
|
|
1753
1233
|
}
|
|
1754
1234
|
|
|
1755
1235
|
.skhema-structured-data {
|
|
@@ -1786,11 +1266,11 @@ class SkhemaElement extends HTMLElement {
|
|
|
1786
1266
|
this.componentConnected = true;
|
|
1787
1267
|
if (this.closest("skhema-component")) {
|
|
1788
1268
|
this.nestedMode = true;
|
|
1789
|
-
const validation = validateAttributes(this);
|
|
1790
|
-
if (!validation.isValid) {
|
|
1269
|
+
const validation$1 = validation.validateAttributes(this);
|
|
1270
|
+
if (!validation$1.isValid) {
|
|
1791
1271
|
console.warn(
|
|
1792
1272
|
"skhema-element: invalid attributes in nested mode",
|
|
1793
|
-
validation.errors
|
|
1273
|
+
validation$1.errors
|
|
1794
1274
|
);
|
|
1795
1275
|
return;
|
|
1796
1276
|
}
|
|
@@ -1800,8 +1280,8 @@ class SkhemaElement extends HTMLElement {
|
|
|
1800
1280
|
return;
|
|
1801
1281
|
}
|
|
1802
1282
|
this.contentData = {
|
|
1803
|
-
contributor_id: validation.contributorId,
|
|
1804
|
-
element_type: validation.elementType,
|
|
1283
|
+
contributor_id: validation$1.contributorId,
|
|
1284
|
+
element_type: validation$1.elementType,
|
|
1805
1285
|
content,
|
|
1806
1286
|
content_hash: generateContentHash(content),
|
|
1807
1287
|
source_url: this.getAttribute("source-url") || window.location.href,
|
|
@@ -1849,9 +1329,9 @@ class SkhemaElement extends HTMLElement {
|
|
|
1849
1329
|
};
|
|
1850
1330
|
}
|
|
1851
1331
|
render() {
|
|
1852
|
-
const validation = validateAttributes(this);
|
|
1853
|
-
if (!validation.isValid) {
|
|
1854
|
-
this.renderError("Invalid component attributes", validation.errors);
|
|
1332
|
+
const validation$1 = validation.validateAttributes(this);
|
|
1333
|
+
if (!validation$1.isValid) {
|
|
1334
|
+
this.renderError("Invalid component attributes", validation$1.errors);
|
|
1855
1335
|
return;
|
|
1856
1336
|
}
|
|
1857
1337
|
const content = this.getContent();
|
|
@@ -1861,7 +1341,7 @@ class SkhemaElement extends HTMLElement {
|
|
|
1861
1341
|
]);
|
|
1862
1342
|
return;
|
|
1863
1343
|
}
|
|
1864
|
-
const securityValidation = validateContentSecurity(content);
|
|
1344
|
+
const securityValidation = render.validateContentSecurity(content);
|
|
1865
1345
|
if (!securityValidation.isSecure) {
|
|
1866
1346
|
this.renderError(
|
|
1867
1347
|
"Content security validation failed",
|
|
@@ -1870,8 +1350,8 @@ class SkhemaElement extends HTMLElement {
|
|
|
1870
1350
|
return;
|
|
1871
1351
|
}
|
|
1872
1352
|
this.contentData = {
|
|
1873
|
-
contributor_id: validation.contributorId,
|
|
1874
|
-
element_type: validation.elementType,
|
|
1353
|
+
contributor_id: validation$1.contributorId,
|
|
1354
|
+
element_type: validation$1.elementType,
|
|
1875
1355
|
content,
|
|
1876
1356
|
content_hash: generateContentHash(content),
|
|
1877
1357
|
source_url: this.getAttribute("source-url") || window.location.href,
|
|
@@ -1887,7 +1367,6 @@ class SkhemaElement extends HTMLElement {
|
|
|
1887
1367
|
renderContent() {
|
|
1888
1368
|
if (!this.contentData) return;
|
|
1889
1369
|
const { element_type, contributor_id, content } = this.contentData;
|
|
1890
|
-
const label = getElementTypeLabel(element_type);
|
|
1891
1370
|
const redirectUrl = generateRedirectUrl(
|
|
1892
1371
|
content,
|
|
1893
1372
|
element_type,
|
|
@@ -1895,50 +1374,20 @@ class SkhemaElement extends HTMLElement {
|
|
|
1895
1374
|
);
|
|
1896
1375
|
const themeAttribute = this.getAttribute("theme") || "auto";
|
|
1897
1376
|
const actualTheme = this.getActualTheme(themeAttribute);
|
|
1898
|
-
const authorFooterHtml = resolveAuthorFooterHtml(this, contributor_id);
|
|
1899
|
-
const componentType = resolveComponentType(element_type);
|
|
1900
|
-
const acronym = getComponentTypeAcronym(componentType);
|
|
1901
|
-
const colors = COMPONENT_COLORS[componentType];
|
|
1902
1377
|
const ariaAttrs = createAriaAttributes(element_type);
|
|
1903
1378
|
Object.entries(ariaAttrs).forEach(([key, value]) => {
|
|
1904
1379
|
this.setAttribute(key, value);
|
|
1905
1380
|
});
|
|
1906
|
-
const
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
</div>
|
|
1917
|
-
|
|
1918
|
-
<div class="skhema-content">
|
|
1919
|
-
<div class="skhema-content-text">${sanitizeContent(content)}</div>
|
|
1920
|
-
</div>
|
|
1921
|
-
|
|
1922
|
-
<div class="skhema-footer">
|
|
1923
|
-
<div class="skhema-footer-row">
|
|
1924
|
-
<span class="skhema-contributor-line">
|
|
1925
|
-
${USER_ICON_SVG}
|
|
1926
|
-
${authorFooterHtml}
|
|
1927
|
-
</span>
|
|
1928
|
-
<a href="${redirectUrl}"
|
|
1929
|
-
class="skhema-save-btn"
|
|
1930
|
-
target="_blank"
|
|
1931
|
-
rel="noopener noreferrer"
|
|
1932
|
-
title="Save this element to Skhema">
|
|
1933
|
-
Save to Skhema
|
|
1934
|
-
</a>
|
|
1935
|
-
</div>
|
|
1936
|
-
<div class="skhema-footer-attribution">
|
|
1937
|
-
Strategy powered by <a href="https://skhema.com" target="_blank" rel="noopener noreferrer">Skhema</a>
|
|
1938
|
-
</div>
|
|
1939
|
-
</div>
|
|
1940
|
-
</div>
|
|
1941
|
-
`;
|
|
1381
|
+
const cardHtml = render.renderElementCardHtml({
|
|
1382
|
+
elementType: element_type,
|
|
1383
|
+
content,
|
|
1384
|
+
saveUrl: redirectUrl,
|
|
1385
|
+
authorName: this.getAttribute("author-name") || this.getAttribute("contributor-name"),
|
|
1386
|
+
authorSlug: this.getAttribute("author-slug"),
|
|
1387
|
+
contributorId: contributor_id,
|
|
1388
|
+
theme: actualTheme
|
|
1389
|
+
});
|
|
1390
|
+
this.shadow.innerHTML = `<style>${styles}</style>${cardHtml}`;
|
|
1942
1391
|
const saveBtn = this.shadow.querySelector(
|
|
1943
1392
|
".skhema-save-btn"
|
|
1944
1393
|
);
|
|
@@ -2126,12 +1575,8 @@ class SkhemaElement extends HTMLElement {
|
|
|
2126
1575
|
}
|
|
2127
1576
|
updateTheme() {
|
|
2128
1577
|
const themeAttribute = this.getAttribute("theme") || "auto";
|
|
2129
|
-
if (themeAttribute === "auto") {
|
|
2130
|
-
|
|
2131
|
-
if (card) {
|
|
2132
|
-
const newTheme = this.getActualTheme("auto");
|
|
2133
|
-
card.setAttribute("data-theme", newTheme);
|
|
2134
|
-
}
|
|
1578
|
+
if (themeAttribute === "auto" && this.contentData && !this.nestedMode) {
|
|
1579
|
+
this.renderContent();
|
|
2135
1580
|
}
|
|
2136
1581
|
}
|
|
2137
1582
|
}
|
|
@@ -2165,6 +1610,15 @@ if (typeof window !== "undefined") {
|
|
|
2165
1610
|
);
|
|
2166
1611
|
}
|
|
2167
1612
|
}
|
|
1613
|
+
exports.getComponentTypeAcronym = validation.getComponentTypeAcronym;
|
|
1614
|
+
exports.getComponentTypeLabel = validation.getComponentTypeLabel;
|
|
1615
|
+
exports.getElementTypeAcronym = validation.getElementTypeAcronym;
|
|
1616
|
+
exports.getElementTypeLabel = validation.getElementTypeLabel;
|
|
1617
|
+
exports.isValidComponentType = validation.isValidComponentType;
|
|
1618
|
+
exports.isValidElementType = validation.isValidElementType;
|
|
1619
|
+
exports.resolveComponentType = validation.resolveComponentType;
|
|
1620
|
+
exports.validateAttributes = validation.validateAttributes;
|
|
1621
|
+
exports.validateElementBelongsToComponent = validation.validateElementBelongsToComponent;
|
|
2168
1622
|
exports.SkhemaComponent = SkhemaComponent;
|
|
2169
1623
|
exports.SkhemaElement = SkhemaElement;
|
|
2170
1624
|
exports.default = SkhemaElement;
|
|
@@ -2173,18 +1627,9 @@ exports.generateComponentRedirectUrl = generateComponentRedirectUrl;
|
|
|
2173
1627
|
exports.generateComponentStructuredData = generateComponentStructuredData;
|
|
2174
1628
|
exports.generateRedirectUrl = generateRedirectUrl;
|
|
2175
1629
|
exports.generateStructuredData = generateStructuredData;
|
|
2176
|
-
exports.getComponentTypeAcronym = getComponentTypeAcronym;
|
|
2177
|
-
exports.getComponentTypeLabel = getComponentTypeLabel;
|
|
2178
|
-
exports.getElementTypeAcronym = getElementTypeAcronym;
|
|
2179
|
-
exports.getElementTypeLabel = getElementTypeLabel;
|
|
2180
|
-
exports.isValidComponentType = isValidComponentType;
|
|
2181
|
-
exports.isValidElementType = isValidElementType;
|
|
2182
1630
|
exports.registerSkhemaComponent = registerSkhemaComponent;
|
|
2183
1631
|
exports.registerSkhemaElement = registerSkhemaElement;
|
|
2184
|
-
exports.resolveComponentType = resolveComponentType;
|
|
2185
1632
|
exports.shouldTrackAnalytics = shouldTrackAnalytics;
|
|
2186
1633
|
exports.trackComponentClick = trackComponentClick;
|
|
2187
1634
|
exports.trackComponentEmbedLoad = trackComponentEmbedLoad;
|
|
2188
|
-
exports.validateAttributes = validateAttributes;
|
|
2189
|
-
exports.validateElementBelongsToComponent = validateElementBelongsToComponent;
|
|
2190
1635
|
//# sourceMappingURL=index.cjs.map
|