sommark 4.5.3 → 5.0.0

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/formatter/mark.js CHANGED
@@ -188,8 +188,42 @@ class MarkdownBuilder {
188
188
  return result;
189
189
  }
190
190
 
191
+ /**
192
+ * Escapes Markdown trigger characters for MDX output using HTML entities instead of
193
+ * backslashes. Backslash escapes render literally inside JSX text children, so entities
194
+ * are the only reliable way to neutralise Markdown symbols in MDX.
195
+ */
196
+ mdxEscaper(text) {
197
+ if (!text) return "";
198
+
199
+ // 1. HTML tags → entities (before & escaping to avoid double-encoding)
200
+ let result = text.replace(/<([a-zA-Z\/][^>]*?)>/g, "&lt;$1&gt;");
201
+
202
+ // 2. Ampersands and quotes
203
+ result = result
204
+ .replace(/&(?!lt;|gt;)/g, "&amp;")
205
+ .replace(/"/g, "&quot;")
206
+ .replace(/'/g, "&#39;");
191
207
 
208
+ // 3. Unordered list triggers: -, *, + at start of line followed by space
209
+ result = result.replace(/^([-*+])(\s+)/gm, (_, c, sp) => `&#${c.codePointAt(0)};${sp}`);
192
210
 
211
+ // 4. Ordered list triggers: 1. at start of line — encode the dot
212
+ result = result.replace(/^(\d+)\.(\s+)/gm, (_, n, sp) => `${n}&#46;${sp}`);
213
+
214
+ // 5. Emphasis triggers: *text*, **text**, _text_, ~~text~~
215
+ result = result.replace(/(\*+|_+|~~)(\S[\s\S]*?\S)\1/g, (_, prefix, content) => {
216
+ const enc = prefix.split("").map(c => `&#${c.codePointAt(0)};`).join("");
217
+ return enc + content + enc;
218
+ });
219
+
220
+ // 6. Horizontal rule triggers: ---, ***, ___ on their own line
221
+ result = result.replace(/^([*_-]{3,})\s*$/gm, (m) =>
222
+ m.replace(/[*_-]/g, c => `&#${c.codePointAt(0)};`)
223
+ );
224
+
225
+ return result;
226
+ }
193
227
 
194
228
  /**
195
229
  * Formats data as a Markdown table.
package/formatter/tag.js CHANGED
@@ -69,10 +69,8 @@ class TagBuilder {
69
69
  const id = this.tagName.toLowerCase();
70
70
  const isCodeStyleOrScript = ["style", "script"].includes(id);
71
71
  let inline_style = "";
72
- const useClassFallback = options.fallbackTarget === "class";
73
- const classSet = new Set();
74
72
 
75
- // 1. Initial CSS Variable/Style processing
73
+ // 1. Initial style processing
76
74
  if (!isCodeStyleOrScript && args.style) {
77
75
  if (typeof args.style === "object") {
78
76
  inline_style = Object.entries(args.style)
@@ -85,20 +83,11 @@ class TagBuilder {
85
83
  }
86
84
  }
87
85
 
88
- // 2. Pre-collect native classes if using class fallback
89
- if (useClassFallback) {
90
- if (args.class) {
91
- String(args.class).split(/\s+/).filter(Boolean).forEach(c => classSet.add(c));
92
- }
93
- }
94
-
95
- // 3. Attribute Dispatching
86
+ // 2. Attribute dispatching
96
87
  const keys = Object.keys(args).filter(arg => isNaN(parseInt(arg)));
97
88
  keys.forEach(key => {
98
- if (!isNaN(parseInt(key))) return; // Skip numeric positional arguments
99
89
  if (key === "style") return;
100
90
  if (isCodeStyleOrScript && key === "scoped") return;
101
- if (useClassFallback && key === "class") return;
102
91
 
103
92
  const isDimensionAttributeSupported = ["img", "video", "svg", "canvas", "iframe", "object", "embed"].includes(id);
104
93
  const isWidthOrHeight = key === "width" || key === "height";
@@ -108,38 +97,23 @@ class TagBuilder {
108
97
  const isDataOrAria = kebabize(key).startsWith("data-") || kebabize(key).startsWith("aria-");
109
98
 
110
99
  const k = isEvent ? key.toLowerCase() : (isNative || isCustom) ? key : kebabize(key);
100
+ const val = typeof args[key] === "object" ? JSON.stringify(args[key]) : args[key];
111
101
 
112
102
  if (isCodeStyleOrScript || options.fallbackTarget === false) {
113
- // Specialized tags or fallback disabled: render standard attributes, no styling fallback
114
- const val = typeof args[key] === "object" ? JSON.stringify(args[key]) : args[key];
103
+ // Specialized tags or fallback disabled: render as standard attributes
115
104
  this.#attr.push(`${k}="${escapeHTML(String(val))}"`);
116
105
  } else {
117
- // Standard elements: process smart fallbacks
118
106
  if (isEvent || ((isNative || isCustom) && (!isWidthOrHeight || isDimensionAttributeSupported)) || isDataOrAria) {
119
- const val = typeof args[key] === "object" ? JSON.stringify(args[key]) : args[key];
120
107
  this.#attr.push(`${k}="${escapeHTML(String(val))}"`);
121
108
  } else {
122
- if (useClassFallback) {
123
- const val = args[key];
124
- if (val === true || val === "true") {
125
- classSet.add(k);
126
- } else if (val !== false && val !== "false" && val !== null && val !== undefined) {
127
- classSet.add(`${k}-${val}`);
128
- }
129
- } else {
130
- const val = typeof args[key] === "object" ? JSON.stringify(args[key]) : args[key];
131
- inline_style += `${k}:${val};`;
132
- }
109
+ // Unknown attribute: fall through to inline style
110
+ inline_style += `${k}:${val};`;
133
111
  }
134
112
  }
135
113
  });
136
114
 
137
- if (useClassFallback && classSet.size > 0) {
138
- this.#attr.push(`class="${escapeHTML([...classSet].join(" "))}"`);
139
- }
140
-
141
115
  if (inline_style) {
142
- // V4 DYNAMIC CSS: Automatically wrap CSS variables in var()
116
+ // Wrap CSS variables in var()
143
117
  const processedStyle = inline_style.replace(/(^|[^\w\-_$])(--[\w\-_$]+)(?![\w\-_$]|:)/g, "$1var($2)");
144
118
  this.#attr.push(`style="${escapeHTML(processedStyle)}"`);
145
119
  }
package/helpers/utils.js CHANGED
@@ -90,9 +90,9 @@ export function matchedValue(outputs, targetId) {
90
90
  * @param {any} [options.fallBack=null] - Value to return if resolution or validation fails.
91
91
  * @returns {any} - The resolved argument value or the fallback.
92
92
  */
93
- export function safeArg({ args, index, key, type = null, setType = null, fallBack = null }) {
94
- if (typeof args !== 'object' || args === null) {
95
- sommarkError([`{line}<$red:TypeError:$> <$yellow:args must be an object$>{line}`]);
93
+ export function safeArg({ props, index, key, type = null, setType = null, fallBack = null }) {
94
+ if (typeof props !== 'object' || props === null) {
95
+ sommarkError([`{line}<$red:TypeError:$> <$yellow:props must be an object$>{line}`]);
96
96
  }
97
97
 
98
98
  if (index === undefined && key === undefined) {
@@ -102,7 +102,6 @@ export function safeArg({ args, index, key, type = null, setType = null, fallBac
102
102
  const validate = value => {
103
103
  if (value === undefined) return false;
104
104
 
105
- // Handle explicit type check functions (e.g., isObject, isArray)
106
105
  if (typeof type === 'function') {
107
106
  return type(value);
108
107
  }
@@ -112,30 +111,30 @@ export function safeArg({ args, index, key, type = null, setType = null, fallBac
112
111
  return typeof evaluated === type;
113
112
  };
114
113
 
115
- if (index !== undefined && validate(args[index])) {
116
- return args[index];
114
+ if (index !== undefined && validate(props[index])) {
115
+ return props[index];
117
116
  }
118
117
 
119
- if (key !== undefined && validate(args[key])) {
120
- return args[key];
118
+ if (key !== undefined && validate(props[key])) {
119
+ return props[key];
121
120
  }
122
121
 
123
122
  return fallBack;
124
123
  }
125
124
 
126
125
  /**
127
- * Extracts and returns all positional arguments from the Object-based 'args' structure of V4.
128
- *
129
- * @param {Object} args - The AST node's argument object.
130
- * @returns {Array<any>} - An ordered array of positional argument values.
126
+ * Extracts positional props from a block node's props object.
127
+ *
128
+ * @param {Object} props - The block node's props object.
129
+ * @returns {Array<any>} - An ordered array of positional prop values.
131
130
  */
132
- export function getPositionalArgs(args) {
133
- if (!args) return [];
134
- const keys = Object.keys(args);
131
+ export function getPositionalArgs(props) {
132
+ if (!props) return [];
133
+ const keys = Object.keys(props);
135
134
  const result = keys
136
135
  .filter(k => !isNaN(parseInt(k)))
137
136
  .sort((a, b) => parseInt(a) - parseInt(b))
138
- .map(k => args[k]);
137
+ .map(k => props[k]);
139
138
 
140
139
  return result;
141
140
  }
package/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import SomMark, { setDefaultFs, setDefaultCwd, setDefaultFindAndLoadConfig } from "./index.shared.js";
1
+ import SomMark, { setDefaultFs, setDefaultCwd, setDefaultFindAndLoadConfig, setDefaultResolvePath } from "./index.shared.js";
2
2
  export * from "./index.shared.js";
3
3
 
4
4
  // Node-specific filesystem import
@@ -14,6 +14,14 @@ if (typeof process !== "undefined" && process.versions?.node) {
14
14
  setDefaultFs(nodeFs);
15
15
  if (typeof process !== "undefined" && process.cwd) setDefaultCwd(process.cwd());
16
16
 
17
+ // Resolve filenames to absolute paths for clear error messages
18
+ if (typeof process !== "undefined" && process.versions?.node) {
19
+ try {
20
+ const nodePath = await import("node:path");
21
+ setDefaultResolvePath(nodePath.resolve.bind(nodePath));
22
+ } catch (e) {}
23
+ }
24
+
17
25
  // Node-specific config-loader import
18
26
  let findAndLoadConfigFn = async () => ({});
19
27
  if (typeof process !== "undefined" && process.versions?.node) {
package/index.shared.js CHANGED
@@ -11,9 +11,12 @@ import MDX from "./mappers/languages/mdx.js";
11
11
  import Json from "./mappers/languages/json.js";
12
12
  import Jsonc from "./mappers/languages/jsonc.js";
13
13
  import XML from "./mappers/languages/xml.js";
14
+ import CSV from "./mappers/languages/csv.js";
15
+ import TOML from "./mappers/languages/toml.js";
16
+ import YAML from "./mappers/languages/yaml.js";
14
17
  import TEXT from "./mappers/languages/text.js";
15
18
  import { runtimeError } from "./core/errors.js";
16
- import FORMATS, { textFormat, htmlFormat, markdownFormat, mdxFormat, jsonFormat, jsoncFormat, xmlFormat } from "./core/formats.js";
19
+ import FORMATS, { textFormat, htmlFormat, markdownFormat, mdxFormat, jsonFormat, jsoncFormat, xmlFormat, csvFormat, tomlFormat, yamlFormat } from "./core/formats.js";
17
20
  import TOKEN_TYPES from "./core/tokenTypes.js";
18
21
  import * as labels from "./core/labels.js";
19
22
  import { resolveModules } from "./core/modules.js";
@@ -26,6 +29,7 @@ import { preprocessRuntimeLogic } from "./core/helpers/preprocessor.js";
26
29
  let defaultFs = null;
27
30
  let defaultCwd = "/";
28
31
  let defaultFindAndLoadConfig = async () => ({});
32
+ let defaultResolvePath = (p) => p; // identity in browser; overridden to path.resolve in Node.js
29
33
 
30
34
  const isURL = (s) => typeof s === "string" && /^https?:\/\//.test(s);
31
35
 
@@ -38,6 +42,12 @@ export function setDefaultFs(fs) {
38
42
  Evaluator.setDefaultFs(fs);
39
43
  }
40
44
 
45
+ export function setDefaultResolvePath(fn) {
46
+ defaultResolvePath = fn;
47
+ }
48
+
49
+ const resolveFilename = (f) => (f && f !== "anonymous") ? defaultResolvePath(f) : f;
50
+
41
51
  export function setDefaultFindAndLoadConfig(fn) {
42
52
  defaultFindAndLoadConfig = fn;
43
53
  }
@@ -64,18 +74,16 @@ class SomMark {
64
74
  * @param {string} [options.baseDir=null] - The base directory for resolving relative paths.
65
75
  */
66
76
  constructor(options = {}) {
67
- const { src, ast = null, format, mapperFile = null, filename = "anonymous", removeComments = true, placeholders = {}, customProps = [], fallbackTarget = "style", outputValidator = null, importAliases = {}, importStack = [], baseDir = null, moduleCache = null, showSpinner = true, security = {}, generateRuntimeOutput = false, hideRuntimeOutput = false, dualOutput = false, moduleIdentityToken = null } = options;
77
+ const { src, ast = null, format, mapperFile = null, filename = "anonymous", removeComments = true, placeholders = {}, customProps = [], fallbackTarget = "style", outputValidator = null, importAliases = {}, importStack = [], baseDir = null, moduleCache = null, showSpinner = true, security = {}, dualOutput = false, moduleIdentityToken = null } = options;
68
78
  this.rawSettings = options;
69
79
  this.src = src;
70
80
  this.ast = ast;
71
81
  this.targetFormat = format;
72
82
  this.mapperFile = mapperFile;
73
- this.filename = filename;
83
+ this.filename = resolveFilename(filename);
74
84
  this.removeComments = removeComments;
75
85
  this.placeholders = placeholders;
76
86
  this.customProps = customProps;
77
- this.generateRuntimeOutput = generateRuntimeOutput;
78
- this.hideRuntimeOutput = hideRuntimeOutput;
79
87
  this.dualOutput = dualOutput;
80
88
  this.cwd = options.baseDir || (options.files ? "/" : defaultCwd);
81
89
  this.fs = options.fs
@@ -115,7 +123,7 @@ class SomMark {
115
123
 
116
124
  this.Mapper = Mapper;
117
125
 
118
- const mapperFiles = { [htmlFormat]: HTML, [markdownFormat]: MARKDOWN, [mdxFormat]: MDX, [jsonFormat]: Json, [jsoncFormat]: Jsonc, [xmlFormat]: XML, [textFormat]: TEXT };
126
+ const mapperFiles = { [htmlFormat]: HTML, [markdownFormat]: MARKDOWN, [mdxFormat]: MDX, [jsonFormat]: Json, [jsoncFormat]: Jsonc, [xmlFormat]: XML, [csvFormat]: CSV, [tomlFormat]: TOML, [yamlFormat]: YAML, [textFormat]: TEXT };
119
127
 
120
128
  if (!this.mapperFile && this.targetFormat) {
121
129
  const DefaultMapper = mapperFiles[this.targetFormat];
@@ -290,8 +298,6 @@ class SomMark {
290
298
  mapperFile: this.mapperFile,
291
299
  security: this.security,
292
300
  settings: this.rawSettings,
293
- generateRuntimeOutput: this.generateRuntimeOutput,
294
- hideRuntimeOutput: this.hideRuntimeOutput,
295
301
  dualOutput: this.dualOutput,
296
302
  instance: this
297
303
  });
@@ -324,7 +330,7 @@ const lex = async (src, filename = "anonymous") => {
324
330
  if (typeof src !== "string") {
325
331
  runtimeError([`{line}<$red:Invalid Source Type:$> <$yellow:The 'src' argument must be a string, received ${typeof src}.$>{line}`]);
326
332
  }
327
- return lexer(src, filename);
333
+ return lexer(src, resolveFilename(filename));
328
334
  };
329
335
 
330
336
  /**
@@ -391,7 +397,7 @@ const lexSync = (src, filename = "anonymous") => {
391
397
  if (typeof src !== "string") {
392
398
  runtimeError([`{line}<$red:Invalid Source Type:$> <$yellow:The 'src' argument must be a string, received ${typeof src}.$>{line}`]);
393
399
  }
394
- return lexer(src, filename);
400
+ return lexer(src, resolveFilename(filename));
395
401
  };
396
402
 
397
403
  /**
@@ -408,8 +414,9 @@ const parseSync = (src, filename = "anonymous") => {
408
414
  if (typeof src !== "string") {
409
415
  runtimeError([`{line}<$red:Invalid Source Type:$> <$yellow:The 'src' argument must be a string, received ${typeof src}.$>{line}`]);
410
416
  }
411
- const tokens = lexer(src, filename);
412
- return parser(tokens, filename);
417
+ const resolved = resolveFilename(filename);
418
+ const tokens = lexer(src, resolved);
419
+ return parser(tokens, resolved);
413
420
  };
414
421
 
415
422
  async function findAndLoadConfig(targetPath) {
@@ -427,6 +434,9 @@ export {
427
434
  Json,
428
435
  Jsonc,
429
436
  XML,
437
+ CSV,
438
+ TOML,
439
+ YAML,
430
440
  Mapper,
431
441
  FORMATS,
432
442
  lex,
@@ -0,0 +1,62 @@
1
+ import Mapper from "../mapper.js";
2
+ import { registerSharedOutputs } from "../shared/index.js";
3
+
4
+ const csvEscape = (value) => {
5
+ const str = String(value ?? "").trim();
6
+ if (str.includes(",") || str.includes('"') || str.includes("\n") || str.includes("\r")) {
7
+ return `"${str.replace(/"/g, '""')}"`;
8
+ }
9
+ return str;
10
+ };
11
+
12
+ const rowFromProps = (props) =>
13
+ Object.keys(props)
14
+ .filter(k => !isNaN(parseInt(k)))
15
+ .sort((a, b) => parseInt(a) - parseInt(b))
16
+ .map(k => csvEscape(props[k]))
17
+ .join(",");
18
+
19
+ const renderRow = async ({ props, ast, isSelfClosing, renderChild }) => {
20
+ if (isSelfClosing) return rowFromProps(props) + "\n";
21
+ const cells = [];
22
+ for (const child of ast.body.filter(c => c.type === "Block")) {
23
+ const out = await renderChild(child);
24
+ if (out != null && out !== "") cells.push(out);
25
+ }
26
+ return cells.join(",") + "\n";
27
+ };
28
+
29
+ const CSV = Mapper.define({
30
+ comment(text) {
31
+ return `# ${text}`;
32
+ },
33
+ text(text) {
34
+ return text.trim() === "" ? "" : text;
35
+ }
36
+ });
37
+
38
+ /**
39
+ * [header] — header row
40
+ * Self-closing: [header = "name", "age", "city" !]
41
+ * Body form: [header][col]name[end][col]age[end][end]
42
+ */
43
+ CSV.register(["header", "thead"], renderRow, { handleAst: true });
44
+
45
+ /**
46
+ * [row] / [tr] — data row
47
+ * Self-closing: [row = "Alice", "30", "New York" !]
48
+ * Body form: [row][col]Alice[end][col]30[end][end]
49
+ */
50
+ CSV.register(["row", "tr"], renderRow, { handleAst: true });
51
+
52
+ /**
53
+ * [col] / [cell] / [td] — single cell, used inside body-form rows
54
+ * [col]New York, NY[end]
55
+ */
56
+ CSV.register(["col", "cell", "td"], ({ textContent }) => {
57
+ return csvEscape(textContent);
58
+ }, { handleAst: true, trimAndWrapBlocks: false });
59
+
60
+ registerSharedOutputs(CSV);
61
+
62
+ export default CSV;
@@ -2,28 +2,27 @@ import Mapper from "../mapper.js";
2
2
  import { VOID_ELEMENTS } from "../../constants/void_elements.js";
3
3
  import { SVG_ELEMENTS } from "../../constants/svg_elements.js";
4
4
  import { registerSharedOutputs } from "../shared/index.js";
5
- import kebabize from "../../helpers/kebabize.js";
6
5
 
7
6
  /**
8
7
  * Helper to format an HTML tag with attributes and content.
9
8
  *
10
9
  * @param {string} id - The name of the HTML tag.
11
- * @param {Object} args - The attributes for the tag.
10
+ * @param {Object} props - The attributes for the tag.
12
11
  * @param {string} content - The text or tags inside this tag.
13
12
  * @returns {string} - The finished HTML string.
14
13
  */
15
- const renderHtmlTag = function (id, args, content, isSelfClosing) {
14
+ const renderHtmlTag = function (id, props, content, isSelfClosing) {
16
15
  const element = this.tag(id);
17
16
  const idLower = id.toLowerCase();
18
17
 
19
18
  if (SVG_ELEMENTS.has(idLower)) {
20
- element.attributes(args);
19
+ element.attributes(props);
21
20
  } else {
22
- element.smartAttributes(args, this.customProps, this.options);
21
+ element.smartAttributes(props, this.customProps, this.options);
23
22
  }
24
23
 
25
24
  let finalContent = content;
26
- if (idLower === "script" && args.scoped === true) {
25
+ if (idLower === "script" && props.scoped === true) {
27
26
  finalContent = `(function(){\n${content}\n})();`;
28
27
  }
29
28
 
@@ -71,27 +70,6 @@ const HTML = Mapper.define({
71
70
  return this.escapeHTML(text);
72
71
  },
73
72
 
74
- /**
75
- * Formats text inside inline tags (like bold or links).
76
- */
77
- inlineText(text, options) {
78
- if (options?.escape !== false) {
79
- return this.escapeHTML(text);
80
- }
81
- return text;
82
- },
83
-
84
- /**
85
- * Formats the content inside AtBlocks.
86
- */
87
- atBlockBody(text, options) {
88
- let out = String(text);
89
- if (options?.escape !== false) {
90
- out = this.escapeHTML(out);
91
- }
92
- return out;
93
- },
94
-
95
73
  /**
96
74
  * Provides high-fidelity fallback for unknown ids by rendering them as HTML elements.
97
75
  * @param {Object} node - The unknown AST node.
@@ -103,11 +81,10 @@ const HTML = Mapper.define({
103
81
  const isCodeStyleOrScript = ["code", "style", "script"].includes(idLower);
104
82
 
105
83
  return {
106
- render: function ({ args, content, isSelfClosing }) { return renderHtmlTag.call(this, node.id, args, content, isSelfClosing); },
84
+ render: function ({ props, content, isSelfClosing }) { return renderHtmlTag.call(this, node.id, props, content, isSelfClosing); },
107
85
  options: {
108
- type: isCodeStyleOrScript ? ["Block", "AtBlock"] : ["Block", "Inline"],
109
86
  escape: !isCodeStyleOrScript,
110
- rules: { is_self_closing: isVoid }
87
+ rules: { is_empty_body: isVoid }
111
88
  }
112
89
  };
113
90
  },
@@ -120,7 +97,7 @@ const HTML = Mapper.define({
120
97
  // DOCTYPE tag
121
98
  HTML.register(["DOCTYPE", "doctype"], () => {
122
99
  return "<!DOCTYPE html>";
123
- }, { type: "Block", rules: { is_self_closing: true } });
100
+ }, { rules: { is_empty_body: true } });
124
101
 
125
102
  // head tag
126
103
  HTML.register("head", function ({ content }) {
@@ -129,52 +106,21 @@ HTML.register("head", function ({ content }) {
129
106
  varsStyle = `<style>:root { ${this.cssVariables} }</style>\n`;
130
107
  }
131
108
  return this.tag("head").body(`${varsStyle}${content}`);
132
- }, { type: "Block", escape: false });
109
+ }, { escape: false });
133
110
 
134
111
  // Root tag for Metadata and CSS Variables (Collector)
135
112
  HTML.register(
136
113
  ["Root", "root"],
137
- function ({ args }) {
114
+ function ({ props }) {
138
115
  this.cssVariables = this.cssVariables || "";
139
- Object.keys(args).forEach(key => {
116
+ Object.keys(props).forEach(key => {
140
117
  if (key.startsWith("--")) {
141
- this.cssVariables += `${key}:${args[key]};`;
118
+ this.cssVariables += `${key}:${props[key]};`;
142
119
  }
143
120
  });
144
121
  return "";
145
122
  },
146
- {
147
- type: "Block"
148
- }
149
123
  );
150
- // Inline CSS tag (Moved from shared)
151
- HTML.register("css", ({ args, content }) => {
152
- // Compile style from named arguments (keys that are not numeric digits)
153
- const namedStyle = Object.keys(args)
154
- .filter(k => isNaN(parseInt(k)))
155
- .map(k => `${kebabize(k)}:${args[k]}`)
156
- .join(";");
157
-
158
- // Fetch positional style string (index 0) or "style" key if present
159
- let positionalStyle = HTML.safeArg({ args, index: 0, key: "style", fallBack: "" });
160
-
161
- // Filter out positional styles that are just duplicates of named arguments
162
- const hasDuplicateNamed = Object.keys(args)
163
- .filter(k => isNaN(parseInt(k)))
164
- .some(k => args[k] === positionalStyle);
165
-
166
- if (hasDuplicateNamed) {
167
- positionalStyle = "";
168
- }
169
-
170
- // Combine both together
171
- let style = [positionalStyle, namedStyle].filter(s => s.trim()).join(";");
172
-
173
- style = style.split(";").filter(s => s.trim()).map(s => s.trim().split(":").map(s => s.trim()).join(":")).join(";");
174
- return HTML.tag("span").attributes({ style }).body(content);
175
- }, {
176
- type: "Inline"
177
- });
178
124
  registerSharedOutputs(HTML);
179
125
 
180
126
  export default HTML;