sommark 2.1.1 → 2.2.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/core/lexer.js CHANGED
@@ -67,7 +67,9 @@ function concatText(input, index, scope_state, extraConditions = []) {
67
67
  text += char;
68
68
  }
69
69
  if (!text) {
70
- sommarkError([`{line}<$red:Concatenation failed:$> string value is <$yellow:'${text}'$>{line}`]);
70
+ sommarkError([
71
+ `{line}From: <$magenta:concatText function:$>{N}<$red:Concatenation failed:$> string value is <$yellow:'${text}'$>{line}`
72
+ ]);
71
73
  }
72
74
  return text;
73
75
  } else {
@@ -117,7 +119,9 @@ function concatEscape(input, index) {
117
119
  sommarkError(["{line}<$red:Next character is not found:$> <$yellow:There is no character after escape character!$>{line}"]);
118
120
  }
119
121
  if (!str) {
120
- sommarkError([`{line}<$red:Concatenation failed:$> string value is <$yellow:'${str}'$>{line}`]);
122
+ sommarkError([
123
+ `{line}From: <$magenta:concatEscape function:$>{N}<$red:Concatenation failed:$> string value is <$yellow:'${str}'$>{line}`
124
+ ]);
121
125
  }
122
126
  if (WHITESPACE_SET.has(str[1])) {
123
127
  const matchedCharacter = Array.from(WHITESPACE_SET).find(ch => ch === str[1]);
@@ -158,7 +162,9 @@ function concatChar(input, index, stop_at_char) {
158
162
  ]);
159
163
  }
160
164
  if (!str) {
161
- sommarkError([`{line}<$red:Concatenation failed:$> string value is <$yellow:'${str}'$>{line}`]);
165
+ sommarkError([
166
+ `{line}From: <$magenta:concatChar function:$>{N}<$red:Concatenation failed:$> string value is <$yellow:'${str}'$>{line}`
167
+ ]);
162
168
  }
163
169
  return str;
