quasar-ui-danx 0.5.1 → 0.5.2
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/.claude/settings.local.json +8 -0
- package/dist/danx.es.js +11359 -10497
- package/dist/danx.es.js.map +1 -1
- package/dist/danx.umd.js +138 -131
- package/dist/danx.umd.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/Utility/Buttons/ActionButton.vue +15 -5
- package/src/components/Utility/Code/CodeViewer.vue +10 -2
- package/src/components/Utility/Code/CodeViewerFooter.vue +2 -0
- package/src/components/Utility/Code/MarkdownContent.vue +31 -163
- package/src/components/Utility/Markdown/MarkdownEditor.vue +7 -2
- package/src/components/Utility/Markdown/MarkdownEditorContent.vue +69 -8
- package/src/components/Utility/Widgets/LabelPillWidget.vue +20 -0
- package/src/composables/markdown/features/useCodeBlocks.spec.ts +59 -33
- package/src/composables/markdown/features/useLinks.spec.ts +29 -10
- package/src/composables/markdown/useMarkdownEditor.ts +16 -7
- package/src/composables/useCodeFormat.ts +17 -10
- package/src/helpers/formats/highlightCSS.ts +236 -0
- package/src/helpers/formats/highlightHTML.ts +483 -0
- package/src/helpers/formats/highlightJavaScript.ts +346 -0
- package/src/helpers/formats/highlightSyntax.ts +15 -4
- package/src/helpers/formats/index.ts +3 -0
- package/src/helpers/formats/markdown/htmlToMarkdown/index.ts +15 -2
- package/src/helpers/formats/markdown/linePatterns.spec.ts +7 -4
- package/src/styles/danx.scss +3 -3
- package/src/styles/index.scss +5 -5
- package/src/styles/themes/danx/code.scss +257 -1
- package/src/styles/themes/danx/index.scss +10 -10
- package/src/styles/themes/danx/markdown.scss +59 -0
- package/src/test/highlighters.test.ts +153 -0
- package/src/types/widgets.d.ts +2 -2
- package/vite.config.js +5 -1
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight syntax highlighting for JavaScript
|
|
3
|
+
* Returns HTML string with syntax highlighting spans
|
|
4
|
+
* Uses character-by-character tokenization for accurate parsing
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Escape HTML entities to prevent XSS
|
|
9
|
+
*/
|
|
10
|
+
function escapeHtml(text: string): string {
|
|
11
|
+
return text
|
|
12
|
+
.replace(/&/g, "&")
|
|
13
|
+
.replace(/</g, "<")
|
|
14
|
+
.replace(/>/g, ">")
|
|
15
|
+
.replace(/"/g, """)
|
|
16
|
+
.replace(/'/g, "'");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* JavaScript keywords that should be highlighted
|
|
21
|
+
*/
|
|
22
|
+
const JS_KEYWORDS = new Set([
|
|
23
|
+
// Declarations
|
|
24
|
+
"const", "let", "var", "function", "class", "extends", "static",
|
|
25
|
+
// Control flow
|
|
26
|
+
"if", "else", "switch", "case", "default", "for", "while", "do",
|
|
27
|
+
"break", "continue", "return", "throw", "try", "catch", "finally",
|
|
28
|
+
// Operators/values
|
|
29
|
+
"new", "delete", "typeof", "instanceof", "in", "of", "void",
|
|
30
|
+
// Async
|
|
31
|
+
"async", "await", "yield",
|
|
32
|
+
// Module
|
|
33
|
+
"import", "export", "from", "as",
|
|
34
|
+
// Other
|
|
35
|
+
"this", "super", "debugger", "with"
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* JavaScript built-in values
|
|
40
|
+
*/
|
|
41
|
+
const JS_BUILTINS = new Set([
|
|
42
|
+
"true", "false", "null", "undefined", "NaN", "Infinity"
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Check if character can start an identifier
|
|
47
|
+
*/
|
|
48
|
+
function isIdentifierStart(char: string): boolean {
|
|
49
|
+
return /[a-zA-Z_$]/.test(char);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Check if character can be part of an identifier
|
|
54
|
+
*/
|
|
55
|
+
function isIdentifierPart(char: string): boolean {
|
|
56
|
+
return /[a-zA-Z0-9_$]/.test(char);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Check if a character could precede a regex literal
|
|
61
|
+
* Regex can appear after: ( [ { , ; : ! & | = + - * / ? ~ ^ %
|
|
62
|
+
* or at the start of a statement (after newline, return, etc.)
|
|
63
|
+
*/
|
|
64
|
+
function canPrecedeRegex(lastToken: string): boolean {
|
|
65
|
+
if (!lastToken) return true;
|
|
66
|
+
const operators = ["(", "[", "{", ",", ";", ":", "!", "&", "|", "=", "+", "-", "*", "/", "?", "~", "^", "%", "<", ">"];
|
|
67
|
+
const keywords = ["return", "throw", "case", "delete", "void", "typeof", "instanceof", "in", "of", "new"];
|
|
68
|
+
return operators.includes(lastToken) || keywords.includes(lastToken);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Highlight JavaScript syntax by tokenizing character-by-character
|
|
73
|
+
*/
|
|
74
|
+
export function highlightJavaScript(code: string): string {
|
|
75
|
+
if (!code) return "";
|
|
76
|
+
|
|
77
|
+
const result: string[] = [];
|
|
78
|
+
let i = 0;
|
|
79
|
+
let lastToken = ""; // Track last significant token for regex detection
|
|
80
|
+
|
|
81
|
+
while (i < code.length) {
|
|
82
|
+
const char = code[i];
|
|
83
|
+
|
|
84
|
+
// Handle single-line comments: // ...
|
|
85
|
+
if (char === "/" && code[i + 1] === "/") {
|
|
86
|
+
const startIndex = i;
|
|
87
|
+
i += 2; // Skip //
|
|
88
|
+
|
|
89
|
+
// Find end of line
|
|
90
|
+
while (i < code.length && code[i] !== "\n") {
|
|
91
|
+
i++;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const comment = code.slice(startIndex, i);
|
|
95
|
+
result.push(`<span class="syntax-comment">${escapeHtml(comment)}</span>`);
|
|
96
|
+
lastToken = "";
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Handle multi-line comments: /* ... */
|
|
101
|
+
if (char === "/" && code[i + 1] === "*") {
|
|
102
|
+
const startIndex = i;
|
|
103
|
+
i += 2; // Skip /*
|
|
104
|
+
|
|
105
|
+
// Find closing */
|
|
106
|
+
while (i < code.length) {
|
|
107
|
+
if (code[i] === "*" && code[i + 1] === "/") {
|
|
108
|
+
i += 2; // Include */
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
i++;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const comment = code.slice(startIndex, i);
|
|
115
|
+
result.push(`<span class="syntax-comment">${escapeHtml(comment)}</span>`);
|
|
116
|
+
lastToken = "";
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Handle template literals: `...`
|
|
121
|
+
if (char === "`") {
|
|
122
|
+
const startIndex = i;
|
|
123
|
+
i++; // Skip opening backtick
|
|
124
|
+
|
|
125
|
+
// Find closing backtick, handling escape sequences and ${} expressions
|
|
126
|
+
while (i < code.length) {
|
|
127
|
+
if (code[i] === "\\" && i + 1 < code.length) {
|
|
128
|
+
i += 2; // Skip escaped character
|
|
129
|
+
} else if (code[i] === "$" && code[i + 1] === "{") {
|
|
130
|
+
// Template expression - for now, just include it in the string
|
|
131
|
+
// A more sophisticated approach would recursively highlight the expression
|
|
132
|
+
let braceDepth = 1;
|
|
133
|
+
i += 2; // Skip ${
|
|
134
|
+
while (i < code.length && braceDepth > 0) {
|
|
135
|
+
if (code[i] === "{") braceDepth++;
|
|
136
|
+
else if (code[i] === "}") braceDepth--;
|
|
137
|
+
i++;
|
|
138
|
+
}
|
|
139
|
+
} else if (code[i] === "`") {
|
|
140
|
+
i++; // Include closing backtick
|
|
141
|
+
break;
|
|
142
|
+
} else {
|
|
143
|
+
i++;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const str = code.slice(startIndex, i);
|
|
148
|
+
result.push(`<span class="syntax-template">${escapeHtml(str)}</span>`);
|
|
149
|
+
lastToken = "string";
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Handle strings (single or double quoted)
|
|
154
|
+
if (char === '"' || char === "'") {
|
|
155
|
+
const quoteChar = char;
|
|
156
|
+
const startIndex = i;
|
|
157
|
+
i++; // Skip opening quote
|
|
158
|
+
|
|
159
|
+
// Find closing quote, handling escape sequences
|
|
160
|
+
while (i < code.length) {
|
|
161
|
+
if (code[i] === "\\" && i + 1 < code.length) {
|
|
162
|
+
i += 2; // Skip escaped character
|
|
163
|
+
} else if (code[i] === quoteChar) {
|
|
164
|
+
i++; // Include closing quote
|
|
165
|
+
break;
|
|
166
|
+
} else if (code[i] === "\n") {
|
|
167
|
+
// Unterminated string - stop at newline
|
|
168
|
+
break;
|
|
169
|
+
} else {
|
|
170
|
+
i++;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const str = code.slice(startIndex, i);
|
|
175
|
+
result.push(`<span class="syntax-string">${escapeHtml(str)}</span>`);
|
|
176
|
+
lastToken = "string";
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Handle regex literals: /pattern/flags
|
|
181
|
+
if (char === "/" && canPrecedeRegex(lastToken)) {
|
|
182
|
+
// Check if this looks like a regex (not division)
|
|
183
|
+
// A regex must be followed by at least one character that's not / or *
|
|
184
|
+
if (code[i + 1] && code[i + 1] !== "/" && code[i + 1] !== "*") {
|
|
185
|
+
const startIndex = i;
|
|
186
|
+
i++; // Skip opening /
|
|
187
|
+
|
|
188
|
+
// Find closing / handling escape sequences and character classes
|
|
189
|
+
let inCharClass = false;
|
|
190
|
+
while (i < code.length) {
|
|
191
|
+
if (code[i] === "\\" && i + 1 < code.length) {
|
|
192
|
+
i += 2; // Skip escaped character
|
|
193
|
+
} else if (code[i] === "[" && !inCharClass) {
|
|
194
|
+
inCharClass = true;
|
|
195
|
+
i++;
|
|
196
|
+
} else if (code[i] === "]" && inCharClass) {
|
|
197
|
+
inCharClass = false;
|
|
198
|
+
i++;
|
|
199
|
+
} else if (code[i] === "/" && !inCharClass) {
|
|
200
|
+
i++; // Include closing /
|
|
201
|
+
// Include flags (g, i, m, s, u, y, d)
|
|
202
|
+
while (i < code.length && /[gimsuy]/.test(code[i])) {
|
|
203
|
+
i++;
|
|
204
|
+
}
|
|
205
|
+
break;
|
|
206
|
+
} else if (code[i] === "\n") {
|
|
207
|
+
// Unterminated regex - stop at newline
|
|
208
|
+
break;
|
|
209
|
+
} else {
|
|
210
|
+
i++;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const regex = code.slice(startIndex, i);
|
|
215
|
+
result.push(`<span class="syntax-regex">${escapeHtml(regex)}</span>`);
|
|
216
|
+
lastToken = "regex";
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Handle numbers
|
|
222
|
+
if (/\d/.test(char) || (char === "." && /\d/.test(code[i + 1] || ""))) {
|
|
223
|
+
const startIndex = i;
|
|
224
|
+
|
|
225
|
+
// Check for hex, octal, binary
|
|
226
|
+
if (char === "0" && code[i + 1]) {
|
|
227
|
+
const next = code[i + 1].toLowerCase();
|
|
228
|
+
if (next === "x") {
|
|
229
|
+
// Hex: 0x[0-9a-f]+
|
|
230
|
+
i += 2;
|
|
231
|
+
while (i < code.length && /[0-9a-fA-F_]/.test(code[i])) i++;
|
|
232
|
+
} else if (next === "b") {
|
|
233
|
+
// Binary: 0b[01]+
|
|
234
|
+
i += 2;
|
|
235
|
+
while (i < code.length && /[01_]/.test(code[i])) i++;
|
|
236
|
+
} else if (next === "o") {
|
|
237
|
+
// Octal: 0o[0-7]+
|
|
238
|
+
i += 2;
|
|
239
|
+
while (i < code.length && /[0-7_]/.test(code[i])) i++;
|
|
240
|
+
} else {
|
|
241
|
+
// Regular number or legacy octal
|
|
242
|
+
i++;
|
|
243
|
+
}
|
|
244
|
+
} else {
|
|
245
|
+
i++;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Continue with decimal part
|
|
249
|
+
while (i < code.length && /[\d_]/.test(code[i])) i++;
|
|
250
|
+
|
|
251
|
+
// Decimal point
|
|
252
|
+
if (code[i] === "." && /\d/.test(code[i + 1] || "")) {
|
|
253
|
+
i++;
|
|
254
|
+
while (i < code.length && /[\d_]/.test(code[i])) i++;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Exponent
|
|
258
|
+
if ((code[i] === "e" || code[i] === "E") && /[\d+-]/.test(code[i + 1] || "")) {
|
|
259
|
+
i++;
|
|
260
|
+
if (code[i] === "+" || code[i] === "-") i++;
|
|
261
|
+
while (i < code.length && /[\d_]/.test(code[i])) i++;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// BigInt suffix
|
|
265
|
+
if (code[i] === "n") i++;
|
|
266
|
+
|
|
267
|
+
const num = code.slice(startIndex, i);
|
|
268
|
+
result.push(`<span class="syntax-number">${escapeHtml(num)}</span>`);
|
|
269
|
+
lastToken = "number";
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Handle identifiers and keywords
|
|
274
|
+
if (isIdentifierStart(char)) {
|
|
275
|
+
const startIndex = i;
|
|
276
|
+
while (i < code.length && isIdentifierPart(code[i])) {
|
|
277
|
+
i++;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const identifier = code.slice(startIndex, i);
|
|
281
|
+
|
|
282
|
+
if (JS_KEYWORDS.has(identifier)) {
|
|
283
|
+
result.push(`<span class="syntax-keyword">${escapeHtml(identifier)}</span>`);
|
|
284
|
+
lastToken = identifier;
|
|
285
|
+
} else if (JS_BUILTINS.has(identifier)) {
|
|
286
|
+
if (identifier === "true" || identifier === "false") {
|
|
287
|
+
result.push(`<span class="syntax-boolean">${escapeHtml(identifier)}</span>`);
|
|
288
|
+
} else {
|
|
289
|
+
result.push(`<span class="syntax-null">${escapeHtml(identifier)}</span>`);
|
|
290
|
+
}
|
|
291
|
+
lastToken = identifier;
|
|
292
|
+
} else {
|
|
293
|
+
// Regular identifier
|
|
294
|
+
result.push(escapeHtml(identifier));
|
|
295
|
+
lastToken = "identifier";
|
|
296
|
+
}
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Handle operators
|
|
301
|
+
const operators = ["===", "!==", "==", "!=", "<=", ">=", "&&", "||", "??",
|
|
302
|
+
"++", "--", "+=", "-=", "*=", "/=", "%=", "**=", "&=", "|=", "^=",
|
|
303
|
+
"<<=", ">>=", ">>>=", "=>", "...", "**", "<<", ">>", ">>>",
|
|
304
|
+
"+", "-", "*", "/", "%", "&", "|", "^", "~", "!", "<", ">", "=", "?", ":"
|
|
305
|
+
];
|
|
306
|
+
|
|
307
|
+
let matchedOp = "";
|
|
308
|
+
for (const op of operators) {
|
|
309
|
+
if (code.slice(i, i + op.length) === op) {
|
|
310
|
+
if (op.length > matchedOp.length) {
|
|
311
|
+
matchedOp = op;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (matchedOp) {
|
|
317
|
+
result.push(`<span class="syntax-operator">${escapeHtml(matchedOp)}</span>`);
|
|
318
|
+
lastToken = matchedOp;
|
|
319
|
+
i += matchedOp.length;
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Handle punctuation
|
|
324
|
+
if (/[{}()\[\];,.]/.test(char)) {
|
|
325
|
+
result.push(`<span class="syntax-punctuation">${escapeHtml(char)}</span>`);
|
|
326
|
+
lastToken = char;
|
|
327
|
+
i++;
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Handle whitespace
|
|
332
|
+
if (/\s/.test(char)) {
|
|
333
|
+
result.push(escapeHtml(char));
|
|
334
|
+
// Don't reset lastToken for whitespace
|
|
335
|
+
i++;
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Default: just escape and add
|
|
340
|
+
result.push(escapeHtml(char));
|
|
341
|
+
lastToken = char;
|
|
342
|
+
i++;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return result.join("");
|
|
346
|
+
}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Lightweight syntax highlighting for JSON and
|
|
2
|
+
* Lightweight syntax highlighting for JSON, YAML, HTML, CSS, and JavaScript
|
|
3
3
|
* Returns HTML string with syntax highlighting spans
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
import { highlightCSS } from "./highlightCSS";
|
|
7
|
+
import { highlightJavaScript } from "./highlightJavaScript";
|
|
8
|
+
import { highlightHTML } from "./highlightHTML";
|
|
9
|
+
|
|
10
|
+
export type HighlightFormat = "json" | "yaml" | "text" | "markdown" | "html" | "css" | "javascript";
|
|
7
11
|
|
|
8
12
|
export interface HighlightOptions {
|
|
9
13
|
format: HighlightFormat;
|
|
@@ -25,7 +29,7 @@ function escapeHtml(text: string): string {
|
|
|
25
29
|
* Highlight JSON syntax by tokenizing first, then applying highlights
|
|
26
30
|
* This prevents issues with regex replacing content inside already-matched strings
|
|
27
31
|
*/
|
|
28
|
-
|
|
32
|
+
function highlightJSON(code: string): string {
|
|
29
33
|
if (!code) return "";
|
|
30
34
|
|
|
31
35
|
const result: string[] = [];
|
|
@@ -156,7 +160,7 @@ function getIndentLevel(line: string): number {
|
|
|
156
160
|
/**
|
|
157
161
|
* Highlight YAML syntax with multi-line string support
|
|
158
162
|
*/
|
|
159
|
-
|
|
163
|
+
function highlightYAML(code: string): string {
|
|
160
164
|
if (!code) return "";
|
|
161
165
|
|
|
162
166
|
const lines = code.split("\n");
|
|
@@ -320,7 +324,14 @@ export function highlightSyntax(code: string, options: HighlightOptions): string
|
|
|
320
324
|
return highlightJSON(code);
|
|
321
325
|
case "yaml":
|
|
322
326
|
return highlightYAML(code);
|
|
327
|
+
case "html":
|
|
328
|
+
return highlightHTML(code);
|
|
329
|
+
case "css":
|
|
330
|
+
return highlightCSS(code);
|
|
331
|
+
case "javascript":
|
|
332
|
+
return highlightJavaScript(code);
|
|
323
333
|
case "text":
|
|
334
|
+
case "markdown":
|
|
324
335
|
default:
|
|
325
336
|
return escapeHtml(code);
|
|
326
337
|
}
|
|
@@ -364,8 +364,21 @@ function processNode(node: Node): string {
|
|
|
364
364
|
break;
|
|
365
365
|
}
|
|
366
366
|
|
|
367
|
-
// Divs
|
|
368
|
-
case "div":
|
|
367
|
+
// Divs - check for code block wrapper first
|
|
368
|
+
case "div": {
|
|
369
|
+
// Handle code block wrapper structure
|
|
370
|
+
if (element.hasAttribute("data-code-block-id")) {
|
|
371
|
+
const mountPoint = element.querySelector(".code-viewer-mount-point");
|
|
372
|
+
const content = mountPoint?.getAttribute("data-content") || "";
|
|
373
|
+
const language = mountPoint?.getAttribute("data-language") || "";
|
|
374
|
+
parts.push(`\`\`\`${language}\n${content}\n\`\`\`\n\n`);
|
|
375
|
+
} else {
|
|
376
|
+
parts.push(processNode(element));
|
|
377
|
+
}
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Spans - just process children
|
|
369
382
|
case "span":
|
|
370
383
|
parts.push(processNode(element));
|
|
371
384
|
break;
|
|
@@ -204,8 +204,10 @@ describe('linePatterns', () => {
|
|
|
204
204
|
});
|
|
205
205
|
|
|
206
206
|
describe('detectCodeFenceStart', () => {
|
|
207
|
-
it('
|
|
208
|
-
|
|
207
|
+
it('returns null for code fence without language (requires language identifier)', () => {
|
|
208
|
+
// Implementation intentionally requires at least one character in language identifier
|
|
209
|
+
// to avoid triggering on just "```" before user finishes typing the language
|
|
210
|
+
expect(detectCodeFenceStart('```')).toBeNull();
|
|
209
211
|
});
|
|
210
212
|
|
|
211
213
|
it('detects code fence with javascript language', () => {
|
|
@@ -426,8 +428,9 @@ describe('linePatterns', () => {
|
|
|
426
428
|
});
|
|
427
429
|
|
|
428
430
|
describe('code block patterns', () => {
|
|
429
|
-
it('
|
|
430
|
-
|
|
431
|
+
it('returns null for code block without language (requires language identifier)', () => {
|
|
432
|
+
// Implementation intentionally requires language identifier
|
|
433
|
+
expect(detectLinePattern('```')).toBeNull();
|
|
431
434
|
});
|
|
432
435
|
|
|
433
436
|
it('detects code block with language', () => {
|
package/src/styles/danx.scss
CHANGED
package/src/styles/index.scss
CHANGED