@constela/runtime 0.3.5 → 0.4.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/dist/index.js +87 -0
- package/package.json +8 -4
package/dist/index.js
CHANGED
|
@@ -381,6 +381,51 @@ async function executeFetchStep(step, ctx) {
|
|
|
381
381
|
}
|
|
382
382
|
}
|
|
383
383
|
|
|
384
|
+
// src/renderer/markdown.ts
|
|
385
|
+
import { marked } from "marked";
|
|
386
|
+
import DOMPurify from "dompurify";
|
|
387
|
+
marked.setOptions({
|
|
388
|
+
gfm: true,
|
|
389
|
+
breaks: false
|
|
390
|
+
});
|
|
391
|
+
function parseMarkdown(content) {
|
|
392
|
+
const rawHtml = marked.parse(content, { async: false });
|
|
393
|
+
return DOMPurify.sanitize(rawHtml, {
|
|
394
|
+
USE_PROFILES: { html: true },
|
|
395
|
+
FORBID_TAGS: ["script", "style", "iframe"],
|
|
396
|
+
FORBID_ATTR: ["onerror", "onload", "onclick"]
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// src/renderer/code.ts
|
|
401
|
+
import { createHighlighter } from "shiki";
|
|
402
|
+
var highlighterPromise = null;
|
|
403
|
+
var DEFAULT_LANGUAGES = ["javascript", "typescript", "json", "html", "css"];
|
|
404
|
+
async function getHighlighter() {
|
|
405
|
+
if (!highlighterPromise) {
|
|
406
|
+
highlighterPromise = createHighlighter({
|
|
407
|
+
themes: ["github-dark", "github-light"],
|
|
408
|
+
langs: DEFAULT_LANGUAGES
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
return highlighterPromise;
|
|
412
|
+
}
|
|
413
|
+
async function highlightCode(code, language) {
|
|
414
|
+
const highlighter = await getHighlighter();
|
|
415
|
+
const loadedLangs = highlighter.getLoadedLanguages();
|
|
416
|
+
if (!loadedLangs.includes(language)) {
|
|
417
|
+
try {
|
|
418
|
+
await highlighter.loadLanguage(language);
|
|
419
|
+
} catch {
|
|
420
|
+
language = "plaintext";
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return highlighter.codeToHtml(code, {
|
|
424
|
+
lang: language,
|
|
425
|
+
theme: "github-dark"
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
|
|
384
429
|
// src/renderer/index.ts
|
|
385
430
|
function isEventHandler(value) {
|
|
386
431
|
return typeof value === "object" && value !== null && "event" in value && "action" in value;
|
|
@@ -395,6 +440,10 @@ function render(node, ctx) {
|
|
|
395
440
|
return renderIf(node, ctx);
|
|
396
441
|
case "each":
|
|
397
442
|
return renderEach(node, ctx);
|
|
443
|
+
case "markdown":
|
|
444
|
+
return renderMarkdown(node, ctx);
|
|
445
|
+
case "code":
|
|
446
|
+
return renderCode(node, ctx);
|
|
398
447
|
default:
|
|
399
448
|
throw new Error("Unknown node kind");
|
|
400
449
|
}
|
|
@@ -602,6 +651,44 @@ function renderEach(node, ctx) {
|
|
|
602
651
|
}
|
|
603
652
|
return fragment;
|
|
604
653
|
}
|
|
654
|
+
function renderMarkdown(node, ctx) {
|
|
655
|
+
const container = document.createElement("div");
|
|
656
|
+
container.className = "constela-markdown";
|
|
657
|
+
const cleanup = createEffect(() => {
|
|
658
|
+
const content = evaluate(node.content, { state: ctx.state, locals: ctx.locals });
|
|
659
|
+
const html = parseMarkdown(String(content ?? ""));
|
|
660
|
+
container.innerHTML = html;
|
|
661
|
+
});
|
|
662
|
+
ctx.cleanups?.push(cleanup);
|
|
663
|
+
return container;
|
|
664
|
+
}
|
|
665
|
+
function renderCode(node, ctx) {
|
|
666
|
+
const container = document.createElement("div");
|
|
667
|
+
container.className = "constela-code";
|
|
668
|
+
const pre = document.createElement("pre");
|
|
669
|
+
const codeEl = document.createElement("code");
|
|
670
|
+
container.appendChild(pre);
|
|
671
|
+
pre.appendChild(codeEl);
|
|
672
|
+
const cleanup = createEffect(() => {
|
|
673
|
+
const language = String(evaluate(node.language, { state: ctx.state, locals: ctx.locals }) ?? "plaintext");
|
|
674
|
+
const content = String(evaluate(node.content, { state: ctx.state, locals: ctx.locals }) ?? "");
|
|
675
|
+
codeEl.className = `language-${language || "plaintext"}`;
|
|
676
|
+
codeEl.dataset["language"] = language || "plaintext";
|
|
677
|
+
container.dataset["language"] = language || "plaintext";
|
|
678
|
+
codeEl.textContent = content;
|
|
679
|
+
highlightCode(content, language || "plaintext").then((html) => {
|
|
680
|
+
container.innerHTML = html;
|
|
681
|
+
const newCode = container.querySelector("code");
|
|
682
|
+
if (newCode) {
|
|
683
|
+
newCode.classList.add(`language-${language || "plaintext"}`);
|
|
684
|
+
newCode.dataset["language"] = language || "plaintext";
|
|
685
|
+
}
|
|
686
|
+
container.dataset["language"] = language || "plaintext";
|
|
687
|
+
});
|
|
688
|
+
});
|
|
689
|
+
ctx.cleanups?.push(cleanup);
|
|
690
|
+
return container;
|
|
691
|
+
}
|
|
605
692
|
|
|
606
693
|
// src/app.ts
|
|
607
694
|
function createApp(program, mount) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@constela/runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Runtime DOM renderer for Constela UI framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -15,13 +15,17 @@
|
|
|
15
15
|
"dist"
|
|
16
16
|
],
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"
|
|
19
|
-
"
|
|
18
|
+
"dompurify": "^3.3.1",
|
|
19
|
+
"marked": "^17.0.1",
|
|
20
|
+
"shiki": "^3.20.0",
|
|
21
|
+
"@constela/compiler": "0.4.0",
|
|
22
|
+
"@constela/core": "0.4.0"
|
|
20
23
|
},
|
|
21
24
|
"devDependencies": {
|
|
25
|
+
"@types/dompurify": "^3.2.0",
|
|
26
|
+
"@types/jsdom": "^21.1.0",
|
|
22
27
|
"@types/node": "^20.10.0",
|
|
23
28
|
"jsdom": "^24.0.0",
|
|
24
|
-
"@types/jsdom": "^21.1.0",
|
|
25
29
|
"tsup": "^8.0.0",
|
|
26
30
|
"typescript": "^5.3.0",
|
|
27
31
|
"vitest": "^2.0.0"
|