164
170
  } else {
@@ -185,10 +191,10 @@ function lexer(src) {
185
191
  tokens.push({ type, value, line, start, end, depth: depth_stack.length });
186
192
  }
187
193
 
188
- const updateMetadata = (text) => {
194
+ const updateMetadata = text => {
189
195
  const newlines = updateNewLine(text) || 0;
190
196
  if (newlines > 0) {
191
- const lines = text.split('\n');
197
+ const lines = text.split("\n");
192
198
  const lastLineLength = lines[lines.length - 1].length;
193
199
  start = end + 1;
194
200
  end = lastLineLength;
@@ -431,7 +437,13 @@ function lexer(src) {
431
437
  // ========================================================================== //
432
438
  // Token: Block Value //
433
439
  // ========================================================================== //
434
- else if ((previous_value === "=" || previous_value === BLOCKCOMMA || previous_value === BLOCKCOLON || previous_value === block_value) && !scope_state) {
440
+ else if (
441
+ (previous_value === "=" ||
442
+ previous_value === BLOCKCOMMA ||
443
+ previous_value === BLOCKCOLON ||
444
+ previous_value === block_value) &&
445
+ !scope_state
446
+ ) {
435
447
  temp_str = concatChar(src, i, ["]", "\\", ",", ":"]);
436
448
  i += temp_str.length - 1;
437
449
  const nextToken = peek(src, i, 1);
@@ -484,12 +496,7 @@ function lexer(src) {
484
496
  previous_value === inline_value) &&
485
497
  !scope_state
486
498
  ) {
487
- temp_str = concatChar(src, i, [
488
- ")",
489
- "\\",
490
- ",",
491
- previous_value === INLINECOLON ? ":" : null
492
- ]);
499
+ temp_str = concatChar(src, i, [")", "\\", ",", previous_value === INLINECOLON ? ":" : null]);
493
500
  i += temp_str.length - 1;
494
501
  // Update Metadata
495
502
  updateMetadata(temp_str);
@@ -588,7 +595,7 @@ function lexer(src) {
588
595
  return tokens;
589
596
  } else {
590
597
  lexerError([
591
- `{line}<$red:Invalid SomMark syntax:$> <$yellow:Expected source input to be a string, got$> <$blue: '${typeof src}'$>{line}`
598
+ `{line}<$red:Invalid SomMark syntax:$> ${src === "" ? "<$yellow: Got empty string '' $>" : `<$yellow:Expected source input to be a string, got$> <$blue: '${typeof src}'$>`}{line}`
592
599
  ]);
593
600
  }
594
601
  }
package/core/parser.js CHANGED
@@ -102,7 +102,6 @@ const updateData = (tokens, i) => {
102
102
  }
103
103
  };
104
104
 
105
-
106
105
  const errorMessage = (tokens, i, expectedValue, behindValue, frontText) => {
107
106
  const tokensUntilError = tokens.slice(0, i);
108
107
  const contextText = tokensUntilError.map(t => t.value).join("");
@@ -123,7 +122,6 @@ const errorMessage = (tokens, i, expectedValue, behindValue, frontText) => {
123
122
  // ========================================================================== //
124
123
  function parseKey(tokens, i) {
125
124
  let key = current_token(tokens, i).value.trim();
126
- validateName(key);
127
125
  // ========================================================================== //
128
126
  // consume Key //
129
127
  // ========================================================================== //
@@ -261,7 +259,12 @@ function parseBlock(tokens, i) {
261
259
  let [key, keyIndex] = parseKey(tokens, i);
262
260
  k = key;
263
261
  i = keyIndex;
262
+ const prev = current_token(tokens, i);
264
263
  i = parseColon(tokens, i, block_id);
264
+ if (current_token(tokens, i).type !== TOKEN_TYPES.VALUE && current_token(tokens, i).type !== TOKEN_TYPES.ESCAPE) {
265
+ parserError(errorMessage(tokens, i, block_value, ":"));
266
+ }
267
+ validateName(k);
265
268
  continue;
266
269
  } else if (
267
270
  current_token(tokens, i) &&
@@ -403,6 +406,7 @@ function parseInline(tokens, i) {
403
406
  parserError(errorMessage(tokens, i, inline_value, "("));
404
407
  }
405
408
  inlineNode.value = current_token(tokens, i).value;
409
+ inlineNode.depth = current_token(tokens, i).depth;
406
410
  // ========================================================================== //
407
411
  // consume Inline Value //
408
412
  // ========================================================================== //
@@ -467,11 +471,12 @@ function parseInline(tokens, i) {
467
471
  parserError(errorMessage(tokens, i, inline_value, ":"));
468
472
  }
469
473
  let v = "";
470
-
471
474
  const pushArg = () => {
472
475
  if (v !== "") {
473
476
  inlineNode.args.push(v);
474
- inlineNode.args[v] = v;
477
+ if (!Number.isInteger(Number(v))) {
478
+ inlineNode.args[v] = v;
479
+ }
475
480
  v = "";
476
481
  }
477
482
  };
@@ -519,11 +524,11 @@ function parseInline(tokens, i) {
519
524
  if (current_token(tokens, i) && current_token(tokens, i).type === TOKEN_TYPES.COMMA) {
520
525
  parserError(errorMessage(tokens, i, ",", "", "Found extra"));
521
526
  }
522
- if (!current_token(tokens, i) || (
523
- current_token(tokens, i) &&
524
- current_token(tokens, i).type !== TOKEN_TYPES.VALUE &&
525
- current_token(tokens, i).type !== TOKEN_TYPES.ESCAPE
526
- )
527
+ if (
528
+ !current_token(tokens, i) ||
529
+ (current_token(tokens, i) &&
530
+ current_token(tokens, i).type !== TOKEN_TYPES.VALUE &&
531
+ current_token(tokens, i).type !== TOKEN_TYPES.ESCAPE)
527
532
  ) {
528
533
  parserError(errorMessage(tokens, i, inline_value, ","));
529
534
  }
@@ -649,6 +654,10 @@ function parseAtBlock(tokens, i) {
649
654
  k = key;
650
655
  i = keyIndex;
651
656
  i = parseColon(tokens, i, at_id);
657
+ if (current_token(tokens, i).type !== TOKEN_TYPES.VALUE && current_token(tokens, i).type !== TOKEN_TYPES.ESCAPE) {
658
+ parserError(errorMessage(tokens, i, at_value, ":"));
659
+ }
660
+ validateName(k);
652
661
  continue;
653
662
  } else if (current_token(tokens, i) && current_token(tokens, i).type === TOKEN_TYPES.ESCAPE) {
654
663
  let [escape_character, escapeIndex] = parseEscape(tokens, i);
@@ -1,7 +1,9 @@
1
1
  import { BLOCK, TEXT, INLINE, ATBLOCK, COMMENT } from "./labels.js";
2
2
  import escapeHTML from "../helpers/escapeHTML.js";
3
3
  import { transpilerError } from "./errors.js";
4
- import { textFormat, htmlFormat, markdownFormat, mdxFormat } from "./formats.js";
4
+ import { textFormat, htmlFormat, markdownFormat, mdxFormat, jsonFormat } from "./formats.js";
5
+
6
+ const expose_for_fmts = [jsonFormat];
5
7
 
6
8
  // ========================================================================== //
7
9
  // Extracting target identifier //
@@ -26,7 +28,7 @@ function matchedValue(outputs, targetId) {
26
28
  return result;
27
29
  }
28
30
 
29
- function validateRules(target, args, content) {
31
+ function validateRules(target, args, content, type = null) {
30
32
  if (!target || !target.options || !target.options.rules) {
31
33
  return;
32
34
  }
@@ -96,6 +98,15 @@ function validateRules(target, args, content) {
96
98
  ]);
97
99
  }
98
100
  }
101
+ // Validate element type
102
+ if (id && rules.type && type) {
103
+ if (rules.type !== type) {
104
+ transpilerError([
105
+ "{line}<$red:Validation Error:$> ",
106
+ `<$yellow:Identifier$> <$blue:'${Array.isArray(id) ? id.join(" | ") : id}'$> <$yellow:is expected to be type$> <$green:'${rules.type}'$>{N}<$cyan:Received type: $> <$magenta:'${type}'$>{line}`
107
+ ]);
108
+ }
109
+ }
99
110
  }
100
111
  // ========================================================================== //
101
112
  // +++++++++++++++++++++++++++++ //
@@ -104,14 +115,16 @@ async function generateOutput(ast, i, format, mapper_file) {
104
115
  const node = Array.isArray(ast) ? ast[i] : ast;
105
116
  let result = "";
106
117
  let context = "";
107
- let target = mapper_file ? matchedValue(mapper_file.outputs, node.id) : "";
118
+ let target = mapper_file ? matchedValue(mapper_file.outputs, node.id) : null;
119
+ const block_formats = [htmlFormat, mdxFormat, jsonFormat];
108
120
  if (target) {
109
- validateRules(target, node.args, "");
110
- result +=
111
- format === htmlFormat || format === mdxFormat
112
- ? `${node.depth > 1 ? " ".repeat(node.depth) : ""}${target.render({ args: node.args, content: "\n<%smark>" })}${node.depth > 1 ? " ".repeat(node.depth) : ""}`
113
- : target.render({ args: node.args, content: "" });
114
- for (const body_node of node.body) {
121
+ validateRules(target, node.args, "", node.type);
122
+ result += block_formats.includes(format)
123
+ ? `${target.render({ args: node.args, content: "<%smark>", ast: expose_for_fmts.includes(format) ? ast[i] : null })}`
124
+ : target.render({ args: node.args, content: "" });
125
+ // Body nodes
126
+ for (let j = 0; j < node.body.length; j++) {
127
+ const body_node = node.body[j];
115
128
  switch (body_node.type) {
116
129
  // ========================================================================== //
117
130
  // Text //
@@ -119,8 +132,7 @@ async function generateOutput(ast, i, format, mapper_file) {
119
132
  case TEXT:
120
133
  validateRules(target, body_node.args, body_node.text);
121
134
  const shouldEscape = target && target.options && target.options.escape === false ? false : true;
122
- context +=
123
- (format === htmlFormat || format === mdxFormat) && shouldEscape ? escapeHTML(body_node.text) : body_node.text;
135
+ context += [htmlFormat, mdxFormat].includes(format) && shouldEscape ? escapeHTML(body_node.text) : body_node.text;
124
136
  break;
125
137
  // ========================================================================== //
126
138
  // Inline //
@@ -128,13 +140,11 @@ async function generateOutput(ast, i, format, mapper_file) {
128
140
  case INLINE:
129
141
  target = matchedValue(mapper_file.outputs, body_node.id);
130
142
  if (target) {
131
- validateRules(target, body_node.args, body_node.value);
132
- context +=
133
- (format === htmlFormat || format === mdxFormat ? "\n" : "") +
134
- target.render({
135
- args: body_node.args.length > 0 ? body_node.args : "",
136
- content: format === htmlFormat || format === mdxFormat ? escapeHTML(body_node.value) : body_node.value
137
- });
143
+ validateRules(target, body_node.args, body_node.value, body_node.type);
144
+ context += target.render({
145
+ args: body_node.args.length > 0 ? body_node.args : [],
146
+ content: format === htmlFormat || format === mdxFormat ? escapeHTML(body_node.value) : body_node.value
147
+ });
138
148
  }
139
149
  break;
140
150
  // ========================================================================== //
@@ -143,7 +153,7 @@ async function generateOutput(ast, i, format, mapper_file) {
143
153
  case ATBLOCK:
144
154
  target = matchedValue(mapper_file.outputs, body_node.id);
145
155
  if (target) {
146
- validateRules(target, body_node.args, body_node.content);
156
+ validateRules(target, body_node.args, body_node.content, body_node.type);
147
157
  // Escape logic: fallback to options.escape, default true
148
158
  const shouldEscape = target.options?.escape ?? true;
149
159
  if (shouldEscape) {
@@ -171,7 +181,8 @@ async function generateOutput(ast, i, format, mapper_file) {
171
181
  break;
172
182
  }
173
183
  }
174
- if (format === htmlFormat || format === mdxFormat) {
184
+
185
+ if (format === htmlFormat || format === mdxFormat || format === jsonFormat) {
175
186
  result = result.replace("<%smark>", context);
176
187
  } else {
177
188
  result += context;
@@ -245,6 +256,9 @@ async function transpiler({ ast, format, mapperFile, includeDocument = true }) {
245
256
  const document = `<!DOCTYPE html>\n<html>\n${finalHeader}\n<body>\n${output}\n</body>\n</html>\n`;
246
257
  return document;
247
258
  }
259
+ if (format === jsonFormat) {
260
+ output = JSON.parse(JSON.stringify(output));
261
+ }
248
262
  return output;
249
263
  }
250
264
 
package/formatter/mark.js CHANGED
@@ -18,7 +18,7 @@ class MarkdownBuilder {
18
18
  } else if (level < min) {
19
19
  level = min;
20
20
  }
21
- return `${"#".repeat(level)} ${text}\n`;
21
+ return `\n${"#".repeat(level)} ${text}\n`;
22
22
  }
23
23
  return text;
24
24
  }
@@ -29,7 +29,7 @@ class MarkdownBuilder {
29
29
  if (!text && !url) {
30
30
  return "";
31
31
  }
32
- return ` ${type === "image" ? "!" : ""}[${text}](${url + (title ? " " : "")}${title}) `;
32
+ return ` ${type === "image" ? "!" : ""}[${text}](${url + (title ? " " : "")}${title ? JSON.stringify(title) : ""}) `;
33
33
  }
34
34
  // ========================================================================== //
35
35
  // Bold //
@@ -91,10 +91,7 @@ class MarkdownBuilder {
91
91
  // Horizontal rule //
92
92
  // ========================================================================== //
93
93
  horizontal(format = "*") {
94
- if (!format) {
95
- return "\n***\n";
96
- }
97
- return format === "*" ? "\n***\n" : format === "_" ? "___" : format === "*" ? "***" : "";
94
+ return `\n${format.repeat(3)}\n`;
98
95
  }
99
96
  // ========================================================================== //
100
97
  // Escape //
package/grammar.ebnf CHANGED
@@ -12,7 +12,6 @@ EscapeChar = "\";
12
12
  (* 1. Identifiers can be only letters and numbers. *)
13
13
  (* 2. Semi-colon is only recognized in Atblock arguments. *)
14
14
  (* 3. Colon is a separator in Block and Atblock arguments; must be escaped if part of value. *)
15
- (* 4. Bodies and Values cannot be empty. *)
16
15
 
17
16
  (* Comments *)
18
17
  Comment = "#", { ? any character except "\n" ? }, "\n";
package/index.js CHANGED
@@ -5,10 +5,11 @@ import Mapper from "./mappers/mapper.js";
5
5
  import HTML from "./mappers/languages/html.js";
6
6
  import MARKDOWN from "./mappers/languages/markdown.js";
7
7
  import MDX from "./mappers/languages/mdx.js";
8
+ import Json from "./mappers/languages/json.js";
8
9
  import TagBuilder from "./formatter/tag.js";
9
10
  import MarkdownBuilder from "./formatter/mark.js";
10
11
  import { runtimeError } from "./core/errors.js";
11
- import FORMATS, { textFormat, htmlFormat, markdownFormat, mdxFormat } from "./core/formats.js";
12
+ import FORMATS, { textFormat, htmlFormat, markdownFormat, mdxFormat, jsonFormat } from "./core/formats.js";
12
13
  import TOKEN_TYPES from "./core/tokenTypes.js";
13
14
  import * as labels from "./core/labels.js";
14
15
  class SomMark {
@@ -19,7 +20,7 @@ class SomMark {
19
20
 
20
21
  this.Mapper = Mapper;
21
22
  this.includeDocument = includeDocument;
22
- const accepted_formats = [textFormat, htmlFormat, markdownFormat, mdxFormat];
23
+ const accepted_formats = [textFormat, htmlFormat, markdownFormat, mdxFormat, jsonFormat];
23
24
  if (!this.format) {
24
25
  runtimeError(["{line}<$red:Undefined Format$>: <$yellow:Format argument is not defined.$>{line}"]);
25
26
  }
@@ -29,7 +30,7 @@ class SomMark {
29
30
  `{N}<$yellow:Accepted formats are:$> [<$cyan: ${accepted_formats.join(", ")}$>]{line}`
30
31
  ]);
31
32
  }
32
- const mapperFiles = { [htmlFormat]: HTML, [markdownFormat]: MARKDOWN, [mdxFormat]: MDX };
33
+ const mapperFiles = { [htmlFormat]: HTML, [markdownFormat]: MARKDOWN, [mdxFormat]: MDX, [jsonFormat]: Json};
33
34
  if (!this.mapperFile && this.format) {
34
35
  this.mapperFile = mapperFiles[this.format];
35
36
  }
@@ -95,7 +96,8 @@ async function transpile(options = {}) {
95
96
  export {
96
97
  HTML,
97
98
  MARKDOWN,
98
- MDX,
99
+ MDX,
100
+ Json,
99
101
  Mapper,
100
102
  TagBuilder,
101
103
  MarkdownBuilder,
@@ -1,15 +1,56 @@
1
1
  import Mapper from "../mapper.js";
2
2
  const HTML = new Mapper();
3
- const { tag, code, list } = HTML;
3
+ const { tag, code, list, safeArg } = HTML;
4
+
5
+ HTML.register(
6
+ ["Html", "html"],
7
+ ({ args }) => {
8
+ HTML.pageProps.pageTitle = safeArg(args, undefined, "title", null, null, HTML.pageProps.pageTitle);
9
+ HTML.pageProps.charset = safeArg(args, undefined, "charset", null, null, HTML.pageProps.charset);
10
+ HTML.pageProps.tabIcon.src = safeArg(args, undefined, "iconSrc", null, null, HTML.pageProps.tabIcon.src);
11
+ HTML.pageProps.tabIcon.type = safeArg(args, undefined, "iconType", null, null, HTML.pageProps.tabIcon.type);
12
+ HTML.pageProps.httpEquiv["X-UA-Compatible"] = safeArg(
13
+ args,
14
+ undefined,
15
+ "httpEquiv",
16
+ null,
17
+ null,
18
+ HTML.pageProps.httpEquiv["X-UA-Compatible"]
19
+ );
20
+ HTML.pageProps.viewport = safeArg(args, undefined, "viewport", null, null, HTML.pageProps.viewport);
21
+ return "";
22
+ },
23
+ {
24
+ rules: {
25
+ type: "Block"
26
+ }
27
+ }
28
+ );
4
29
 
5
30
  // Block
6
- HTML.register("Block", ({ content }) => {
7
- return content;
8
- });
31
+ HTML.register(
32
+ ["Block", "block"],
33
+ ({ content }) => {
34
+ return content;
35
+ },
36
+ {
37
+ rules: {
38
+ type: "Block"
39
+ }
40
+ }
41
+ );
9
42
  // Section
10
- HTML.register("Section", ({ content }) => {
11
- return tag("section").body(content);
12
- });
43
+ HTML.register(
44
+ ["Section", "section"],
45
+ ({ content }) => {
46
+ return tag("section").body(content);
47
+ },
48
+ {
49
+ rules: {
50
+ type: "Block"
51
+ }
52
+ }
53
+ );
13
54
  // Headings
14
55
  ["h1", "h2", "h3", "h4", "h5", "h6"].forEach(heading => {
15
56
  HTML.register(heading, ({ content }) => {
@@ -17,44 +58,44 @@ HTML.register("Section", ({ content }) => {
17
58
  });
18
59
  });
19
60
  // Bold
20
- HTML.register(["bold", "b"], ({ content }) => {
61
+ HTML.register(["bold", "Bold", "b"], ({ content }) => {
21
62
  return tag("strong").body(content);
22
63
  });
23
64
  // Italic
24
65
  HTML.register(["italic", "i"], ({ content }) => {
25
66
  return tag("i").body(content);
26
67
  });
27
- // Italic
68
+ // Emphasis
28
69
  HTML.register(["emphasis", "e"], ({ content }) => {
29
70
  return tag("span").attributes({ style: "font-weight:bold; font-style: italic;" }).body(content);
30
71
  });
31
72
  // Colored Text
32
- HTML.register("color", ({ args, content }) => {
73
+ HTML.register(["color", "Color"], ({ args, content }) => {
74
+ const color = safeArg(args, 0, undefined, null, null, "none");
33
75
  return tag("span")
34
- .attributes({ style: `color:${args[0]}` })
76
+ .attributes({ style: `color:${color}` })
35
77
  .body(content);
36
78
  });
37
79
  // Link
38
- HTML.register("link", ({ args, content }) => {
39
- return tag("a")
40
- .attributes({ href: args[0].trim(), title: args[1] ? args[1].trim() : "" })
41
- .body(content);
80
+ HTML.register(["link", "Link"], ({ args, content }) => {
81
+ const url = safeArg(args, 0, "url", null, null, "");
82
+ const title = safeArg(args, 1, "title", null, null, "");
83
+ return tag("a").attributes({ href: url.trim(), title: title.trim(), target: "_blank" }).body(content);
42
84
  });
43
85
  // Image
44
86
  HTML.register(
45
87
  ["image", "Image"],
46
88
  ({ args }) => {
47
- const src = args && args["src"] ? args["src"] : "";
48
- const alt = args && args["alt"] ? args["alt"] : "";
49
- const width = args && args["width"] ? args["width"] : "",
50
- height = args && args["height"] ? args["height"] : "";
89
+ const src = safeArg(args, undefined, "src", null, null, "");
90
+ const alt = safeArg(args, undefined, "alt", null, null, "");
91
+ const width = safeArg(args, undefined, "width", null, null, "");
92
+ const height = safeArg(args, undefined, "height", null, null, "");
51
93
  return tag("img").attributes({ src, alt, width, height }).selfClose();
52
94
  },
53
95
  {
54
96
  rules: {
55
- is_Self_closing: true,
56
97
  args: {
57
- required: ["src"]
98
+ required: ["src"]
58
99
  }
59
100
  }
60
101
  }
@@ -65,7 +106,7 @@ HTML.register(
65
106
  ({ args, content }) => {
66
107
  return code(args, content);
67
108
  },
68
- { escape: false }
109
+ { escape: false, rules: { type: "AtBlock" } }
69
110
  );
70
111
  // List
71
112
  HTML.register(
@@ -81,7 +122,12 @@ HTML.register(
81
122
  ({ content, args }) => {
82
123
  return HTML.htmlTable(content.split(/\n/), args);
83
124
  },
84
- { escape: false }
125
+ {
126
+ escape: false,
127
+ rules: {
128
+ type: "AtBlock"
129
+ }
130
+ }
85
131
  );
86
132
  // Horizontal Rule
87
133
  HTML.register(
@@ -98,9 +144,8 @@ HTML.register(
98
144
  // Todo
99
145
  HTML.register("todo", ({ args, content }) => {
100
146
  const checked = HTML.todo(content);
101
- return tag("div").body(
102
- tag("input").attributes({ type: "checkbox", disabled: true, checked }).selfClose() + (args[0] ? args[0] : "")
103
- );
147
+ const task = safeArg(args, 0, undefined, null, null, "");
148
+ return tag("div").body(tag("input").attributes({ type: "checkbox", disabled: true, checked }).selfClose() + task);
104
149
  });
105
150
 
106
151
  export default HTML;