@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.
Files changed (2) hide show
  1. package/dist/index.js +87 -0
  2. 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.5",
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
- "@constela/core": "0.3.3",
19
- "@constela/compiler": "0.3.3"
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"