From 213c556659b25e95ce4041a55379451b1fbf1328 Mon Sep 17 00:00:00 2001 From: Gordon Smith Date: Tue, 8 Oct 2024 16:42:17 +0100 Subject: [PATCH] feat: Add markdown-it-plugin Signed-off-by: Gordon Smith --- package-lock.json | 98 ++++- package.json | 1 + packages/markdown-it-plugins/.gitignore | 2 + .../markdown-it-plugins/.vitepress/config.ts | 62 ++++ .../.vitepress/theme/index.ts | 19 + .../markdown-it-plugins/.vscode/launch.json | 47 +++ .../markdown-it-plugins/.vscode/tasks.json | 36 ++ packages/markdown-it-plugins/README.md | 48 +++ .../markdown-it-plugins/docs/ecl-vitepress.md | 63 ++++ packages/markdown-it-plugins/docs/ecl.md | 63 ++++ .../markdown-it-plugins/docs/hello-world.md | 33 ++ packages/markdown-it-plugins/docs/index.md | 28 ++ .../docs/observablehq-markdown-it.md | 111 ++++++ .../docs/observablehq-vitepress.md | 130 +++++++ packages/markdown-it-plugins/esbuild.js | 8 + packages/markdown-it-plugins/index.html | 22 ++ packages/markdown-it-plugins/package.json | 69 ++++ .../public/assets/hpcc-systems-dark.svg | 79 ++++ .../public/assets/hpcc-systems.svg | 69 ++++ .../src/ecl-lang/ecl-configuration.json | 164 +++++++++ .../src/ecl-lang/ecl.tmLanguage.json | 348 ++++++++++++++++++ .../markdown-it-plugins/src/ecl-lang/index.ts | 12 + packages/markdown-it-plugins/src/fence.ts | 59 +++ packages/markdown-it-plugins/src/index.ts | 47 +++ packages/markdown-it-plugins/src/render.ts | 38 ++ .../src/template-literal.ts | 84 +++++ packages/markdown-it-plugins/src/util.ts | 78 ++++ .../src/vitepress/RenderComponent.vue | 33 ++ .../src/vitepress/style.css | 149 ++++++++ .../tests/Basic usage/Minimal Example.md | 22 ++ .../tests/index.browser.spec.ts | 33 ++ packages/markdown-it-plugins/tests/index.ts | 23 ++ packages/markdown-it-plugins/tests/style.css | 20 + packages/markdown-it-plugins/tsconfig.json | 24 ++ .../markdown-it-plugins/vitest.workspace.ts | 6 + 35 files changed, 2112 insertions(+), 16 deletions(-) create mode 100644 packages/markdown-it-plugins/.gitignore create mode 100644 packages/markdown-it-plugins/.vitepress/config.ts create mode 100644 packages/markdown-it-plugins/.vitepress/theme/index.ts create mode 100644 packages/markdown-it-plugins/.vscode/launch.json create mode 100644 packages/markdown-it-plugins/.vscode/tasks.json create mode 100644 packages/markdown-it-plugins/README.md create mode 100644 packages/markdown-it-plugins/docs/ecl-vitepress.md create mode 100644 packages/markdown-it-plugins/docs/ecl.md create mode 100644 packages/markdown-it-plugins/docs/hello-world.md create mode 100644 packages/markdown-it-plugins/docs/index.md create mode 100644 packages/markdown-it-plugins/docs/observablehq-markdown-it.md create mode 100644 packages/markdown-it-plugins/docs/observablehq-vitepress.md create mode 100644 packages/markdown-it-plugins/esbuild.js create mode 100644 packages/markdown-it-plugins/index.html create mode 100644 packages/markdown-it-plugins/package.json create mode 100644 packages/markdown-it-plugins/public/assets/hpcc-systems-dark.svg create mode 100644 packages/markdown-it-plugins/public/assets/hpcc-systems.svg create mode 100644 packages/markdown-it-plugins/src/ecl-lang/ecl-configuration.json create mode 100644 packages/markdown-it-plugins/src/ecl-lang/ecl.tmLanguage.json create mode 100644 packages/markdown-it-plugins/src/ecl-lang/index.ts create mode 100644 packages/markdown-it-plugins/src/fence.ts create mode 100644 packages/markdown-it-plugins/src/index.ts create mode 100644 packages/markdown-it-plugins/src/render.ts create mode 100644 packages/markdown-it-plugins/src/template-literal.ts create mode 100644 packages/markdown-it-plugins/src/util.ts create mode 100644 packages/markdown-it-plugins/src/vitepress/RenderComponent.vue create mode 100644 packages/markdown-it-plugins/src/vitepress/style.css create mode 100644 packages/markdown-it-plugins/tests/Basic usage/Minimal Example.md create mode 100644 packages/markdown-it-plugins/tests/index.browser.spec.ts create mode 100644 packages/markdown-it-plugins/tests/index.ts create mode 100644 packages/markdown-it-plugins/tests/style.css create mode 100644 packages/markdown-it-plugins/tsconfig.json create mode 100644 packages/markdown-it-plugins/vitest.workspace.ts diff --git a/package-lock.json b/package-lock.json index 0acdda673e..f72a143084 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,6 +34,7 @@ "xpackages/loader", "xpackages/map", "xpackages/map-deck", + "packages/markdown-it-plugins", "xpackages/marshaller", "packages/observable-shim", "packages/observablehq-compiler", @@ -1239,6 +1240,10 @@ "resolved": "packages/esbuild-plugins", "link": true }, + "node_modules/@hpcc-js/markdown-it-plugins": { + "resolved": "packages/markdown-it-plugins", + "link": true + }, "node_modules/@hpcc-js/observable-shim": { "resolved": "packages/observable-shim", "link": true @@ -2651,7 +2656,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/@observablehq/inspector/-/inspector-5.0.1.tgz", "integrity": "sha512-euwWxwDa6KccU4G3D2JBD7GI/2McJh/z7HHEzJKbj2TDa7zhI37eTbTxiwE9rgTWBagvVBel+hAmnJRYBYOv2Q==", - "dev": true, "dependencies": { "isoformat": "^0.2.0" } @@ -2673,7 +2677,6 @@ "version": "5.9.9", "resolved": "https://registry.npmjs.org/@observablehq/runtime/-/runtime-5.9.9.tgz", "integrity": "sha512-vvRNEI+hESOfnM0pzRTMZa5qbhQaO8KX3LzaDB5h6iHr321T+T2k5ZcK8JTgsH73BvvD3d+CuHBe1nnbUI+C8w==", - "dev": true, "dependencies": { "@observablehq/inspector": "^5.0.0", "@observablehq/stdlib": "^5.0.0" @@ -2683,7 +2686,6 @@ "version": "5.8.8", "resolved": "https://registry.npmjs.org/@observablehq/stdlib/-/stdlib-5.8.8.tgz", "integrity": "sha512-XxVfXX4N+8QYqg308+KT2cpXcsiL6yFphrYNOyCNReqezeoK0Zd9xOdSvo/0NX8NJ8HypIZdTQNwPeOvQWOm2Q==", - "dev": true, "dependencies": { "d3-array": "^3.2.0", "d3-dsv": "^3.0.1", @@ -2697,7 +2699,6 @@ "version": "3.2.4", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", - "dev": true, "dependencies": { "internmap": "1 - 2" }, @@ -6243,7 +6244,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", - "dev": true, "dependencies": { "commander": "7", "iconv-lite": "0.6", @@ -6268,7 +6268,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "dev": true, "engines": { "node": ">= 10" } @@ -6277,7 +6276,6 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -6352,8 +6350,7 @@ "node_modules/d3-require": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/d3-require/-/d3-require-1.3.0.tgz", - "integrity": "sha512-XaNc2azaAwXhGjmCMtxlD+AowpMfLimVsAoTMpqrvb8CWoA4QqyV12mc4Ue6KSoDvfuS831tsumfhDYxGd4FGA==", - "dev": true + "integrity": "sha512-XaNc2azaAwXhGjmCMtxlD+AowpMfLimVsAoTMpqrvb8CWoA4QqyV12mc4Ue6KSoDvfuS831tsumfhDYxGd4FGA==" }, "node_modules/d3-scale": { "version": "1.0.7", @@ -9106,7 +9103,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", - "dev": true, "engines": { "node": ">=12" } @@ -9607,8 +9603,7 @@ "node_modules/isoformat": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/isoformat/-/isoformat-0.2.1.tgz", - "integrity": "sha512-tFLRAygk9NqrRPhJSnNGh7g7oaVWDwR0wKh/GM2LgmPa50Eg4UfyaCO4I8k6EqJHl1/uh2RAD6g06n5ygEnrjQ==", - "dev": true + "integrity": "sha512-tFLRAygk9NqrRPhJSnNGh7g7oaVWDwR0wKh/GM2LgmPa50Eg4UfyaCO4I8k6EqJHl1/uh2RAD6g06n5ygEnrjQ==" }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", @@ -10314,6 +10309,16 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, "node_modules/load-json-file": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-6.2.0.tgz", @@ -10499,6 +10504,31 @@ "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", "dev": true }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdown-it/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, "node_modules/mdast-util-to-hast": { "version": "13.2.0", "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", @@ -10520,6 +10550,13 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true, + "license": "MIT" + }, "node_modules/memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", @@ -13627,6 +13664,16 @@ "node": ">=6" } }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/pupa": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/pupa/-/pupa-3.1.0.tgz", @@ -14318,8 +14365,7 @@ "node_modules/rw": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", - "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", - "dev": true + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" }, "node_modules/rxjs": { "version": "7.8.1", @@ -14387,8 +14433,7 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sax": { "version": "1.4.1", @@ -15645,6 +15690,13 @@ "typescript": "^2.1.6 || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >=3.0.0-dev" } }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" + }, "node_modules/uglify-js": { "version": "3.19.3", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", @@ -17254,6 +17306,20 @@ "@esbuild/win32-x64": "0.24.0" } }, + "packages/markdown-it-plugins": { + "name": "@hpcc-js/markdown-it-plugins", + "version": "1.0.0", + "license": "Apache-2.0", + "dependencies": { + "@hpcc-js/observablehq-compiler": "^1.3.0", + "@observablehq/runtime": "5.9.9" + }, + "devDependencies": { + "@hpcc-js/esbuild-plugins": "^1.1.2", + "@types/markdown-it": "14.1.2", + "markdown-it": "14.1.0" + } + }, "packages/observable-shim": { "name": "@hpcc-js/observable-shim", "version": "2.6.0", diff --git a/package.json b/package.json index 50c3dca8c6..659bbcd8b2 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "xpackages/loader", "xpackages/map", "xpackages/map-deck", + "packages/markdown-it-plugins", "xpackages/marshaller", "packages/observable-shim", "packages/observablehq-compiler", diff --git a/packages/markdown-it-plugins/.gitignore b/packages/markdown-it-plugins/.gitignore new file mode 100644 index 0000000000..b73303468e --- /dev/null +++ b/packages/markdown-it-plugins/.gitignore @@ -0,0 +1,2 @@ +.vitepress/cache +.vitepress/dist diff --git a/packages/markdown-it-plugins/.vitepress/config.ts b/packages/markdown-it-plugins/.vitepress/config.ts new file mode 100644 index 0000000000..02c382f3dc --- /dev/null +++ b/packages/markdown-it-plugins/.vitepress/config.ts @@ -0,0 +1,62 @@ +import { defineConfig } from "vitepress"; +import { observable } from "@hpcc-js/markdown-it-plugins"; +import { eclLang } from "@hpcc-js/markdown-it-plugins/ecl-lang"; + +// https://vitepress.dev/reference/site-config +export default async () => { + + return defineConfig({ + title: "@hpcc-js/markdown-it-plugins", + description: "ObservableHQ plugin for markdown-it", + base: "/Visualization/markdown-it-plugins", + srcDir: ".", + rewrites: { + 'docs/index.md': 'index.md' + }, + lastUpdated: true, + themeConfig: { + // https://vitepress.dev/reference/default-theme-config + nav: [ + { text: "Home", link: "/" }, + { text: "Getting Started", link: "/README" }, + { text: "Documentation", link: "/docs/observablehq-markdown-it" }, + ], + + sidebar: [ + { text: "Getting Started", link: "/README" }, + { + text: "Documentation", + items: [ + { + text: "markdown-it", + items: [ + { text: "ObservableHQ", link: "/docs/observablehq-markdown-it" }, + ] + }, + { + text: "VitePress", + items: [ + { text: "ObservableHQ", link: "/docs/observablehq-vitepress" }, + { text: "ECL Code Highlighting", link: "/docs/ecl-vitepress" } + ] + } + ] + } + ], + + socialLinks: [ + { icon: "github", link: "https://github.com/hpcc-systems/visualization" } + ], + + }, + markdown: { + // https://github.com/vuejs/vitepress/blob/main/src/node/markdown/markdown.ts + config: md => { + md.use(observable, { vitePress: true }); + }, + + languages: [eclLang()], + }, + + }); +}; diff --git a/packages/markdown-it-plugins/.vitepress/theme/index.ts b/packages/markdown-it-plugins/.vitepress/theme/index.ts new file mode 100644 index 0000000000..bbee82473e --- /dev/null +++ b/packages/markdown-it-plugins/.vitepress/theme/index.ts @@ -0,0 +1,19 @@ +// https://vitepress.dev/guide/custom-theme +import { h } from "vue"; +import type { Theme } from "vitepress"; +import DefaultTheme from "vitepress/theme"; +import RenderComponent from "@hpcc-js/markdown-it-plugins/vitepress/RenderComponent.vue"; +import "@hpcc-js/markdown-it-plugins/vitepress/style.css"; + +export default { + extends: DefaultTheme, + Layout: () => { + return h(DefaultTheme.Layout, null, { + // https://vitepress.dev/guide/extending-default-theme#layout-slots + }); + }, + enhanceApp({ app }) { + app.component("RenderComponent", RenderComponent); + }, + +} satisfies Theme; diff --git a/packages/markdown-it-plugins/.vscode/launch.json b/packages/markdown-it-plugins/.vscode/launch.json new file mode 100644 index 0000000000..7c2436335f --- /dev/null +++ b/packages/markdown-it-plugins/.vscode/launch.json @@ -0,0 +1,47 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "test-browser", + "type": "msedge", + "request": "launch", + "url": "http://localhost:8888", + "webRoot": "${workspaceFolder}", + "outFiles": [ + "${workspaceFolder}/**/*.js", + "!**/node_modules/**" + ], + }, + { + "name": "test-node", + "type": "node", + "request": "launch", + "runtimeArgs": [ + "run-script", + "test-node" + ], + "runtimeExecutable": "npm", + "skipFiles": [ + "/**" + ], + "outFiles": [ + "${workspaceFolder}/**/*.js", + "!**/node_modules/**" + ], + }, + { + "name": "index.html", + "request": "launch", + "type": "msedge", + "url": "file:///${workspaceFolder}/index.html", + "runtimeArgs": [ + "--disable-web-security" + ], + "webRoot": "${workspaceFolder}", + "outFiles": [ + "${workspaceFolder}/**/*.js", + "!**/node_modules/**" + ] + } + ] +} \ No newline at end of file diff --git a/packages/markdown-it-plugins/.vscode/tasks.json b/packages/markdown-it-plugins/.vscode/tasks.json new file mode 100644 index 0000000000..120545d06c --- /dev/null +++ b/packages/markdown-it-plugins/.vscode/tasks.json @@ -0,0 +1,36 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "gen-types-watch", + "type": "npm", + "script": "gen-types-watch", + "problemMatcher": [ + "$tsc-watch" + ], + "presentation": { + "group": "group-build" + } + }, + { + "label": "bundle-watch", + "type": "npm", + "script": "bundle-watch", + "problemMatcher": [], + "presentation": { + "group": "group-build" + } + }, + { + "label": "build", + "dependsOn": [ + "gen-types-watch", + "bundle-watch", + ], + "group": { + "kind": "build", + "isDefault": true + } + } + ] +} \ No newline at end of file diff --git a/packages/markdown-it-plugins/README.md b/packages/markdown-it-plugins/README.md new file mode 100644 index 0000000000..a49a0f6de6 --- /dev/null +++ b/packages/markdown-it-plugins/README.md @@ -0,0 +1,48 @@ +# @hpcc-js/markdown-it-plugins + +_Collection of plugins and extensions for [markdown-it](https://github.com/markdown-it/markdown-it) and [VitePress](https://vitepress.dev)._ + +## Quick Start + +_Simple example of using the `exec` directive to execute ObservableHQ style JavaScript code in markdown:_ + +1. Install with your package manager of choice: + +```bash +npm install --save @hpcc-js/markdown-it-plugins +``` + +2. Initialise `markdown-it` as normal and `use` the `observable` plugin: + +```js +import markdownit from "markdown-it"; +import { observable } from "@hpcc-js/markdown-it-plugins"; + +const md = markdownit({ + html: true, + linkify: true, + typographer: true +}); + +const md = markdownit(); +md.use(observable); + +const html = md.render(`\ +# Hello World - I am ${age} years old! + +\`\`\`js exec +age = 20 + 1; +\`\`\` +`); +``` + +Which will produce: `Hello World - I am 21 years old!` + +## Documentation + +* ObservableHQ style notebook Integration: + * [markdown-it](docs/observablehq-markdown-it.md) + * [VitePress](docs/observablehq-vitepress.md) +* ECL Code Highlighting + * [VitePress](docs/ecl-vitepress.md) +d \ No newline at end of file diff --git a/packages/markdown-it-plugins/docs/ecl-vitepress.md b/packages/markdown-it-plugins/docs/ecl-vitepress.md new file mode 100644 index 0000000000..502dfcf943 --- /dev/null +++ b/packages/markdown-it-plugins/docs/ecl-vitepress.md @@ -0,0 +1,63 @@ +# ECL Code Highlighting + +This plugin adds support for highlighting ECL code blocks in VitePress markdown. + +## VitePress Configuration + +To enable ECL code highlighting in VitePress, add the following to your `.vitepress/config.js`: + +```js +import { defineConfig } from "vitepress"; +import { eclLang } from "@hpcc-js/markdown-it-plugins/ecl-lang"; // [!code focus] + +export default async () => { + + return defineConfig({ + ... + + markdown: { + ... + + languages: [eclLang()] // [!code focus] + }, +``` + +## Usage + +To use ECL code highlighting in your markdown, simply specify `ecl` as the fenced code block type: + +````markdown +```ecl +/* Simple ECL */ + +Layout_Person := RECORD + UNSIGNED1 PersonID; + STRING15 FirstName; + STRING25 LastName; +END; + +allPeople := DATASET([ {1, 'Fred', 'Smith'}, + {2, 'Joe', 'Blow'}, + {3, 'Jane', 'Smith'}], Layout_Person); + +somePeople := allPeople(LastName = 'Smith'); +``` +```` + +Produces: + +```ecl +/* Simple ECL */ + +Layout_Person := RECORD + UNSIGNED1 PersonID; + STRING15 FirstName; + STRING25 LastName; +END; + +allPeople := DATASET([ {1, 'Fred', 'Smith'}, + {2, 'Joe', 'Blow'}, + {3, 'Jane', 'Smith'}], Layout_Person); + +somePeople := allPeople(LastName = 'Smith'); +``` diff --git a/packages/markdown-it-plugins/docs/ecl.md b/packages/markdown-it-plugins/docs/ecl.md new file mode 100644 index 0000000000..502dfcf943 --- /dev/null +++ b/packages/markdown-it-plugins/docs/ecl.md @@ -0,0 +1,63 @@ +# ECL Code Highlighting + +This plugin adds support for highlighting ECL code blocks in VitePress markdown. + +## VitePress Configuration + +To enable ECL code highlighting in VitePress, add the following to your `.vitepress/config.js`: + +```js +import { defineConfig } from "vitepress"; +import { eclLang } from "@hpcc-js/markdown-it-plugins/ecl-lang"; // [!code focus] + +export default async () => { + + return defineConfig({ + ... + + markdown: { + ... + + languages: [eclLang()] // [!code focus] + }, +``` + +## Usage + +To use ECL code highlighting in your markdown, simply specify `ecl` as the fenced code block type: + +````markdown +```ecl +/* Simple ECL */ + +Layout_Person := RECORD + UNSIGNED1 PersonID; + STRING15 FirstName; + STRING25 LastName; +END; + +allPeople := DATASET([ {1, 'Fred', 'Smith'}, + {2, 'Joe', 'Blow'}, + {3, 'Jane', 'Smith'}], Layout_Person); + +somePeople := allPeople(LastName = 'Smith'); +``` +```` + +Produces: + +```ecl +/* Simple ECL */ + +Layout_Person := RECORD + UNSIGNED1 PersonID; + STRING15 FirstName; + STRING25 LastName; +END; + +allPeople := DATASET([ {1, 'Fred', 'Smith'}, + {2, 'Joe', 'Blow'}, + {3, 'Jane', 'Smith'}], Layout_Person); + +somePeople := allPeople(LastName = 'Smith'); +``` diff --git a/packages/markdown-it-plugins/docs/hello-world.md b/packages/markdown-it-plugins/docs/hello-world.md new file mode 100644 index 0000000000..ff51d70d1b --- /dev/null +++ b/packages/markdown-it-plugins/docs/hello-world.md @@ -0,0 +1,33 @@ +# Markdown Extension Examples + +This page demonstrates some of the built-in markdown extensions provided by VitePress. + +## Syntax Highlighting + +VitePress provides Syntax Highlighting powered by [Shiki](https://github.com/shikijs/shiki), with additional features like line-highlighting: + +```js exec +Plot.rectY({length: 10000}, Plot.binX({y: "count"}, {x: d3.randomNormal()})).plot(); +``` + + +```ecl +r := RECORD + name: STRING; + age: INT; +END; +OUTPUT([{name: 'Alice', age: 30}, {name: 'Bob', age: 40}], r); +``` + + +```js exec echo +x = 40 + 2; +``` + +```js exec echo +z = x * 2; +``` + +```js +y = 40 + 3; +``` diff --git a/packages/markdown-it-plugins/docs/index.md b/packages/markdown-it-plugins/docs/index.md new file mode 100644 index 0000000000..7d5630c095 --- /dev/null +++ b/packages/markdown-it-plugins/docs/index.md @@ -0,0 +1,28 @@ +--- +# https://vitepress.dev/reference/default-theme-home-page +layout: home + +hero: + name: "markdown-it-plugins" + text: "" + tagline: "Various extensions for markdown-it and VitePress" + image: + dark: assets/hpcc-systems-dark.svg + light: assets/hpcc-systems.svg + actions: + - theme: brand + text: Getting Started + link: README + - theme: alt + text: Documentation + link: /docs/observablehq-markdown-it + +features: + - title: ObservableHQ Integration + details: Integrate ObservableHQ style JavaScript directly into your markdown + - title: VitePress Support + details: Includes custom theme to also support ObservableHQ style JavaScript in VitePress documentation + - title: ECL Language Highlighting + details: Includes highlighting support for fenced ECL code blocks +--- + diff --git a/packages/markdown-it-plugins/docs/observablehq-markdown-it.md b/packages/markdown-it-plugins/docs/observablehq-markdown-it.md new file mode 100644 index 0000000000..f26f398456 --- /dev/null +++ b/packages/markdown-it-plugins/docs/observablehq-markdown-it.md @@ -0,0 +1,111 @@ +# markdown-it Plugin + +This plugin adds support for ObservableHQ style notebooks in your generated markdown. + +## Configuration + +To enable ObservableHQ style notebooks in markdown-it, simply add the plugin to your markdown-it setup and add the `exec` annotation to the code block you wish to execute. + +```js +import markdownit from "markdown-it"; +import { observable } from "@hpcc-js/markdown-it-plugins"; // [!code focus] + +const md = markdownit({ + html: true, + linkify: true, + typographer: true +}); + +const md = markdownit(); +md.use(observable); // [!code focus] + +const html = md.render(`\ +# Hello World - I am ${age} years old! + +\`\`\`js exec // [!code focus] +age = 20 + 1; +\`\`\` +`); +``` + +## Usage + +To use ObservableHQ style notebooks in your markdown, simply add the `exec` annotation to the `js` or `javascript` code block. The code block will then be executed and the result will be displayed in the markdown. + +### Annotations + +The following annotations are supported: + +- `exec` - Executes the code block. +- `echo` - Echoes the code block. +- `hide` - Hides any output from the code block. + +### Examples + +1. `js exec` + +_The following example will execute the contents of the code block and display the result, replacing the code block block in the markdown:_ + +````markdown +```js exec +pi = Math.PI; +``` +```` + +Produces: + +```js exec +pi = Math.PI; +``` + +--- + +2. `js echo` + +_The following example will display the result of the execution and include the origonal code block in the markdown:_ + +````markdown +```js exec echo +piEcho = Math.PI; +``` +```` + +Produces: + +```js exec echo +piEcho = Math.PI; +``` + +3. `js exec echo hide` + +_The following example will execute the code block and hide its result. It will echo origonal code block in the markdown:_ + +````markdown +```js exec echo hide +piEchoHide = Math.PI; +``` +```` + +Produces: + +```js exec echo hide +piEchoHide = Math.PI; +``` + +4. `js exec hide` + +_The following example will execute the code block and hide its result. It will not echo the origonal code block in the markdown:_ + +````markdown +```js exec hide +piHide = Math.PI; +``` +```` + +Produces: + +```js exec hide +piHide = Math.PI; +``` + +...no output, but the code block is executed... \ No newline at end of file diff --git a/packages/markdown-it-plugins/docs/observablehq-vitepress.md b/packages/markdown-it-plugins/docs/observablehq-vitepress.md new file mode 100644 index 0000000000..c3b22a23fd --- /dev/null +++ b/packages/markdown-it-plugins/docs/observablehq-vitepress.md @@ -0,0 +1,130 @@ +# ObservableHQ VitePress Plugin + +This plugin adds support for ObservableHQ style notebooks in VitePress markdown. + +## VitePress Configuration + +To enable ObservableHQ style notebooks in VitePress requires ammending both the config.ts and the themes config. + +1. Add the following to your `.vitepress/config.js`: + +```js +import { defineConfig } from "vitepress"; +import { observable } from "@hpcc-js/markdown-it-plugins"; // [!code focus] +import { eclLang } from "@hpcc-js/markdown-it-plugins/ecl-lang"; + +export default async () => { + + return defineConfig({ + ... + + markdown: { + config: md => { // [!code focus] + md.use(observable, { vitePress: true }); // [!code focus] + }, // [!code focus] + languages: [eclLang()] + }, +``` + +2. Add the following to your `.vitepress/theme/index.ts`: + +```ts +import { h } from "vue"; +import type { Theme } from "vitepress"; +import DefaultTheme from "vitepress/theme"; +import RenderComponent from "@hpcc-js/markdown-it-plugins/vitepress/RenderComponent.vue"; // [!code focus] + +export default { + extends: DefaultTheme, + Layout: () => { + return h(DefaultTheme.Layout, null, { + // https://vitepress.dev/guide/extending-default-theme#layout-slots + }); + }, + enhanceApp({ app }) { // [!code focus] + app.component("RenderComponent", RenderComponent); // [!code focus] + }, // [!code focus] +} satisfies Theme; +``` + +## Usage + +To use ObservableHQ style notebooks in your markdown, simply add the `exec` annotation to the `js` or `javascript` code block. The code block will then be executed and the result will be displayed in the markdown. + +### Annotations + +The following annotations are supported: + +- `exec` - Executes the code block. +- `echo` - Echoes the code block. +- `hide` - Hides any output from the code block. + +### Examples + +1. `js exec` + +_The following example will execute the contents of the code block and display the result, replacing the code block block in the markdown:_ + +````markdown +```js exec +pi = Math.PI; +``` +```` + +Produces: + +```js exec +pi = Math.PI; +``` + +--- + +2. `js echo` + +_The following example will display the result of the execution and include the origonal code block in the markdown:_ + +````markdown +```js exec echo +piEcho = Math.PI; +``` +```` + +Produces: + +```js exec echo +piEcho = Math.PI; +``` + +3. `js exec echo hide` + +_The following example will execute the code block and hide its result. It will echo origonal code block in the markdown:_ + +````markdown +```js exec echo hide +piEchoHide = Math.PI; +``` +```` + +Produces: + +```js exec echo hide +piEchoHide = Math.PI; +``` + +4. `js exec hide` + +_The following example will execute the code block and hide its result. It will not echo the origonal code block in the markdown:_ + +````markdown +```js exec hide +piHide = Math.PI; +``` +```` + +Produces: + +```js exec hide +piHide = Math.PI; +``` + +...no output, but the code block is executed... \ No newline at end of file diff --git a/packages/markdown-it-plugins/esbuild.js b/packages/markdown-it-plugins/esbuild.js new file mode 100644 index 0000000000..c732fa596f --- /dev/null +++ b/packages/markdown-it-plugins/esbuild.js @@ -0,0 +1,8 @@ +import { nodeTpl, browserTpl } from "@hpcc-js/esbuild-plugins"; + +// config --- +await Promise.all([ + nodeTpl("src/index.ts", "dist/index.node"), + nodeTpl("src/ecl-lang/index.ts", "dist/ecl-lang"), + browserTpl("src/index.ts", "dist/index") +]); diff --git a/packages/markdown-it-plugins/index.html b/packages/markdown-it-plugins/index.html new file mode 100644 index 0000000000..2a07d12828 --- /dev/null +++ b/packages/markdown-it-plugins/index.html @@ -0,0 +1,22 @@ + + + + + + + + Vite + TS + + + +
+

min

+
+
+
+

full

+
+ + + + \ No newline at end of file diff --git a/packages/markdown-it-plugins/package.json b/packages/markdown-it-plugins/package.json new file mode 100644 index 0000000000..981512e43d --- /dev/null +++ b/packages/markdown-it-plugins/package.json @@ -0,0 +1,69 @@ +{ + "name": "@hpcc-js/markdown-it-plugins", + "version": "1.0.0", + "description": "markdown-it plugins", + "type": "module", + "exports": { + ".": { + "types": "./types/index.d.ts", + "import": "./dist/index.node.js", + "browser": "./dist/index.js", + "default": "./dist/index.js" + }, + "./ecl-lang": { + "types": "./types/ecl-lang/index.d.ts", + "default": "./dist/ecl-lang.js" + }, + "./vitepress/*": { + "default": "./src/vitepress/*" + } + }, + "types": "./types/index.d.ts", + "main": "./dist/index.node.js", + "browser": "./dist/index.js", + "files": [ + "dist/*", + "src/*", + "types/*" + ], + "scripts": { + "clean": "rimraf --glob lib* types dist dist-test .vitepress/cache .vitepress/dist *.tsbuildinfo .turbo", + "bundle": "node esbuild.js", + "bundle-watch": "npm run bundle -- --development --watch", + "gen-types": "tsc --project tsconfig.json", + "gen-types-watch": "npm run gen-types -- --watch", + "build": "run-p gen-types bundle", + "dev-docs": "vitepress dev", + "build-docs": "vitepress build", + "preview-docs": "vitepress preview", + "stamp": "node ../../node_modules/@hpcc-js/bundle/src/stamp.js", + "lint": "eslint ./src", + "docs": "typedoc --options tdoptions.json .", + "test-browser": "vitest run --project browser", + "test-node": "vitest run --project node", + "test": "vitest run", + "coverage": "vitest run --coverage", + "update": "npx -y npm-check-updates -u -t minor", + "update-major": "npx -y npm-check-updates -u" + }, + "dependencies": { + "@hpcc-js/observablehq-compiler": "^1.3.0", + "@observablehq/runtime": "5.9.9" + }, + "devDependencies": { + "@types/markdown-it": "14.1.2", + "markdown-it": "14.1.0", + "@hpcc-js/esbuild-plugins": "^1.1.2" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/hpcc-systems/Visualization.git" + }, + "author": "Gordon Smith ", + "contributors": [], + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/hpcc-systems/Visualization/issues" + }, + "homepage": "https://github.com/hpcc-systems/Visualization/tree/trunk/packages/markdonw-it-plugins" +} \ No newline at end of file diff --git a/packages/markdown-it-plugins/public/assets/hpcc-systems-dark.svg b/packages/markdown-it-plugins/public/assets/hpcc-systems-dark.svg new file mode 100644 index 0000000000..1484707bcb --- /dev/null +++ b/packages/markdown-it-plugins/public/assets/hpcc-systems-dark.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/markdown-it-plugins/public/assets/hpcc-systems.svg b/packages/markdown-it-plugins/public/assets/hpcc-systems.svg new file mode 100644 index 0000000000..316847edf3 --- /dev/null +++ b/packages/markdown-it-plugins/public/assets/hpcc-systems.svg @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/markdown-it-plugins/src/ecl-lang/ecl-configuration.json b/packages/markdown-it-plugins/src/ecl-lang/ecl-configuration.json new file mode 100644 index 0000000000..b8e748e78a --- /dev/null +++ b/packages/markdown-it-plugins/src/ecl-lang/ecl-configuration.json @@ -0,0 +1,164 @@ +{ + "comments": { + // symbol used for single line comment. Remove this entry if your language does not support line comments + "lineComment": "//", + // symbols used for start and end a block comment. Remove this entry if your language does not support block comments + "blockComment": [ + "/*", + "*/" + ] + }, + // symbols used as brackets + "brackets": [ + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ] + ], + // symbols that are auto closed when typing + "autoClosingPairs": [ + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ], + [ + "'", + "'" + ] + ], + // symbols that that can be used to surround a selection + "surroundingPairs": [ + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ], + [ + "\"", + "\"" + ], + [ + "'", + "'" + ], + [ + "function", + "end;" + ], + [ + "Function", + "End;" + ], + [ + "FUNCTION", + "END;" + ], + [ + "module", + "end;" + ], + [ + "Module", + "End;" + ], + [ + "MODULE", + "END;" + ], + [ + "interface", + "end;" + ], + [ + "Interface", + "End;" + ], + [ + "INTERFACE", + "END;" + ], + [ + "transform", + "end;" + ], + [ + "Transform", + "End;" + ], + [ + "TRANSFORM", + "END;" + ], + [ + "record", + "end;" + ], + [ + "Record", + "End;" + ], + [ + "RECORD", + "END;" + ], + [ + "beginc++", + "endc++;" + ], + [ + "Beginc++", + "Endc++;" + ], + [ + "BEGINC++", + "ENDC++;" + ], + [ + "macro", + "endmacro;" + ], + [ + "MACRO", + "ENDMACRO;" + ], + [ + "Macro", + "Endmacro;" + ], + [ + "functionmacro", + "endmacro;" + ], + [ + "Functionmacro", + "Endmacro;" + ], + [ + "FUNCTIONMACRO", + "ENDMACRO;" + ] + ] +} \ No newline at end of file diff --git a/packages/markdown-it-plugins/src/ecl-lang/ecl.tmLanguage.json b/packages/markdown-it-plugins/src/ecl-lang/ecl.tmLanguage.json new file mode 100644 index 0000000000..0e5503e368 --- /dev/null +++ b/packages/markdown-it-plugins/src/ecl-lang/ecl.tmLanguage.json @@ -0,0 +1,348 @@ +{ + "scopeName": "source.ecl", + "fileTypes": [ + "ecl" + ], + "name": "ECL", + "patterns": [ + { + "include": "#expression" + }, + { + "name": "support.class.ecl", + "match": "\\b(?i:(std).(file|date|str|math|metaphone|metaphone3|uni|audit|blas|system.(debug|email|job|log|thorlib|util|workunit)))\\b" + } + ], + "repository": { + "expression": { + "name": "meta.expression.ecl", + "patterns": [ + { + "include": "#comment" + }, + { + "include": "#special-structures" + }, + { + "include": "#embedded-single" + }, + { + "include": "#embedded-any" + }, + { + "include": "#embedded-cpp" + }, + { + "include": "#function-call" + }, + { + "include": "#operators" + }, + { + "include": "#logicals" + }, + { + "include": "#types" + }, + { + "include": "#keywords-pound" + }, + { + "include": "#keywords-workflow" + }, + { + "include": "#keywords" + }, + { + "include": "#digital-signature" + }, + { + "include": "#string" + }, + { + "include": "#literal" + } + ] + }, + "operators": { + "name": "keyword.operator.ecl", + "match": "(>|>=|<|<=|<>|/|\\|+|-|=)" + }, + "logicals": { + "match": "\\b(?i:(and|in|not|or))\\b", + "name": "keyword.other.ecl" + }, + "special-structures": { + "match": "\\b(?i:(endmacro|function|functionmacro|interface|macro|module|record|transform))\\b", + "name": "entity.name.function.ecl" + }, + "types": { + "name": "entity.name.type.ecl", + "patterns": [ + { + "include": "#real" + }, + { + "include": "#decimal" + }, + { + "include": "#unicode" + }, + { + "include": "#integer" + }, + { + "include": "#type-number" + }, + { + "include": "#type-rest" + } + ] + }, + "real": { + "name": "entity.name.type.ecl", + "match": "\\b(?i)real(?-i)(4|8)\\b" + }, + "decimal": { + "name": "entity.name.type.ecl", + "match": "\\b(?i)(u?)decimal(\\d+(_\\d+)?)\\b" + }, + "unicode": { + "name": "entity.name.type.ecl", + "match": "\\b(?i:(d|u|q|v|x))(8)?(?='.*')" + }, + "integer": { + "name": "entity.name.type.ecl", + "match": "\\b(?i:(integer|unsigned))[1-8]\\b" + }, + "type-number": { + "name": "entity.name.type.ecl", + "match": "\\b(?i:(data|string|qstring|varstring|varunicode|unicode|utf8))\\d+\\b" + }, + "type-rest": { + "name": "entity.name.type.ecl", + "match": "\\b(?i:(ascii|big_endian|boolean|data|decimal|ebcdic|grouped|integer|linkcounted|pattern|qstring|real|rule|set of|streamed|string|token|udecimal|unicode|utf8|unsigned|varstring|varunicode))\\b" + }, + "function-call": { + "match": "([#A-Za-z_0-9]+)\\s*(\\()", + "captures": { + "1": { + "patterns": [ + { + "include": "#functions" + }, + { + "include": "#functions-pound" + }, + { + "include": "#functions-hash" + }, + { + "include": "#functions-hash2" + }, + { + "include": "#functions-action" + }, + { + "include": "#functions-workflow" + } + ] + } + } + }, + "functions": { + "name": "entity.name.function.ecl", + "match": "\\b(?i:(abs|acos|aggregate|allnodes|apply|apply|ascii|asin|assert|asstring|atan|atan2|ave|build|buildindex|case|catch|choose|choosen|choosesets|clustersize|combine|correlation|cos|cosh|count|covariance|cron|dataset|dedup|define|denormalize|deprecated|dictionary|distribute|distributed|distribution|ebcdic|enth|evaluate|evaluate|event|eventextra|eventname|exists|exp|fail|failcode|failmessage|fetch|fromunicode|fromxml|getenv|getisvalid|graph|group|hashcrc|hashmd5|having|httpcall|httpheader|if|iff|index|interface|intformat|isvalid|iterate|join|keydiff|keypatch|keyunicode|length|library|limit|ln|loadxml|local|log|loop|map|matched|matchlength|matchposition|matchtext|matchunicode|max|merge|mergejoin|min|nofold|nolocal|nonempty|normalize|nothor|notify|opt|output|parse|pattern|penalty|pipe|power|preload|process|project|pull|random|range|rank|ranked|realformat|record|recordof|regexfind|regexfindset|regexreplace|regroup|rejected|rollup|round|roundup|row|rowdiff|rule|sample|sequential|sin|sinh|sizeof|soapcall|sort|sorted|sqrt|stepped|stored|sum|table|tan|tanh|thisnode|topn|tounicode|toxml|transfer|transform|trim|truncate|typeof|ungroup|unicodeorder|use|validate|variance|wait|when|which|xmldecode|xmlencode|xmltext|xmlunicode))\\b" + }, + "functions-pound": { + "name": "keyword.other.ecl", + "match": "#(?i:(append|constant|declare|demangle|end|else|elseif|error|expand|export|exportXML|for|getdatatype|if|inmodule|mangle|onwarning|option|set|stored|text|uniquename|warning|webservice|workunit))\\b" + }, + "functions-hash": { + "name": "entity.name.function.ecl", + "match": "\\b(?i:hash(32|64|crc)?)\\b" + }, + "functions-hash2": { + "name": "entity.name.function.ecl", + "match": "\\b(?i:hashmd(5))\\b" + }, + "functions-action": { + "name": "keyword.other.ecl", + "match": "\\b(?i:(algorithm|cluster|escape|encrypt|expire|heading|keyed|maxlength|module|named|ordered|parallel|quote|terminator|threshold|timelimit|timeout|separator|set|skew|virtual|wild))\\b" + }, + "functions-workflow": { + "name": "keyword.control.ecl", + "match": "\\b(?i:(checkpoint|deprecated|failmessage|failure|global|onwarning|persist|priority|recovery|stored|success|when))\\b" + }, + "keywords-workflow": { + "name": "keyword.control.ecl", + "match": "\\b(?i:(global|independent|once))\\b" + }, + "keywords-pound": { + "name": "keyword.other.ecl", + "match": "#(?i:(break|else|end|loop))\\b" + }, + "keywords": { + "name": "keyword.other.ecl", + "match": "\\b(?i:(after|all|andor|any|as|atmost|before|best|between|case|compressed|compression|const|counter|csv|default|descend|distributed|encoding|end|endmacro|enum|error|except|exclusive|expand|export|exportxml|extend|fail|failcode|few|fileposition|first|flat|for|forward|from|full|group|grouped|hole|if|ifblock|import|inmodule|inner|internal|joined|keep|keyed|last|left|limit|linkcounted|literal|little_endian|load|local|locale|lookup|lzw|mangle|many|maxcount|maxlength|min skew|mofn|multiple|named|namespace|nocase|noroot|noscan|nosort|noxpath|of|onfail|only|opt|option|outer|overwrite|packed|partition|physicallength|pipe|prefetch|repeat|retry|return|right|right1|right2|rows|rowset|scan|scope|self|service|set|shared|skip|smart|soapaction|sql|stable|store|stored|success|thor|token|trim|type|unicodeorder|uniquename|unordered|unsorted|unstable|update|virtual|warning|whole|width|within|wnotrim|xml|xpath|__compressed_))\\b" + }, + "embedded-single": { + "match": "(?i:(embed))\\s*(\\([^\\)]+\\));", + "captures": { + "1": { + "name": "entity.name.function.ecl" + } + } + }, + "embedded-any": { + "begin": "((?i:(embed)))\\s*(\\()", + "end": "(?i:(endembed))", + "beginCaptures": { + "0": { + "name": "entity.name.function.ecl" + } + }, + "endCaptures": { + "0": { + "name": "entity.name.function.ecl" + } + }, + "name": "entity.other.ecl" + }, + "embedded-cpp": { + "begin": "(?i:(beginc))\\+\\+", + "end": "(?i:(endc))\\+\\+", + "beginCaptures": { + "0": { + "name": "entity.name.function.ecl" + } + }, + "endCaptures": { + "0": { + "name": "entity.name.function.ecl" + } + }, + "name": "entity.other.ecl" + }, + "comment": { + "name": "comment.ecl", + "patterns": [ + { + "include": "#comment-block-doc" + }, + { + "include": "#comment-block" + }, + { + "include": "#comment-line" + } + ] + }, + "comment-block": { + "begin": "/\\*", + "end": "\\*/", + "name": "comment.block.ecl" + }, + "comment-block-doc": { + "begin": "/\\*\\*(?!/)", + "end": "\\*/", + "name": "comment.block.documentation.ecl" + }, + "comment-line": { + "match": "(//).*$\\n?", + "name": "comment.line.ecl" + }, + "digital-signature": { + "name": "digital-signature.ecl", + "patterns": [ + { + "include": "#digital-signature-header" + }, + { + "include": "#digital-signature-footer" + } + ] + }, + "digital-signature-header": { + "begin": "-----BEGIN PGP SIGNED MESSAGE-----", + "end": "Hash: SHA512", + "name": "keyword.control.ecl" + }, + "digital-signature-footer": { + "begin": "-----BEGIN PGP SIGNATURE-----", + "end": "-----END PGP SIGNATURE-----", + "name": "keyword.control.ecl" + }, + "string": { + "name": "string.ecl", + "patterns": [ + { + "include": "#qstring-single" + } + ] + }, + "qstring-single": { + "begin": "'", + "end": "\\'|(?:[^\\\\\\n]$)", + "name": "string.single.ecl", + "patterns": [ + { + "include": "#string-character-escape" + } + ] + }, + "string-character-escape": { + "match": "\\\\(x\\h{2}|[0-2][0-7]{,2}|3[0-6][0-7]?|37[0-7]?|[4-7][0-7]?|.|$)", + "name": "constant.character.escape.ecl" + }, + "literal": { + "name": "literal.ecl", + "patterns": [ + { + "include": "#numeric-literal" + }, + { + "include": "#boolean-literal" + }, + { + "include": "#array-literal" + }, + { + "include": "#self-literal" + } + ] + }, + "numeric-literal": { + "match": "\\b(?<=[^$])((0(x|X)[0-9a-fA-F]+)|([0-9a-fA-F]+(x|X))|(0(o|O)[0-7]+)|(0(b|B)(0|1)+)|((0|1)+(b|B))|(([0-9]+(\\.[0-9]+)?))([eE]([+-]?)[0-9]+(\\.[0-9]+)?)?)\\b", + "name": "constant.numeric.ecl" + }, + "boolean-literal": { + "match": "\\b(?i:(false|true))\\b", + "name": "constant.language.boolean.ecl" + }, + "self-literal": { + "match": "\\b(?i:(self))\\b", + "name": "constant.language.this.ecl" + }, + "array-literal": { + "begin": "\\[", + "beginCaptures": { + "0": { + "name": "meta.brace.square.ecl" + } + }, + "end": "\\]", + "endCaptures": { + "0": { + "name": "meta.brace.square.ecl" + } + }, + "name": "meta.array.literal.ecl", + "patterns": [ + { + "include": "#expression" + } + ] + } + } +} \ No newline at end of file diff --git a/packages/markdown-it-plugins/src/ecl-lang/index.ts b/packages/markdown-it-plugins/src/ecl-lang/index.ts new file mode 100644 index 0000000000..514ad9f7de --- /dev/null +++ b/packages/markdown-it-plugins/src/ecl-lang/index.ts @@ -0,0 +1,12 @@ +import type { LanguageInput } from "shiki"; + +import ecl_lang from "./ecl.tmLanguage.json" with { type: "json" }; + +export function eclLang(): LanguageInput { + const retVal = { + ...ecl_lang, + name: "ecl" + } as unknown as LanguageInput; + return retVal; +} + diff --git a/packages/markdown-it-plugins/src/fence.ts b/packages/markdown-it-plugins/src/fence.ts new file mode 100644 index 0000000000..6e78d9ddfc --- /dev/null +++ b/packages/markdown-it-plugins/src/fence.ts @@ -0,0 +1,59 @@ +import type MarkdownIt from "markdown-it"; +import type { Options } from "markdown-it"; +import type Token from "markdown-it/lib/token.mjs"; +import type Renderer from "markdown-it/lib/renderer.mjs"; +import { RenderNode } from "./render.ts"; +import { ENV_KEY, executeSrc, FenceInfo, fenceInfoDefaults, generatePlaceholders, showSrc } from "./util.ts"; + +const deserializeFenceInfo = (attrs: string): FenceInfo => + attrs + .split(/\s+/) + .reduce((acc: FenceInfo, pair, idx: number) => { + if (idx === 0) { + acc.type = pair as "js" | "javascript" | string; + return acc; + } + + const [key, value] = pair.split("=") as [keyof FenceInfo, string]; + switch (key) { + case "exec": + case "echo": + case "hide": + acc[key] = value !== "false"; + break; + } + return acc; + }, { ...fenceInfoDefaults }) + ; + +const proxy = (tokens: Token[], idx: number, options: Options, _env: any, self: Renderer) => self.renderToken(tokens, idx, options); + +export function hookFence(md: MarkdownIt) { + const defaultFenceRenderer = md.renderer.rules.fence || proxy; + const fenceRenderer = (tokens: Token[], idx: number, options: Options, env: { [ENV_KEY]?: RenderNode[] }, self: Renderer) => { + const token = tokens[idx]; + const fenceInfo = deserializeFenceInfo(token.info); + if (fenceInfo.type === "javascript") { + fenceInfo.type = "js"; + } + token.content += "\n"; + let preHtml = ""; + switch (fenceInfo.type) { + case "js": + if (executeSrc(fenceInfo)) { + if (!env[ENV_KEY]) { + env[ENV_KEY] = []; + } + preHtml += generatePlaceholders(token.content, fenceInfo, env); + } + if (showSrc(fenceInfo)) { + return preHtml + defaultFenceRenderer(tokens, idx, options, env, self); + } + break; + default: + return preHtml + defaultFenceRenderer(tokens, idx, options, env, self); + } + return preHtml; + }; + md.renderer.rules.fence = fenceRenderer; +} diff --git a/packages/markdown-it-plugins/src/index.ts b/packages/markdown-it-plugins/src/index.ts new file mode 100644 index 0000000000..c1d72c7e80 --- /dev/null +++ b/packages/markdown-it-plugins/src/index.ts @@ -0,0 +1,47 @@ +import type MarkdownIt from "markdown-it"; +import { render } from "./render.ts"; +import { ENV_KEY } from "./util.ts"; +import { hookTemplateLiterals } from "./template-literal.ts"; +import { hookFence } from "./fence.ts"; + +function hookRender(md: MarkdownIt) { + const originalRender = md.render; + md.render = (src: string, _env?: any): string => { + const env = { ..._env }; + const retVal = originalRender.call(md, src, env); + render(env[ENV_KEY] ?? []); + return retVal; + }; +} + +function hookVitepressRender(md: MarkdownIt) { + + const originalRender = md.render; + md.render = (src: string, env?: any): string => { + const myEnv = env ?? {}; + let retVal = originalRender.call(md, src, myEnv); + if (env[ENV_KEY]?.length) { + const content = encodeURI(JSON.stringify(env[ENV_KEY])); + retVal += ``; + } + return retVal; + }; +} + +export interface ObservableOptions { + vitePress?: boolean; +} + +export function observable(md: MarkdownIt, opts: ObservableOptions = {}) { + hookTemplateLiterals(md); + hookFence(md); + if (opts.vitePress) { + hookVitepressRender(md); + } else { + hookRender(md); + } +} + +export { + render +}; diff --git a/packages/markdown-it-plugins/src/render.ts b/packages/markdown-it-plugins/src/render.ts new file mode 100644 index 0000000000..a41a9dd530 --- /dev/null +++ b/packages/markdown-it-plugins/src/render.ts @@ -0,0 +1,38 @@ +import { Runtime, Library, Inspector } from "@observablehq/runtime"; +import { compile, type ohq } from "@hpcc-js/observablehq-compiler"; +import { FenceInfo, renderExecutedSrc } from "./util.ts"; + +export interface RenderNode extends FenceInfo { + id: string; + content: string; + innerHTML?: string; +} + +export async function render(nodes: RenderNode[]) { + const displayIndex: { [key: string]: boolean } = {}; + const nb: ohq.Notebook = { nodes: [], files: [] }; + for (const node of nodes) { + nb.nodes.push({ + id: node.id, + name: node.id, + mode: "js", + value: node.content + }); + displayIndex[node.id] = renderExecutedSrc(node); + } + const define = await compile(nb); + const runtime = new Runtime(new Library()); + runtime.module(define, () => { + define(runtime, (_name: string | undefined, id: string | number) => { + const placeholder = globalThis?.document?.getElementById("" + id); + if (placeholder && displayIndex[id]) { + return new Inspector(placeholder); + } + return { + pending() { }, + fulfilled(_value: any, _name?: string) { }, + rejected(_error: any, _name?: string) { } + }; + }); + }); +} diff --git a/packages/markdown-it-plugins/src/template-literal.ts b/packages/markdown-it-plugins/src/template-literal.ts new file mode 100644 index 0000000000..28a79753ff --- /dev/null +++ b/packages/markdown-it-plugins/src/template-literal.ts @@ -0,0 +1,84 @@ +import type MarkdownIt from "markdown-it"; +import type { Options } from "markdown-it"; +import type Token from "markdown-it/lib/token.mjs"; +import type Renderer from "markdown-it/lib/renderer.mjs"; +import type StateBlock from "markdown-it/lib/rules_block/state_block.mjs"; +import type StateInline from "markdown-it/lib/rules_inline/state_inline.mjs"; +import { generatePlaceholders } from "./util.ts"; + +function renderObservable(tokens: Token[], idx: number, _options: Options, env: any, _self: Renderer) { + return generatePlaceholders(tokens[idx].content, { type: "js", exec: true }, env); +} + +const DOLLAR = 0x24; +const CURLEY_OPEN = 0x7B; +const CURLEY_CLOSE = 0x7D; + +function parseObservableRef(state: StateInline | StateBlock, stateEx: { pos: number, posMax: number }, silent: boolean) { + + const start = stateEx.pos; + const max = stateEx.posMax; + + if (state.src.charCodeAt(start) !== DOLLAR || state.src.charCodeAt(start + 1) !== CURLEY_OPEN) { + return false; + } + let pos = start + 2; + + const observableStart = pos; + let nestedCurly = 0; + let done = false; + while (!done && pos < max) { + switch (state.src.charCodeAt(pos)) { + case CURLEY_OPEN: + nestedCurly++; + break; + case CURLEY_CLOSE: + if (nestedCurly === 0) { + done = true; + --pos; + } else { + nestedCurly--; + } + break; + } + pos++; + } + const observableEnd = pos; + if (pos >= max || observableStart == observableEnd) return false; + + if (state.src.charCodeAt(pos) !== CURLEY_CLOSE) return false; + + const observableJs = state.src.slice(observableStart, observableEnd).trim(); + + if (!silent) { + const token = state.push("observable", "", 0); + + token.block = true; + token.content = observableJs; + } + + stateEx.pos = pos + 1; + return true; +} + +function parseObservableRefBlock(state: StateBlock, startLine: number, _endLine: number, silent: boolean) { + const start = state.bMarks[startLine] + state.tShift[startLine]; + const max = state.eMarks[startLine]; + const pos = state.src.indexOf("${", start); + if (pos < 0 || pos >= max) return false; + const retVal = parseObservableRef(state, { pos, posMax: max }, silent); + if (retVal) { + state.line = startLine + 1; + } + return retVal; +} + +function parseObservableRefInline(state: StateInline, silent: boolean) { + return parseObservableRef(state, state, silent); +} + +export function hookTemplateLiterals(md: MarkdownIt) { + md.renderer.rules.observable = (tokens: Token[], idx: number, options: any, env: any, self: Renderer) => renderObservable(tokens, idx, options, env, self); + md.block.ruler.before("html_block", "observable_ref", (state: StateBlock, startLine: number, _endLine: number, silent: boolean) => parseObservableRefBlock(state, startLine, _endLine, silent)); + md.inline.ruler.before("text", "observable_ref", (state: StateInline, silent: boolean) => parseObservableRefInline(state, silent)); +} \ No newline at end of file diff --git a/packages/markdown-it-plugins/src/util.ts b/packages/markdown-it-plugins/src/util.ts new file mode 100644 index 0000000000..fe2f269f2f --- /dev/null +++ b/packages/markdown-it-plugins/src/util.ts @@ -0,0 +1,78 @@ +import { ojs2notebook } from "@hpcc-js/observablehq-compiler"; +import { RenderNode } from "./render.ts"; + +export interface FenceInfo { + type: "js" | "javascript" | string; + exec?: boolean; + echo?: boolean; + hide?: boolean; +} + +export const fenceInfoDefaults: Readonly = { + type: "js", + exec: false, + echo: undefined, + hide: undefined +}; + +export function showSrc(fetchInfo: FenceInfo): boolean { + if (fetchInfo.exec === true) { + return fetchInfo.echo === true; + } + return fetchInfo.echo !== false; +} + +export function executeSrc(fenceInfo: FenceInfo): boolean { + return fenceInfo.exec === true; +} + +export function renderExecutedSrc(fetchInfo: FenceInfo): boolean { + return fetchInfo.exec == true && fetchInfo.hide !== true; +} + +let idx = 0; +function calcPlaceholders(content: string, fenceInfo: FenceInfo): RenderNode[] { + const retVal: RenderNode[] = []; + ++idx; + try { + const cellNb = ojs2notebook(content); + for (let i = 0; i < cellNb.nodes.length; ++i) { + const id = `fence-${idx}-${i + 1}`; + const content = cellNb.nodes[i].value; + retVal.push({ + ...fenceInfo, + id, + content, + innerHTML: `\ + + ${renderExecutedSrc(fenceInfo) ? content : ""} +` + }); + } + } catch (e: any) { + const id = `fence-${idx}-error`; + retVal.push({ + ...fenceInfo, + id, + content: JSON.stringify(e), + innerHTML: `\ + + ${content} +` + }); + } + return retVal; +} + +export const ENV_KEY = "ENV_OBSERVABLE"; + +export function generatePlaceholders(content: string, fenceInfo: FenceInfo, env: any): string { + if (!env[ENV_KEY]) { + env[ENV_KEY] = []; + } + return calcPlaceholders(content, fenceInfo).reduce((acc, cur) => { + env[ENV_KEY]!.push({ ...cur, innerHTML: undefined }); + return acc + cur.innerHTML; + }, ""); +} + diff --git a/packages/markdown-it-plugins/src/vitepress/RenderComponent.vue b/packages/markdown-it-plugins/src/vitepress/RenderComponent.vue new file mode 100644 index 0000000000..22cd1ff4c4 --- /dev/null +++ b/packages/markdown-it-plugins/src/vitepress/RenderComponent.vue @@ -0,0 +1,33 @@ + + + + + \ No newline at end of file diff --git a/packages/markdown-it-plugins/src/vitepress/style.css b/packages/markdown-it-plugins/src/vitepress/style.css new file mode 100644 index 0000000000..03a031e33d --- /dev/null +++ b/packages/markdown-it-plugins/src/vitepress/style.css @@ -0,0 +1,149 @@ +/** + * Customize default theme styling by overriding CSS variables: + * https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css + */ + +/** + * Colors + * + * Each colors have exact same color scale system with 3 levels of solid + * colors with different brightness, and 1 soft color. + * + * - `XXX-1`: The most solid color used mainly for colored text. It must + * satisfy the contrast ratio against when used on top of `XXX-soft`. + * + * - `XXX-2`: The color used mainly for hover state of the button. + * + * - `XXX-3`: The color for solid background, such as bg color of the button. + * It must satisfy the contrast ratio with pure white (#ffffff) text on + * top of it. + * + * - `XXX-soft`: The color used for subtle background such as custom container + * or badges. It must satisfy the contrast ratio when putting `XXX-1` colors + * on top of it. + * + * The soft color must be semi transparent alpha channel. This is crucial + * because it allows adding multiple "soft" colors on top of each other + * to create a accent, such as when having inline code block inside + * custom containers. + * + * - `default`: The color used purely for subtle indication without any + * special meanings attched to it such as bg color for menu hover state. + * + * - `brand`: Used for primary brand colors, such as link text, button with + * brand theme, etc. + * + * - `tip`: Used to indicate useful information. The default theme uses the + * brand color for this by default. + * + * - `warning`: Used to indicate warning to the users. Used in custom + * container, badges, etc. + * + * - `danger`: Used to show error, or dangerous message to the users. Used + * in custom container, badges, etc. + * -------------------------------------------------------------------------- */ + + + +:root { + --vp-c-brand-1: #08557F; + --vp-c-brand-2: #347FAA; + --vp-c-brand-3: #61AAD4; + --vp-c-brand-4: #8DD4FF; +} + +:root.dark { + /* + --vp-c-default-1: var(--vp-c-gray-1); + --vp-c-default-2: var(--vp-c-gray-2); + --vp-c-default-3: var(--vp-c-gray-3); + --vp-c-default-soft: var(--vp-c-gray-soft); */ + + --vp-c-brand-0: #8DD4FF; + --vp-c-brand-1: #61AAD4; + --vp-c-brand-2: #347FAA; + --vp-c-brand-3: #08557F; + + /* --vp-c-brand-1: var(--vp-c-indigo-1); + --vp-c-brand-2: var(--vp-c-indigo-2); + --vp-c-brand-3: var(--vp-c-indigo-3); + --vp-c-brand-soft: var(--vp-c-indigo-soft); */ + + /* --vp-c-tip-1: var(--vp-c-brand-1); + --vp-c-tip-2: var(--vp-c-brand-2); + --vp-c-tip-3: var(--vp-c-brand-3); + --vp-c-tip-soft: var(--vp-c-brand-soft); + + --vp-c-warning-1: var(--vp-c-yellow-1); + --vp-c-warning-2: var(--vp-c-yellow-2); + --vp-c-warning-3: var(--vp-c-yellow-3); + --vp-c-warning-soft: var(--vp-c-yellow-soft); + + --vp-c-danger-1: var(--vp-c-red-1); + --vp-c-danger-2: var(--vp-c-red-2); + --vp-c-danger-3: var(--vp-c-red-3); + --vp-c-danger-soft: var(--vp-c-red-soft); */ +} + +/** + * Component: Button + * -------------------------------------------------------------------------- */ + +/* :root { + --vp-button-brand-border: transparent; + --vp-button-brand-text: var(--vp-c-white); + --vp-button-brand-bg: var(--vp-c-brand-3); + --vp-button-brand-hover-border: transparent; + --vp-button-brand-hover-text: var(--vp-c-white); + --vp-button-brand-hover-bg: var(--vp-c-brand-2); + --vp-button-brand-active-border: transparent; + --vp-button-brand-active-text: var(--vp-c-white); + --vp-button-brand-active-bg: var(--vp-c-brand-1); +} */ + +/** + * Component: Home + * -------------------------------------------------------------------------- */ + +:root { + --vp-home-hero-name-color: transparent; + --vp-home-hero-name-background: -webkit-linear-gradient(120deg, + #38af90 30%, + #417fac); + + --vp-home-hero-image-background-image: linear-gradient(-45deg, + #38af90 50%, + #417fac 50%); + --vp-home-hero-image-filter: blur(44px); +} + +@media (min-width: 640px) { + :root { + --vp-home-hero-image-filter: blur(56px); + } +} + +@media (min-width: 960px) { + :root { + --vp-home-hero-image-filter: blur(68px); + } +} + +/** + * Component: Custom Block + * -------------------------------------------------------------------------- */ + +/* :root { + --vp-custom-block-tip-border: transparent; + --vp-custom-block-tip-text: var(--vp-c-text-1); + --vp-custom-block-tip-bg: var(--vp-c-brand-soft); + --vp-custom-block-tip-code-bg: var(--vp-c-brand-soft); +} */ + +/** + * Component: Algolia + * -------------------------------------------------------------------------- */ + +/* .DocSearch { + --docsearch-primary-color: var(--vp-c-brand-1) !important; +} */ \ No newline at end of file diff --git a/packages/markdown-it-plugins/tests/Basic usage/Minimal Example.md b/packages/markdown-it-plugins/tests/Basic usage/Minimal Example.md new file mode 100644 index 0000000000..357d619400 --- /dev/null +++ b/packages/markdown-it-plugins/tests/Basic usage/Minimal Example.md @@ -0,0 +1,22 @@ +The meaning of life is ${ mol1 }, ${mol2 }, ${ mol1}, ${mol2}, ${ mol1 }. + +--- + +

Hello and Welcome - ${mol3}

+ +```js exec echo +mol1 = 40 + 2; +``` + +```js exec echo +mol2 = 39 + 2 + 1; +``` + +```js exec echo +mol3 = 38 + 2 + 2; +``` + +```js exec echo +Plot.rectY({length: 10000}, Plot.binX({y: "count"}, {x: d3.randomNormal()})).plot() +``` + diff --git a/packages/markdown-it-plugins/tests/index.browser.spec.ts b/packages/markdown-it-plugins/tests/index.browser.spec.ts new file mode 100644 index 0000000000..5e2231810b --- /dev/null +++ b/packages/markdown-it-plugins/tests/index.browser.spec.ts @@ -0,0 +1,33 @@ +import { describe, it, expect } from "vitest"; +import markdownit from "markdown-it"; +import { observable } from "@hpcc-js/markdown-it-plugins"; + +import "./style.css"; + +const md = markdownit({ + html: true, + linkify: true, + typographer: true +}); + +describe("markdown-it-observable", () => { + + it("minimal example", async () => { + md.use(observable); + + const minimalMd = fetch("/tests/Basic usage/Minimal Example.md").then(response => { + return response.text(); + }); + const minimalMdText = await minimalMd; + expect(minimalMdText).to.be.a.string; + expect(minimalMdText).to.not.be.empty; + + let minimalHtml = md.render(minimalMdText); + expect(minimalHtml).to.be.a.string; + expect(minimalHtml).to.not.be.empty; + minimalHtml = minimalHtml.trim(); + expect(minimalHtml.charAt(0)).to.equal("<"); + expect(minimalHtml.charAt(minimalHtml.length - 1)).to.equal(">"); + }); +}); + diff --git a/packages/markdown-it-plugins/tests/index.ts b/packages/markdown-it-plugins/tests/index.ts new file mode 100644 index 0000000000..cbd4508f84 --- /dev/null +++ b/packages/markdown-it-plugins/tests/index.ts @@ -0,0 +1,23 @@ +import markdownit from "markdown-it"; +import { observable } from "@hpcc-js/markdown-it-plugins"; + +import "./style.css"; + +const md = markdownit({ + html: true, + linkify: true, + typographer: true +}); + +md.use(observable); + +const minimalMd = fetch("/tests/Basic usage/Minimal Example.md").then(response => { + return response.text(); +}).catch(e => { + console.error("Error: ", e.message ?? e); +}); + +const minimalHtml = md.render("" + await minimalMd); + +document.querySelector("#min")!.innerHTML = minimalHtml; + diff --git a/packages/markdown-it-plugins/tests/style.css b/packages/markdown-it-plugins/tests/style.css new file mode 100644 index 0000000000..4f33eba0ea --- /dev/null +++ b/packages/markdown-it-plugins/tests/style.css @@ -0,0 +1,20 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +body { + margin: 20; + min-width: 320px; + min-height: 100vh; +} \ No newline at end of file diff --git a/packages/markdown-it-plugins/tsconfig.json b/packages/markdown-it-plugins/tsconfig.json new file mode 100644 index 0000000000..79a2741623 --- /dev/null +++ b/packages/markdown-it-plugins/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "rootDir": "src", + "module": "NodeNext", + "target": "ESNext", + "resolveJsonModule": true, + "emitDeclarationOnly": true, + "declaration": true, + "declarationDir": "types", + "strict": true, + "skipLibCheck": true, + "allowImportingTsExtensions": true, + "noImplicitAny": false, + "lib": [ + "DOM", + "ESNext", + "ES2020" + ] + }, + "include": [ + "./src/index.ts", + "./src/ecl-lang/index.ts" + ] +} \ No newline at end of file diff --git a/packages/markdown-it-plugins/vitest.workspace.ts b/packages/markdown-it-plugins/vitest.workspace.ts new file mode 100644 index 0000000000..722950dd03 --- /dev/null +++ b/packages/markdown-it-plugins/vitest.workspace.ts @@ -0,0 +1,6 @@ +import { defineWorkspace } from "vitest/config"; +import baseWorkspace from "../../vitest.workspace.ts"; + +export default defineWorkspace([ + ...baseWorkspace +]); \ No newline at end of file