@skhema/web-component 0.0.14 → 0.0.16

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 CHANGED
@@ -19,23 +19,23 @@ npm install @skhema/web-component
19
19
  ```
20
20
 
21
21
  ```javascript
22
- import '@skhema/web-component';
22
+ import '@skhema/web-component'
23
23
  // Component is automatically registered
24
24
  ```
25
25
 
26
26
  ## Attributes
27
27
 
28
- | Attribute | Required | Description |
29
- |-----------|----------|-------------|
30
- | `element-type` | ✓ | Type of strategic element |
31
- | `contributor-id` | ✓ | Your contributor identifier |
32
- | `content` | | Alternative to inner text |
33
- | `theme` | | Visual theme: `light`, `dark`, `auto` |
28
+ | Attribute | Required | Description |
29
+ | ---------------- | -------- | ------------------------------------- |
30
+ | `element-type` | ✓ | Type of strategic element |
31
+ | `contributor-id` | ✓ | Your contributor identifier |
32
+ | `content` | | Alternative to inner text |
33
+ | `theme` | | Visual theme: `light`, `dark`, `auto` |
34
34
 
35
35
  ## Element Types
36
36
 
37
37
  - `key_challenge` - Business challenges
38
- - `supporting_fact` - Evidence and data points
38
+ - `supporting_fact` - Evidence and data points
39
39
  - `guiding_policy` - Strategic approaches
40
40
  - `solution_alternative` - Potential solutions
41
41
  - And more...
@@ -45,7 +45,7 @@ import '@skhema/web-component';
45
45
  ```html
46
46
  <article>
47
47
  <p>The automotive industry is undergoing transformation...</p>
48
-
48
+
49
49
  <skhema-element element-type="key_challenge" contributor-id="analyst">
50
50
  Traditional automakers face retooling challenges while competing with Tesla.
51
51
  </skhema-element>
@@ -54,4 +54,4 @@ import '@skhema/web-component';
54
54
 
55
55
  ## License
56
56
 
57
- MIT
57
+ MIT
package/dist/cdn.d.ts CHANGED
@@ -1,4 +1,3 @@
1
1
  import { SkhemaElement } from './components/SkhemaElement.js';
2
-
3
2
  export { SkhemaElement };
4
3
  //# sourceMappingURL=cdn.d.ts.map
