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