@skhema/embed 0.1.9 → 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 +79 -440
- package/dist/index.cjs.map +1 -1
- package/dist/index.es.js +81 -442
- 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 +1 -1
- package/dist/tokens.d.ts +1 -1
- package/dist/tokens.d.ts.map +1 -1
- package/dist/tokens.es.js +1 -1
- 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-CNWaQfh_.cjs → validation-DCw9rQeH.cjs} +40 -95
- package/dist/validation-DCw9rQeH.cjs.map +1 -0
- package/dist/{validation-MSiRF5Wt.js → validation-Dt4L_df8.js} +42 -97
- package/dist/validation-Dt4L_df8.js.map +1 -0
- package/package.json +11 -4
- package/dist/utils/author-attribution.d.ts +0 -16
- package/dist/utils/author-attribution.d.ts.map +0 -1
- package/dist/validation-CNWaQfh_.cjs.map +0 -1
- package/dist/validation-MSiRF5Wt.js.map +0 -1
package/dist/index.es.js
CHANGED
|
@@ -1,39 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
const authorName = element.getAttribute("author-name") || element.getAttribute("contributor-name");
|
|
5
|
-
if (!authorName?.trim()) {
|
|
6
|
-
return null;
|
|
7
|
-
}
|
|
8
|
-
const authorSlug = element.getAttribute("author-slug")?.trim();
|
|
9
|
-
return {
|
|
10
|
-
authorName: authorName.trim(),
|
|
11
|
-
authorSlug: authorSlug || void 0
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
function escapeHtml(text) {
|
|
15
|
-
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
16
|
-
}
|
|
17
|
-
function formatAuthorAttributionHtml(authorName, authorSlug) {
|
|
18
|
-
const label = `By ${escapeHtml(authorName)}`;
|
|
19
|
-
if (authorSlug) {
|
|
20
|
-
return `<a href="https://skhema.com/contributors/${encodeURIComponent(authorSlug)}" class="skhema-author-link" target="_blank" rel="noopener noreferrer">${label}</a>`;
|
|
21
|
-
}
|
|
22
|
-
return `<span class="skhema-author-text">${label}</span>`;
|
|
23
|
-
}
|
|
24
|
-
function formatContributorIdFallback(contributorId) {
|
|
25
|
-
return contributorId.split(/[_-]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
|
|
26
|
-
}
|
|
27
|
-
function resolveAuthorFooterHtml(element, contributorId) {
|
|
28
|
-
const attribution = readAuthorAttribution(element);
|
|
29
|
-
if (attribution) {
|
|
30
|
-
return formatAuthorAttributionHtml(
|
|
31
|
-
attribution.authorName,
|
|
32
|
-
attribution.authorSlug
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
return `<span class="skhema-author-text">By ${escapeHtml(formatContributorIdFallback(contributorId))}</span>`;
|
|
36
|
-
}
|
|
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";
|
|
37
4
|
const ANALYTICS_ENDPOINT = "https://analytics.skhema.com/functions/v1/embed-manage";
|
|
38
5
|
const TRACKING_COOKIE_NAME = "_sk";
|
|
39
6
|
const TRACKING_EXPIRY_HOURS = 24;
|
|
@@ -426,75 +393,6 @@ function generateComponentHash(elements) {
|
|
|
426
393
|
const combined = sorted.map((e) => `${e.elementType}:${e.content}`).join("|");
|
|
427
394
|
return generateContentHash(combined);
|
|
428
395
|
}
|
|
429
|
-
function sanitizeContent(content) {
|
|
430
|
-
const htmlEncode = (str) => {
|
|
431
|
-
const div = document.createElement("div");
|
|
432
|
-
div.textContent = str;
|
|
433
|
-
return div.innerHTML;
|
|
434
|
-
};
|
|
435
|
-
let sanitized = stripUrls(content);
|
|
436
|
-
sanitized = htmlEncode(sanitized);
|
|
437
|
-
sanitized = sanitized.replace(/\n/g, "<br>");
|
|
438
|
-
sanitized = applyTextWrapping(sanitized);
|
|
439
|
-
return sanitized;
|
|
440
|
-
}
|
|
441
|
-
function stripUrls(text) {
|
|
442
|
-
const patterns = [
|
|
443
|
-
// Standard URLs with protocols
|
|
444
|
-
/https?:\/\/[^\s<>"{}|\\^`[\]]+/gi,
|
|
445
|
-
// FTP URLs
|
|
446
|
-
/ftp:\/\/[^\s<>"{}|\\^`[\]]+/gi,
|
|
447
|
-
// URLs without protocol but with www
|
|
448
|
-
/www\.[^\s<>"{}|\\^`[\]]+/gi,
|
|
449
|
-
// Email-like patterns
|
|
450
|
-
/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/gi,
|
|
451
|
-
// Common domain patterns (anything.com, anything.org, etc.)
|
|
452
|
-
/(?:^|\s)([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(?:\/[^\s]*)?/gi
|
|
453
|
-
];
|
|
454
|
-
let stripped = text;
|
|
455
|
-
patterns.forEach((pattern) => {
|
|
456
|
-
stripped = stripped.replace(pattern, "");
|
|
457
|
-
});
|
|
458
|
-
stripped = stripped.replace(/\s+/g, " ").trim();
|
|
459
|
-
return stripped;
|
|
460
|
-
}
|
|
461
|
-
function applyTextWrapping(text) {
|
|
462
|
-
const words = text.split(/(\s+)/);
|
|
463
|
-
return words.map((word) => {
|
|
464
|
-
if (/^\s+$/.test(word) || word.includes("<")) {
|
|
465
|
-
return word;
|
|
466
|
-
}
|
|
467
|
-
if (word.length > 30) {
|
|
468
|
-
return word.replace(/(.{10})/g, "$1");
|
|
469
|
-
}
|
|
470
|
-
return word;
|
|
471
|
-
}).join("");
|
|
472
|
-
}
|
|
473
|
-
function validateContentSecurity(content) {
|
|
474
|
-
const issues = [];
|
|
475
|
-
if (/<script[\s>]/i.test(content)) {
|
|
476
|
-
issues.push("Script tags detected");
|
|
477
|
-
}
|
|
478
|
-
if (/on\w+\s*=/i.test(content)) {
|
|
479
|
-
issues.push("Event handlers detected");
|
|
480
|
-
}
|
|
481
|
-
if (/javascript:/i.test(content)) {
|
|
482
|
-
issues.push("JavaScript protocol detected");
|
|
483
|
-
}
|
|
484
|
-
if (/data:[^,]*script/i.test(content)) {
|
|
485
|
-
issues.push("Data URL with script detected");
|
|
486
|
-
}
|
|
487
|
-
if (/<iframe[\s>]/i.test(content)) {
|
|
488
|
-
issues.push("Iframe tags detected");
|
|
489
|
-
}
|
|
490
|
-
if (/https?:\/\//i.test(content) || /www\./i.test(content)) {
|
|
491
|
-
issues.push("URLs detected in content");
|
|
492
|
-
}
|
|
493
|
-
return {
|
|
494
|
-
isSecure: issues.length === 0,
|
|
495
|
-
issues
|
|
496
|
-
};
|
|
497
|
-
}
|
|
498
396
|
function generateStructuredData(content, elementType, contributorId, sourceUrl) {
|
|
499
397
|
return {
|
|
500
398
|
"@context": "https://schema.org",
|
|
@@ -626,7 +524,7 @@ const styles$1 = `
|
|
|
626
524
|
color: var(--skhema-text);
|
|
627
525
|
}
|
|
628
526
|
|
|
629
|
-
/* Dark mode */
|
|
527
|
+
/* Dark mode (skeleton/error only) */
|
|
630
528
|
.skhema-component-card[data-theme="dark"],
|
|
631
529
|
.skhema-skeleton[data-theme="dark"] {
|
|
632
530
|
--skhema-bg: hsl(222.2 84% 4.9%);
|
|
@@ -638,106 +536,40 @@ const styles$1 = `
|
|
|
638
536
|
--skhema-surface-subtle: hsl(217.2 32.6% 12%);
|
|
639
537
|
}
|
|
640
538
|
|
|
641
|
-
/*
|
|
642
|
-
.
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
border: 1px solid var(--skhema-border);
|
|
646
|
-
border-radius: calc(var(--skhema-radius) + 2px);
|
|
647
|
-
padding: 0;
|
|
648
|
-
box-shadow: var(--skhema-shadow);
|
|
649
|
-
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
650
|
-
max-width: 600px;
|
|
651
|
-
margin: 8px 0;
|
|
652
|
-
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);
|
|
653
543
|
}
|
|
654
544
|
|
|
655
|
-
.skhema-
|
|
656
|
-
box-shadow:
|
|
545
|
+
.skhema-card:hover {
|
|
546
|
+
box-shadow: ${CARD_SHADOW_LG} !important;
|
|
657
547
|
transform: translateY(-1px);
|
|
658
548
|
}
|
|
659
549
|
|
|
660
|
-
|
|
661
|
-
.
|
|
662
|
-
height: 2px;
|
|
663
|
-
width: 100%;
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
/* Header */
|
|
667
|
-
.skhema-header {
|
|
668
|
-
display: flex;
|
|
669
|
-
align-items: center;
|
|
670
|
-
gap: 8px;
|
|
671
|
-
padding: 12px 16px;
|
|
672
|
-
border-bottom: 1px solid var(--skhema-border);
|
|
550
|
+
.skhema-save-btn {
|
|
551
|
+
transition: background 0.15s ease;
|
|
673
552
|
}
|
|
674
553
|
|
|
675
|
-
.skhema-
|
|
676
|
-
|
|
677
|
-
font-weight: 500;
|
|
678
|
-
color: var(--skhema-text-muted);
|
|
554
|
+
.skhema-save-btn:hover {
|
|
555
|
+
background: ${PRIMARY_HOVER_HEX} !important;
|
|
679
556
|
}
|
|
680
557
|
|
|
681
|
-
.skhema-
|
|
682
|
-
|
|
683
|
-
font-size: 13px;
|
|
558
|
+
.skhema-save-btn:active {
|
|
559
|
+
background: ${PRIMARY_PRESSED_HEX} !important;
|
|
684
560
|
}
|
|
685
561
|
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
display: flex;
|
|
696
|
-
flex-direction: column;
|
|
697
|
-
gap: 16px;
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
/* Element group */
|
|
701
|
-
.skhema-element-group {
|
|
702
|
-
display: flex;
|
|
703
|
-
flex-direction: column;
|
|
704
|
-
gap: 4px;
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
.skhema-element-label {
|
|
708
|
-
text-transform: uppercase;
|
|
709
|
-
letter-spacing: 0.05em;
|
|
710
|
-
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
|
|
711
|
-
font-size: 10px;
|
|
712
|
-
font-weight: 600;
|
|
713
|
-
color: var(--skhema-text-muted);
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
.skhema-element-content {
|
|
717
|
-
font-size: 14px;
|
|
718
|
-
line-height: 1.6;
|
|
719
|
-
color: var(--skhema-text);
|
|
720
|
-
padding-left: 8px;
|
|
721
|
-
border-left: 2px solid var(--skhema-border);
|
|
722
|
-
word-wrap: break-word;
|
|
723
|
-
overflow-wrap: break-word;
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
/* Footer */
|
|
727
|
-
.skhema-footer {
|
|
728
|
-
padding: 12px 16px;
|
|
729
|
-
border-top: 1px solid var(--skhema-border);
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
.skhema-footer-row {
|
|
733
|
-
display: flex;
|
|
734
|
-
align-items: center;
|
|
735
|
-
justify-content: space-between;
|
|
736
|
-
gap: 12px;
|
|
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;
|
|
737
571
|
}
|
|
738
572
|
|
|
739
|
-
${SHARED_CARD_STYLES}
|
|
740
|
-
|
|
741
573
|
/* Error state */
|
|
742
574
|
.skhema-error {
|
|
743
575
|
background: hsl(0 93% 94%);
|
|
@@ -822,39 +654,9 @@ ${SHARED_CARD_STYLES}
|
|
|
822
654
|
100% { background-position: 200% 0; }
|
|
823
655
|
}
|
|
824
656
|
|
|
825
|
-
/* Responsive */
|
|
826
|
-
@media (max-width: 640px) {
|
|
827
|
-
.skhema-component-card {
|
|
828
|
-
margin: 4px 0;
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
.skhema-header {
|
|
832
|
-
padding: 10px 12px;
|
|
833
|
-
flex-wrap: wrap;
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
.skhema-elements {
|
|
837
|
-
padding: 12px;
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
.skhema-footer {
|
|
841
|
-
padding: 10px 12px;
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
.skhema-footer-row {
|
|
845
|
-
flex-direction: column;
|
|
846
|
-
align-items: stretch;
|
|
847
|
-
gap: 8px;
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
.skhema-save-btn {
|
|
851
|
-
justify-content: center;
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
|
-
|
|
855
657
|
/* Accessibility */
|
|
856
658
|
@media (prefers-reduced-motion: reduce) {
|
|
857
|
-
.skhema-
|
|
659
|
+
.skhema-card,
|
|
858
660
|
.skhema-save-btn {
|
|
859
661
|
transition: none;
|
|
860
662
|
}
|
|
@@ -1020,9 +822,6 @@ class SkhemaComponent extends HTMLElement {
|
|
|
1020
822
|
if (!this.contentData) return;
|
|
1021
823
|
const { component_type, contributor_id, title } = this.contentData;
|
|
1022
824
|
const componentLabel = getComponentTypeLabel(component_type);
|
|
1023
|
-
const acronym = getComponentTypeAcronym(component_type);
|
|
1024
|
-
const colors = COMPONENT_COLORS[component_type];
|
|
1025
|
-
const authorFooterHtml = resolveAuthorFooterHtml(this, contributor_id);
|
|
1026
825
|
const themeAttribute = this.getAttribute("theme") || "auto";
|
|
1027
826
|
const actualTheme = this.getActualTheme(themeAttribute);
|
|
1028
827
|
const redirectUrl = generateComponentRedirectUrl(
|
|
@@ -1030,65 +829,25 @@ class SkhemaComponent extends HTMLElement {
|
|
|
1030
829
|
component_type,
|
|
1031
830
|
contributor_id
|
|
1032
831
|
);
|
|
1033
|
-
const badgeStyle = colors ? `background: ${colors.bg}; color: ${colors.text}; border: 1px solid ${colors.border};` : "";
|
|
1034
|
-
const topBorderColor = colors ? colors.text : "var(--skhema-primary)";
|
|
1035
|
-
const groupedElements = this.groupElementsByType(elements);
|
|
1036
|
-
const elementSectionsHtml = groupedElements.map(
|
|
1037
|
-
(group) => `
|
|
1038
|
-
<div class="skhema-element-group">
|
|
1039
|
-
<div class="skhema-element-label">${getElementTypeLabel(group.elementType)}</div>
|
|
1040
|
-
${group.items.map(
|
|
1041
|
-
(item) => `<div class="skhema-element-content">${sanitizeContent(item.content)}</div>`
|
|
1042
|
-
).join("")}
|
|
1043
|
-
</div>
|
|
1044
|
-
`
|
|
1045
|
-
).join("");
|
|
1046
832
|
this.setAttribute("role", "article");
|
|
1047
833
|
this.setAttribute(
|
|
1048
834
|
"aria-label",
|
|
1049
835
|
`${componentLabel} component${title ? ` — ${title}` : ""}`
|
|
1050
836
|
);
|
|
1051
|
-
const
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
<div class="skhema-elements">
|
|
1067
|
-
${elementSectionsHtml}
|
|
1068
|
-
</div>
|
|
1069
|
-
|
|
1070
|
-
<div class="skhema-footer">
|
|
1071
|
-
<div class="skhema-footer-row">
|
|
1072
|
-
<span class="skhema-contributor-line">
|
|
1073
|
-
${USER_ICON_SVG}
|
|
1074
|
-
${authorFooterHtml}
|
|
1075
|
-
</span>
|
|
1076
|
-
<a href="${redirectUrl}"
|
|
1077
|
-
class="skhema-save-btn"
|
|
1078
|
-
target="_blank"
|
|
1079
|
-
rel="noopener noreferrer"
|
|
1080
|
-
title="Save this component to Skhema">
|
|
1081
|
-
Save to Skhema
|
|
1082
|
-
</a>
|
|
1083
|
-
</div>
|
|
1084
|
-
<div class="skhema-footer-attribution">
|
|
1085
|
-
Strategy powered by <a href="https://skhema.com" target="_blank" rel="noopener noreferrer">Skhema</a>
|
|
1086
|
-
</div>
|
|
1087
|
-
</div>
|
|
1088
|
-
</div>
|
|
1089
|
-
|
|
1090
|
-
<slot style="display:none;"></slot>
|
|
1091
|
-
`;
|
|
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>`;
|
|
1092
851
|
const saveBtn = this.shadow.querySelector(
|
|
1093
852
|
".skhema-save-btn"
|
|
1094
853
|
);
|
|
@@ -1098,21 +857,6 @@ class SkhemaComponent extends HTMLElement {
|
|
|
1098
857
|
});
|
|
1099
858
|
}
|
|
1100
859
|
}
|
|
1101
|
-
groupElementsByType(elements) {
|
|
1102
|
-
const groups = /* @__PURE__ */ new Map();
|
|
1103
|
-
for (const el of elements) {
|
|
1104
|
-
const existing = groups.get(el.elementType);
|
|
1105
|
-
if (existing) {
|
|
1106
|
-
existing.push(el);
|
|
1107
|
-
} else {
|
|
1108
|
-
groups.set(el.elementType, [el]);
|
|
1109
|
-
}
|
|
1110
|
-
}
|
|
1111
|
-
return Array.from(groups.entries()).map(([elementType, items]) => ({
|
|
1112
|
-
elementType,
|
|
1113
|
-
items
|
|
1114
|
-
}));
|
|
1115
|
-
}
|
|
1116
860
|
getActualTheme(themeAttribute) {
|
|
1117
861
|
if (themeAttribute === "light" || themeAttribute === "dark") {
|
|
1118
862
|
return themeAttribute;
|
|
@@ -1300,12 +1044,8 @@ class SkhemaComponent extends HTMLElement {
|
|
|
1300
1044
|
}
|
|
1301
1045
|
updateTheme() {
|
|
1302
1046
|
const themeAttribute = this.getAttribute("theme") || "auto";
|
|
1303
|
-
if (themeAttribute === "auto") {
|
|
1304
|
-
|
|
1305
|
-
if (card) {
|
|
1306
|
-
const newTheme = this.getActualTheme("auto");
|
|
1307
|
-
card.setAttribute("data-theme", newTheme);
|
|
1308
|
-
}
|
|
1047
|
+
if (themeAttribute === "auto" && this.contentData) {
|
|
1048
|
+
this.scheduleRender();
|
|
1309
1049
|
}
|
|
1310
1050
|
}
|
|
1311
1051
|
}
|
|
@@ -1337,7 +1077,7 @@ const styles = `
|
|
|
1337
1077
|
color: var(--skhema-text);
|
|
1338
1078
|
}
|
|
1339
1079
|
|
|
1340
|
-
/* Dark mode styles - applied via data-theme attribute */
|
|
1080
|
+
/* Dark mode styles - applied via data-theme attribute (skeleton/error only) */
|
|
1341
1081
|
.skhema-element-card[data-theme="dark"],
|
|
1342
1082
|
.skhema-skeleton[data-theme="dark"] {
|
|
1343
1083
|
--skhema-bg: hsl(222.2 84% 4.9%);
|
|
@@ -1348,71 +1088,42 @@ const styles = `
|
|
|
1348
1088
|
--skhema-accent: hsl(217.2 32.6% 17.5%);
|
|
1349
1089
|
}
|
|
1350
1090
|
|
|
1351
|
-
/*
|
|
1352
|
-
.
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
border: 1px solid var(--skhema-border);
|
|
1356
|
-
border-radius: calc(var(--skhema-radius) + 2px);
|
|
1357
|
-
padding: 0;
|
|
1358
|
-
box-shadow: var(--skhema-shadow);
|
|
1359
|
-
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
1360
|
-
max-width: 600px;
|
|
1361
|
-
margin: 8px 0;
|
|
1362
|
-
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);
|
|
1363
1095
|
}
|
|
1364
1096
|
|
|
1365
|
-
.skhema-
|
|
1366
|
-
box-shadow:
|
|
1367
|
-
border-color: var(--skhema-primary);
|
|
1097
|
+
.skhema-card:hover {
|
|
1098
|
+
box-shadow: ${CARD_SHADOW_LG} !important;
|
|
1368
1099
|
transform: translateY(-1px);
|
|
1369
1100
|
}
|
|
1370
1101
|
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
display: flex;
|
|
1374
|
-
align-items: center;
|
|
1375
|
-
gap: 8px;
|
|
1376
|
-
padding: 12px 16px;
|
|
1377
|
-
border-bottom: 1px solid var(--skhema-border);
|
|
1378
|
-
}
|
|
1379
|
-
|
|
1380
|
-
/* Content section */
|
|
1381
|
-
.skhema-content {
|
|
1382
|
-
padding: 16px;
|
|
1102
|
+
.skhema-card[data-skhema-kind="element"]:hover {
|
|
1103
|
+
border-color: ${PRIMARY_HEX} !important;
|
|
1383
1104
|
}
|
|
1384
1105
|
|
|
1385
|
-
.skhema-
|
|
1386
|
-
|
|
1387
|
-
line-height: 1.6;
|
|
1388
|
-
color: var(--skhema-text);
|
|
1389
|
-
margin: 0;
|
|
1390
|
-
word-wrap: break-word;
|
|
1391
|
-
overflow-wrap: break-word;
|
|
1392
|
-
hyphens: auto;
|
|
1393
|
-
max-width: 100%;
|
|
1106
|
+
.skhema-save-btn {
|
|
1107
|
+
transition: background 0.15s ease;
|
|
1394
1108
|
}
|
|
1395
1109
|
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
padding: 12px 16px;
|
|
1399
|
-
border-top: 1px solid var(--skhema-border);
|
|
1110
|
+
.skhema-save-btn:hover {
|
|
1111
|
+
background: ${PRIMARY_HOVER_HEX} !important;
|
|
1400
1112
|
}
|
|
1401
1113
|
|
|
1402
|
-
.skhema-
|
|
1403
|
-
|
|
1404
|
-
align-items: center;
|
|
1405
|
-
justify-content: space-between;
|
|
1406
|
-
gap: 12px;
|
|
1114
|
+
.skhema-save-btn:active {
|
|
1115
|
+
background: ${PRIMARY_PRESSED_HEX} !important;
|
|
1407
1116
|
}
|
|
1408
1117
|
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
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;
|
|
1416
1127
|
}
|
|
1417
1128
|
|
|
1418
1129
|
/* Error state */
|
|
@@ -1512,49 +1223,12 @@ ${SHARED_CARD_STYLES}
|
|
|
1512
1223
|
}
|
|
1513
1224
|
}
|
|
1514
1225
|
|
|
1515
|
-
/* Responsive design */
|
|
1516
|
-
@media (max-width: 640px) {
|
|
1517
|
-
.skhema-element-card {
|
|
1518
|
-
margin: 4px 0;
|
|
1519
|
-
}
|
|
1520
|
-
|
|
1521
|
-
.skhema-header {
|
|
1522
|
-
padding: 10px 12px;
|
|
1523
|
-
}
|
|
1524
|
-
|
|
1525
|
-
.skhema-content {
|
|
1526
|
-
padding: 12px;
|
|
1527
|
-
}
|
|
1528
|
-
|
|
1529
|
-
.skhema-footer {
|
|
1530
|
-
padding: 10px 12px;
|
|
1531
|
-
}
|
|
1532
|
-
|
|
1533
|
-
.skhema-footer-row {
|
|
1534
|
-
flex-direction: column;
|
|
1535
|
-
align-items: stretch;
|
|
1536
|
-
gap: 8px;
|
|
1537
|
-
}
|
|
1538
|
-
|
|
1539
|
-
.skhema-save-btn {
|
|
1540
|
-
justify-content: center;
|
|
1541
|
-
}
|
|
1542
|
-
}
|
|
1543
|
-
|
|
1544
1226
|
/* Accessibility */
|
|
1545
1227
|
@media (prefers-reduced-motion: reduce) {
|
|
1546
|
-
.skhema-
|
|
1228
|
+
.skhema-card,
|
|
1547
1229
|
.skhema-save-btn {
|
|
1548
1230
|
transition: none;
|
|
1549
1231
|
}
|
|
1550
|
-
|
|
1551
|
-
.skhema-save-btn::after {
|
|
1552
|
-
transition: none;
|
|
1553
|
-
}
|
|
1554
|
-
|
|
1555
|
-
.skhema-save-btn:hover::after {
|
|
1556
|
-
transform: none;
|
|
1557
|
-
}
|
|
1558
1232
|
}
|
|
1559
1233
|
|
|
1560
1234
|
.skhema-structured-data {
|
|
@@ -1692,7 +1366,6 @@ class SkhemaElement extends HTMLElement {
|
|
|
1692
1366
|
renderContent() {
|
|
1693
1367
|
if (!this.contentData) return;
|
|
1694
1368
|
const { element_type, contributor_id, content } = this.contentData;
|
|
1695
|
-
const label = getElementTypeLabel(element_type);
|
|
1696
1369
|
const redirectUrl = generateRedirectUrl(
|
|
1697
1370
|
content,
|
|
1698
1371
|
element_type,
|
|
@@ -1700,50 +1373,20 @@ class SkhemaElement extends HTMLElement {
|
|
|
1700
1373
|
);
|
|
1701
1374
|
const themeAttribute = this.getAttribute("theme") || "auto";
|
|
1702
1375
|
const actualTheme = this.getActualTheme(themeAttribute);
|
|
1703
|
-
const authorFooterHtml = resolveAuthorFooterHtml(this, contributor_id);
|
|
1704
|
-
const componentType = resolveComponentType(element_type);
|
|
1705
|
-
const acronym = getComponentTypeAcronym(componentType);
|
|
1706
|
-
const colors = COMPONENT_COLORS[componentType];
|
|
1707
1376
|
const ariaAttrs = createAriaAttributes(element_type);
|
|
1708
1377
|
Object.entries(ariaAttrs).forEach(([key, value]) => {
|
|
1709
1378
|
this.setAttribute(key, value);
|
|
1710
1379
|
});
|
|
1711
|
-
const
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
</div>
|
|
1722
|
-
|
|
1723
|
-
<div class="skhema-content">
|
|
1724
|
-
<div class="skhema-content-text">${sanitizeContent(content)}</div>
|
|
1725
|
-
</div>
|
|
1726
|
-
|
|
1727
|
-
<div class="skhema-footer">
|
|
1728
|
-
<div class="skhema-footer-row">
|
|
1729
|
-
<span class="skhema-contributor-line">
|
|
1730
|
-
${USER_ICON_SVG}
|
|
1731
|
-
${authorFooterHtml}
|
|
1732
|
-
</span>
|
|
1733
|
-
<a href="${redirectUrl}"
|
|
1734
|
-
class="skhema-save-btn"
|
|
1735
|
-
target="_blank"
|
|
1736
|
-
rel="noopener noreferrer"
|
|
1737
|
-
title="Save this element to Skhema">
|
|
1738
|
-
Save to Skhema
|
|
1739
|
-
</a>
|
|
1740
|
-
</div>
|
|
1741
|
-
<div class="skhema-footer-attribution">
|
|
1742
|
-
Strategy powered by <a href="https://skhema.com" target="_blank" rel="noopener noreferrer">Skhema</a>
|
|
1743
|
-
</div>
|
|
1744
|
-
</div>
|
|
1745
|
-
</div>
|
|
1746
|
-
`;
|
|
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}`;
|
|
1747
1390
|
const saveBtn = this.shadow.querySelector(
|
|
1748
1391
|
".skhema-save-btn"
|
|
1749
1392
|
);
|
|
@@ -1931,12 +1574,8 @@ class SkhemaElement extends HTMLElement {
|
|
|
1931
1574
|
}
|
|
1932
1575
|
updateTheme() {
|
|
1933
1576
|
const themeAttribute = this.getAttribute("theme") || "auto";
|
|
1934
|
-
if (themeAttribute === "auto") {
|
|
1935
|
-
|
|
1936
|
-
if (card) {
|
|
1937
|
-
const newTheme = this.getActualTheme("auto");
|
|
1938
|
-
card.setAttribute("data-theme", newTheme);
|
|
1939
|
-
}
|
|
1577
|
+
if (themeAttribute === "auto" && this.contentData && !this.nestedMode) {
|
|
1578
|
+
this.renderContent();
|
|
1940
1579
|
}
|
|
1941
1580
|
}
|
|
1942
1581
|
}
|
|
@@ -1979,7 +1618,7 @@ export {
|
|
|
1979
1618
|
generateComponentStructuredData,
|
|
1980
1619
|
generateRedirectUrl,
|
|
1981
1620
|
generateStructuredData,
|
|
1982
|
-
getComponentTypeAcronym,
|
|
1621
|
+
g as getComponentTypeAcronym,
|
|
1983
1622
|
getComponentTypeLabel,
|
|
1984
1623
|
c as getElementTypeAcronym,
|
|
1985
1624
|
getElementTypeLabel,
|
|
@@ -1987,7 +1626,7 @@ export {
|
|
|
1987
1626
|
f as isValidElementType,
|
|
1988
1627
|
registerSkhemaComponent,
|
|
1989
1628
|
registerSkhemaElement,
|
|
1990
|
-
resolveComponentType,
|
|
1629
|
+
r as resolveComponentType,
|
|
1991
1630
|
shouldTrackAnalytics,
|
|
1992
1631
|
trackComponentClick,
|
|
1993
1632
|
trackComponentEmbedLoad,
|