package/dist/cdn.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cdn.d.ts","sourceRoot":"","sources":["../src/cdn.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAY9D,OAAO,EAAE,aAAa,EAAE,CAAC"}
1
+ {"version":3,"file":"cdn.d.ts","sourceRoot":"","sources":["../src/cdn.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAA;AAiB7D,OAAO,EAAE,aAAa,EAAE,CAAA"}
@@ -1,5 +1,4 @@
1
- import { SkhemaElementAttributes, ContentData, SkhemaElementEventMap } from './types.js';
2
-
1
+ import { ContentData, SkhemaElementAttributes, SkhemaElementEventMap } from './types.js';
3
2
  export declare class SkhemaElement extends HTMLElement {
4
3
  private shadow;
5
4
  private contentData;
@@ -23,12 +22,11 @@ export declare class SkhemaElement extends HTMLElement {
23
22
  declare global {
24
23
  interface HTMLElementEventMap extends SkhemaElementEventMap {
25
24
  }
26
- namespace JSX {
27
- interface IntrinsicElements {
28
- 'skhema-element': Partial<SkhemaElementAttributes> & {
29
- [key: string]: any;
30
- };
31
- }
25
+ interface SkhemaElementJSX extends Partial<SkhemaElementAttributes> {
26
+ [key: string]: unknown;
27
+ }
28
+ interface JSXIntrinsicElements {
29
+ 'skhema-element': SkhemaElementJSX;
32
30
  }
33
31
  }
34
32
  //# sourceMappingURL=SkhemaElement.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SkhemaElement.d.ts","sourceRoot":"","sources":["../../src/components/SkhemaElement.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAkB,WAAW,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AA+T9G,qBAAa,aAAc,SAAQ,WAAW;IAC5C,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,WAAW,CAA4B;IAC/C,OAAO,CAAC,kBAAkB,CAAS;;IAOnC,MAAM,KAAK,kBAAkB,IAAI,CAAC,MAAM,uBAAuB,CAAC,EAAE,CAEjE;IAED,iBAAiB;IAYjB,wBAAwB,CAAC,KAAK,EAAE,MAAM,uBAAuB,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAM/G,OAAO,CAAC,MAAM;IA4Bd,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,aAAa;IAiErB,OAAO,CAAC,qBAAqB;IAQ7B,OAAO,CAAC,WAAW;IASnB,OAAO,CAAC,WAAW;IAuBnB,OAAO,CAAC,iBAAiB;YAoBX,SAAS;YAuBT,eAAe;IAgBtB,cAAc,IAAI,WAAW,GAAG,IAAI;IAIpC,OAAO,IAAI,IAAI;CAGvB;AAGD,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,mBAAoB,SAAQ,qBAAqB;KAAG;IAE9D,UAAU,GAAG,CAAC;QACZ,UAAU,iBAAiB;YACzB,gBAAgB,EAAE,OAAO,CAAC,uBAAuB,CAAC,GAAG;gBACnD,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;aACpB,CAAC;SACH;KACF;CACF"}
1
+ {"version":3,"file":"SkhemaElement.d.ts","sourceRoot":"","sources":["../../src/components/SkhemaElement.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EACV,WAAW,EAEX,uBAAuB,EACvB,qBAAqB,EACtB,MAAM,YAAY,CAAA;AA+TnB,qBAAa,aAAc,SAAQ,WAAW;IAC5C,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,WAAW,CAA2B;IAC9C,OAAO,CAAC,kBAAkB,CAAQ;;IAOlC,MAAM,KAAK,kBAAkB,IAAI,CAAC,MAAM,uBAAuB,CAAC,EAAE,CASjE;IAED,iBAAiB;IAYjB,wBAAwB,CACtB,KAAK,EAAE,MAAM,uBAAuB,EACpC,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,QAAQ,EAAE,MAAM,GAAG,IAAI;IAOzB,OAAO,CAAC,MAAM;IAwCd,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,aAAa;IAuErB,OAAO,CAAC,qBAAqB;IAQ7B,OAAO,CAAC,WAAW;IASnB,OAAO,CAAC,WAAW;IAyBnB,OAAO,CAAC,iBAAiB;YA0BX,SAAS;YAyBT,eAAe;IAkBtB,cAAc,IAAI,WAAW,GAAG,IAAI;IAIpC,OAAO,IAAI,IAAI;CAGvB;AAGD,OAAO,CAAC,MAAM,CAAC;IAEb,UAAU,mBAAoB,SAAQ,qBAAqB;KAAG;IAE9D,UAAU,gBAAiB,SAAQ,OAAO,CAAC,uBAAuB,CAAC;QACjE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KACvB;IAGD,UAAU,oBAAoB;QAC5B,gBAAgB,EAAE,gBAAgB,CAAA;KACnC;CACF"}
@@ -1,11 +1,10 @@
1
1
  import { ElementValue } from '@skhema/types';
2
-
3
2
  export interface SkhemaElementAttributes {
4
3
  'element-type': ElementValue;
5
4
  'contributor-id': string;
6
- 'content'?: string;
5
+ content?: string;
7
6
  'source-url'?: string;
8
- 'theme'?: 'light' | 'dark' | 'auto';
7
+ theme?: 'light' | 'dark' | 'auto';
9
8
  'track-analytics'?: 'true' | 'false';
10
9
  }
11
10
  export interface EmbedAnalytics {
@@ -38,7 +37,7 @@ export interface SkhemaElementEventMap {
38
37
  'skhema:click': CustomEvent<ContentData>;
39
38
  'skhema:error': CustomEvent<{
40
39
  error: string;
41
- details?: any;
40
+ details?: unknown;
42
41
  }>;
43
42
  }
44
43
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/components/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAElD,MAAM,WAAW,uBAAuB;IACtC,cAAc,EAAE,YAAY,CAAC;IAC7B,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;IACpC,iBAAiB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACtC;AAED,MAAM,WAAW,cAAc;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,YAAY,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,YAAY,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,qBAAqB;IACpC,aAAa,EAAE,WAAW,CAAC,cAAc,CAAC,CAAC;IAC3C,cAAc,EAAE,WAAW,CAAC,WAAW,CAAC,CAAC;IACzC,cAAc,EAAE,WAAW,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC,CAAC;CAC/D"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/components/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAEjD,MAAM,WAAW,uBAAuB;IACtC,cAAc,EAAE,YAAY,CAAA;IAC5B,gBAAgB,EAAE,MAAM,CAAA;IACxB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAA;IACjC,iBAAiB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CACrC;AAED,MAAM,WAAW,cAAc;IAC7B,aAAa,EAAE,MAAM,CAAA;IACrB,WAAW,EAAE,YAAY,CAAA;IACzB,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,cAAc,EAAE,MAAM,CAAA;IACtB,YAAY,EAAE,YAAY,CAAA;IAC1B,OAAO,EAAE,MAAM,CAAA;IACf,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,qBAAqB;IACpC,aAAa,EAAE,WAAW,CAAC,cAAc,CAAC,CAAA;IAC1C,cAAc,EAAE,WAAW,CAAC,WAAW,CAAC,CAAA;IACxC,cAAc,EAAE,WAAW,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAA;CAClE"}
package/dist/index.cjs CHANGED
@@ -1,45 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
3
3
  const types = require("@skhema/types");
4
- function isValidElementType(elementType) {
5
- const validTypes = Object.values(types.ELEMENT_TYPES).map((type) => type.value);
6
- return validTypes.includes(elementType);
7
- }
8
- function validateAttributes(element) {
9
- const errors = [];
10
- const elementType = element.getAttribute("element-type");
11
- const contributorId = element.getAttribute("contributor-id");
12
- if (!elementType) {
13
- errors.push("Missing required attribute: element-type");
14
- } else if (!isValidElementType(elementType)) {
15
- const validTypes = Object.values(types.ELEMENT_TYPES).map((t) => t.value).join(", ");
16
- errors.push(`Invalid element-type "${elementType}". Valid types: ${validTypes}`);
17
- }
18
- if (!contributorId) {
19
- errors.push("Missing required attribute: contributor-id");
20
- } else if (contributorId.trim().length === 0) {
21
- errors.push("contributor-id cannot be empty");
22
- }
23
- return {
24
- isValid: errors.length === 0,
25
- errors,
26
- elementType: isValidElementType(elementType || "") ? elementType : void 0,
27
- contributorId: contributorId || void 0
28
- };
29
- }
30
- function getElementTypeLabel(elementType) {
31
- const type = Object.values(types.ELEMENT_TYPES).find((t) => t.value === elementType);
32
- return (type == null ? void 0 : type.label) || elementType;
33
- }
34
- function getElementTypeAcronym(elementType) {
35
- const type = Object.values(types.ELEMENT_TYPES).find((t) => t.value === elementType);
36
- return (type == null ? void 0 : type.acronym) || elementType.substring(0, 2).toUpperCase();
37
- }
38
4
  function toUrlSafeBase64(str) {
39
- const base64 = btoa(encodeURIComponent(str).replace(
40
- /%([0-9A-F]{2})/g,
41
- (_, p1) => String.fromCharCode(parseInt(p1, 16))
42
- ));
5
+ const base64 = btoa(
6
+ encodeURIComponent(str).replace(
7
+ /%([0-9A-F]{2})/g,
8
+ (_, p1) => String.fromCharCode(parseInt(p1, 16))
9
+ )
10
+ );
43
11
  return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
44
12
  }
45
13
  async function trackEmbedLoad(analytics) {
@@ -55,7 +23,10 @@ async function trackEmbedLoad(analytics) {
55
23
  user_agent: analytics.userAgent || ""
56
24
  });
57
25
  if (navigator.sendBeacon) {
58
- navigator.sendBeacon("https://api.skhema.com/api:XGdoUqHx/component/embed", data);
26
+ navigator.sendBeacon(
27
+ "https://api.skhema.com/api:XGdoUqHx/component/embed",
28
+ data
29
+ );
59
30
  } else {
60
31
  fetch("https://api.skhema.com/api:XGdoUqHx/component/embed", {
61
32
  method: "POST",
@@ -104,26 +75,131 @@ function generateContentHash(content) {
104
75
  }
105
76
  return Math.abs(hash).toString(36).substring(0, 12);
106
77
  }
78
+ function sanitizeContent(content) {
79
+ const htmlEncode = (str) => {
80
+ const div = document.createElement("div");
81
+ div.textContent = str;
82
+ return div.innerHTML;
83
+ };
84
+ let sanitized = stripUrls(content);
85
+ sanitized = htmlEncode(sanitized);
86
+ sanitized = sanitized.replace(/\n/g, "<br>");
87
+ sanitized = applyTextWrapping(sanitized);
88
+ return sanitized;
89
+ }
90
+ function stripUrls(text) {
91
+ const patterns = [
92
+ // Standard URLs with protocols
93
+ /https?:\/\/[^\s<>"{}|\\^`[\]]+/gi,
94
+ // FTP URLs
95
+ /ftp:\/\/[^\s<>"{}|\\^`[\]]+/gi,
96
+ // URLs without protocol but with www
97
+ /www\.[^\s<>"{}|\\^`[\]]+/gi,
98
+ // Email-like patterns
99
+ /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/gi,
100
+ // Common domain patterns (anything.com, anything.org, etc.)
101
+ /(?:^|\s)([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(?:\/[^\s]*)?/gi
102
+ ];
103
+ let stripped = text;
104
+ patterns.forEach((pattern) => {
105
+ stripped = stripped.replace(pattern, "");
106
+ });
107
+ stripped = stripped.replace(/\s+/g, " ").trim();
108
+ return stripped;
109
+ }
110
+ function applyTextWrapping(text) {
111
+ const words = text.split(/(\s+)/);
112
+ return words.map((word) => {
113
+ if (/^\s+$/.test(word) || word.includes("<")) {
114
+ return word;
115
+ }
116
+ if (word.length > 30) {
117
+ return word.replace(/(.{10})/g, "$1​");
118
+ }
119
+ return word;
120
+ }).join("");
121
+ }
122
+ function validateContentSecurity(content) {
123
+ const issues = [];
124
+ if (/<script[\s>]/i.test(content)) {
125
+ issues.push("Script tags detected");
126
+ }
127
+ if (/on\w+\s*=/i.test(content)) {
128
+ issues.push("Event handlers detected");
129
+ }
130
+ if (/javascript:/i.test(content)) {
131
+ issues.push("JavaScript protocol detected");
132
+ }
133
+ if (/data:[^,]*script/i.test(content)) {
134
+ issues.push("Data URL with script detected");
135
+ }
136
+ if (/<iframe[\s>]/i.test(content)) {
137
+ issues.push("Iframe tags detected");
138
+ }
139
+ if (/https?:\/\//i.test(content) || /www\./i.test(content)) {
140
+ issues.push("URLs detected in content");
141
+ }
142
+ return {
143
+ isSecure: issues.length === 0,
144
+ issues
145
+ };
146
+ }
147
+ function isValidElementType(elementType) {
148
+ const validTypes = Object.values(types.ELEMENT_TYPES).map((type) => type.value);
149
+ return validTypes.includes(elementType);
150
+ }
151
+ function validateAttributes(element) {
152
+ const errors = [];
153
+ const elementType = element.getAttribute("element-type");
154
+ const contributorId = element.getAttribute("contributor-id");
155
+ if (!elementType) {
156
+ errors.push("Missing required attribute: element-type");
157
+ } else if (!isValidElementType(elementType)) {
158
+ const validTypes = Object.values(types.ELEMENT_TYPES).map((t) => t.value).join(", ");
159
+ errors.push(
160
+ `Invalid element-type "${elementType}". Valid types: ${validTypes}`
161
+ );
162
+ }
163
+ if (!contributorId) {
164
+ errors.push("Missing required attribute: contributor-id");
165
+ } else if (contributorId.trim().length === 0) {
166
+ errors.push("contributor-id cannot be empty");
167
+ }
168
+ return {
169
+ isValid: errors.length === 0,
170
+ errors,
171
+ elementType: isValidElementType(elementType || "") ? elementType : void 0,
172
+ contributorId: contributorId || void 0
173
+ };
174
+ }
175
+ function getElementTypeLabel(elementType) {
176
+ const type = Object.values(types.ELEMENT_TYPES).find((t) => t.value === elementType);
177
+ return type?.label || elementType;
178
+ }
179
+ function getElementTypeAcronym(elementType) {
180
+ const type = Object.values(types.ELEMENT_TYPES).find((t) => t.value === elementType);
181
+ return type?.acronym || elementType.substring(0, 2).toUpperCase();
182
+ }
107
183
  function generateStructuredData(content, elementType, contributorId, sourceUrl) {
108
184
  return {
109
185
  "@context": "https://schema.org",
110
186
  "@type": "AnalysisContent",
111
- "text": content,
112
- "analysisType": elementType,
113
- "category": getElementTypeLabel(elementType),
114
- "contributor": contributorId,
115
- "url": generateRedirectUrl(content, elementType, contributorId),
116
- "provider": {
187
+ text: content,
188
+ analysisType: elementType,
189
+ category: getElementTypeLabel(elementType),
190
+ contributor: contributorId,
191
+ url: generateRedirectUrl(content, elementType, contributorId),
192
+ provider: {
117
193
  "@type": "Organization",
118
- "name": "Skhema",
119
- "url": "https://skhema.com"
194
+ name: "Skhema",
195
+ url: "https://skhema.com"
120
196
  },
121
- "isPartOf": {
197
+ isPartOf: {
122
198
  "@type": "WebPage",
123
- "url": sourceUrl
199
+ url: sourceUrl
124
200
  },
125
- "dateCreated": (/* @__PURE__ */ new Date()).toISOString(),
126
- "platform": "Skhema"
201
+ dateCreated: (/* @__PURE__ */ new Date()).toISOString(),
202
+ platform: "Skhema"
127
203
  };
128
204
  }
129
205
  function generateRedirectUrl(content, elementType, contributorId, options = {}) {
@@ -156,7 +232,7 @@ function createMetaTags(content, elementType, contributorId) {
156
232
  function createAriaAttributes(elementType) {
157
233
  const label = getElementTypeLabel(elementType);
158
234
  return {
159
- "role": "article",
235
+ role: "article",
160
236
  "aria-label": `${label} - Strategic insight`,
161
237
  "aria-describedby": "skhema-description"
162
238
  };
@@ -298,6 +374,10 @@ const styles = `
298
374
  margin: 0;
299
375
  font-style: italic;
300
376
  position: relative;
377
+ word-wrap: break-word;
378
+ overflow-wrap: break-word;
379
+ hyphens: auto;
380
+ max-width: 100%;
301
381
  }
302
382
 
303
383
  .skhema-content-text::before {
@@ -480,7 +560,14 @@ class SkhemaElement extends HTMLElement {
480
560
  this.shadow = this.attachShadow({ mode: "closed" });
481
561
  }
482
562
  static get observedAttributes() {
483
- return ["element-type", "contributor-id", "content", "source-url", "theme", "track-analytics"];
563
+ return [
564
+ "element-type",
565
+ "contributor-id",
566
+ "content",
567
+ "source-url",
568
+ "theme",
569
+ "track-analytics"
570
+ ];
484
571
  }
485
572
  connectedCallback() {
486
573
  if (this.componentConnected) return;
@@ -505,7 +592,17 @@ class SkhemaElement extends HTMLElement {
505
592
  }
506
593
  const content = this.getContent();
507
594
  if (!content.trim()) {
508
- this.renderError("Component requires content", ["Add content between the opening and closing tags, or use the content attribute"]);
595
+ this.renderError("Component requires content", [
596
+ "Add content between the opening and closing tags, or use the content attribute"
597
+ ]);
598
+ return;
599
+ }
600
+ const securityValidation = validateContentSecurity(content);
601
+ if (!securityValidation.isSecure) {
602
+ this.renderError(
603
+ "Content security validation failed",
604
+ securityValidation.issues
605
+ );
509
606
  return;
510
607
  }
511
608
  this.contentData = {
@@ -527,7 +624,11 @@ class SkhemaElement extends HTMLElement {
527
624
  if (!this.contentData) return;
528
625
  const { element_type, contributor_id, content } = this.contentData;
529
626
  const label = getElementTypeLabel(element_type);
530
- const redirectUrl = generateRedirectUrl(content, element_type, contributor_id);
627
+ const redirectUrl = generateRedirectUrl(
628
+ content,
629
+ element_type,
630
+ contributor_id
631
+ );
531
632
  const theme = this.getAttribute("theme") || "auto";
532
633
  const displayName = this.formatContributorName(contributor_id);
533
634
  const initials = this.getInitials(displayName);
@@ -555,7 +656,7 @@ class SkhemaElement extends HTMLElement {
555
656
  </div>
556
657
 
557
658
  <div class="skhema-content">
558
- <div class="skhema-content-text">${content}</div>
659
+ <div class="skhema-content-text">${sanitizeContent(content)}</div>
559
660
  </div>
560
661
 
561
662
  <div class="skhema-footer">
@@ -572,7 +673,9 @@ class SkhemaElement extends HTMLElement {
572
673
  </div>
573
674
  </div>
574
675
  `;
575
- const saveBtn = this.shadow.querySelector(".skhema-save-btn");
676
+ const saveBtn = this.shadow.querySelector(
677
+ ".skhema-save-btn"
678
+ );
576
679
  if (saveBtn) {
577
680
  saveBtn.addEventListener("click", (event) => {
578
681
  this.handleSaveClick(event);
@@ -599,15 +702,22 @@ class SkhemaElement extends HTMLElement {
599
702
  </div>
600
703
  </div>
601
704
  `;
602
- this.dispatchEvent(new CustomEvent("skhema:error", {
603
- detail: { error: title, details: errors },
604
- bubbles: true
605
- }));
705
+ this.dispatchEvent(
706
+ new CustomEvent("skhema:error", {
707
+ detail: { error: title, details: errors },
708
+ bubbles: true
709
+ })
710
+ );
606
711
  }
607
712
  addStructuredData() {
608
713
  if (!this.contentData) return;
609
714
  const { content, element_type, contributor_id, source_url } = this.contentData;
610
- const structuredData = generateStructuredData(content, element_type, contributor_id, source_url);
715
+ const structuredData = generateStructuredData(
716
+ content,
717
+ element_type,
718
+ contributor_id,
719
+ source_url
720
+ );
611
721
  const script = document.createElement("script");
612
722
  script.type = "application/ld+json";
613
723
  script.textContent = JSON.stringify(structuredData);
@@ -631,20 +741,24 @@ class SkhemaElement extends HTMLElement {
631
741
  userAgent: navigator.userAgent
632
742
  };
633
743
  await trackEmbedLoad(analytics);
634
- this.dispatchEvent(new CustomEvent("skhema:load", {
635
- detail: analytics,
636
- bubbles: true
637
- }));
744
+ this.dispatchEvent(
745
+ new CustomEvent("skhema:load", {
746
+ detail: analytics,
747
+ bubbles: true
748
+ })
749
+ );
638
750
  }
639
751
  async handleSaveClick(_event) {
640
752
  if (!this.contentData) return;
641
753
  if (shouldTrackAnalytics(this)) {
642
754
  await trackClick(this.contentData);
643
755
  }
644
- this.dispatchEvent(new CustomEvent("skhema:click", {
645
- detail: this.contentData,
646
- bubbles: true
647
- }));
756
+ this.dispatchEvent(
757
+ new CustomEvent("skhema:click", {
758
+ detail: this.contentData,
759
+ bubbles: true
760
+ })
761
+ );
648
762
  }
649
763
  // Public API methods
650
764
  getContentData() {
@@ -656,11 +770,17 @@ class SkhemaElement extends HTMLElement {
656
770
  }
657
771
  function registerSkhemaElement() {
658
772
  if (typeof window !== "undefined" && !customElements.get("skhema-element")) {
659
- customElements.define("skhema-element", SkhemaElement);
773
+ customElements.define(
774
+ "skhema-element",
775
+ SkhemaElement
776
+ );
660
777
  }
661
778
  }
662
779
  if (typeof window !== "undefined" && !customElements.get("skhema-element")) {
663
- customElements.define("skhema-element", SkhemaElement);
780
+ customElements.define(
781
+ "skhema-element",
782
+ SkhemaElement
783
+ );
664
784
  }
665
785
  exports.SkhemaElement = SkhemaElement;
666
786
  exports.default = SkhemaElement;