Compare commits

..

No commits in common. "891c76a003f2528448e99e1faa933a45a15464a4" and "21775454544fdc4294524eac6e266477ce6c206b" have entirely different histories.

17 changed files with 2810 additions and 1226 deletions

View file

@ -1,656 +0,0 @@
{
"name": "carta-md",
"version": "3.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "carta-md",
"version": "3.0.0",
"license": "MIT",
"dependencies": {
"prismjs": "^1.29.0",
"rehype-stringify": "^10.0.0",
"remark-gfm": "^4.0.0",
"remark-parse": "^11.0.0",
"remark-rehype": "^11.1.0",
"shiki": "^1.4.0",
"unified": "^11.0.4"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^3.1.1",
"@sveltejs/kit": "^2.5.4",
"@sveltejs/package": "^2.3.0",
"@sveltejs/vite-plugin-svelte": "^3.0.2",
"@types/prismjs": "^1.26.4",
"svelte-check": "^3.6.7",
"tslib": "^2.4.1",
"typescript": "^5.1.6",
"vite": "^5.1.6"
},
"peerDependencies": {
"svelte": "^3.54.0 || ^4.0.0"
}
},
"../../node_modules/.pnpm/@sveltejs+adapter-auto@3.1.1_@sveltejs+kit@2.5.4/node_modules/@sveltejs/adapter-auto": {
"version": "3.1.1",
"dev": true,
"license": "MIT",
"dependencies": {
"import-meta-resolve": "^4.0.0"
},
"devDependencies": {
"@sveltejs/kit": "^2.4.1",
"@sveltejs/vite-plugin-svelte": "^3.0.1",
"@types/node": "^18.19.3",
"typescript": "^5.3.3"
},
"peerDependencies": {
"@sveltejs/kit": "^2.0.0"
}
},
"../../node_modules/.pnpm/@sveltejs+kit@2.5.4_@sveltejs+vite-plugin-svelte@3.0.2_svelte@4.2.2_vite@5.1.6/node_modules/@sveltejs/kit": {
"version": "2.5.4",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"@types/cookie": "^0.6.0",
"cookie": "^0.6.0",
"devalue": "^4.3.2",
"esm-env": "^1.0.0",
"import-meta-resolve": "^4.0.0",
"kleur": "^4.1.5",
"magic-string": "^0.30.5",
"mrmime": "^2.0.0",
"sade": "^1.8.1",
"set-cookie-parser": "^2.6.0",
"sirv": "^2.0.4",
"tiny-glob": "^0.2.9"
},
"bin": {
"svelte-kit": "svelte-kit.js"
},
"devDependencies": {
"@playwright/test": "^1.41.0",
"@sveltejs/vite-plugin-svelte": "^3.0.1",
"@types/connect": "^3.4.38",
"@types/node": "^18.19.3",
"@types/sade": "^1.7.8",
"@types/set-cookie-parser": "^2.4.7",
"dts-buddy": "^0.4.3",
"rollup": "^4.9.5",
"svelte": "^4.2.10",
"svelte-preprocess": "^5.1.3",
"typescript": "^5.3.3",
"vite": "^5.1.0",
"vitest": "^1.2.0"
},
"engines": {
"node": ">=18.13"
},
"peerDependencies": {
"@sveltejs/vite-plugin-svelte": "^3.0.0",
"svelte": "^4.0.0 || ^5.0.0-next.0",
"vite": "^5.0.3"
}
},
"../../node_modules/.pnpm/@sveltejs+package@2.3.0_svelte@4.2.2_typescript@5.1.6/node_modules/@sveltejs/package": {
"version": "2.3.0",
"dev": true,
"license": "MIT",
"dependencies": {
"chokidar": "^3.6.0",
"kleur": "^4.1.5",
"sade": "^1.8.1",
"semver": "^7.5.4",
"svelte2tsx": "~0.7.0"
},
"bin": {
"svelte-package": "svelte-package.js"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^3.0.1",
"@types/node": "^18.19.3",
"@types/semver": "^7.5.6",
"svelte": "^4.2.10",
"svelte-preprocess": "^5.1.3",
"typescript": "^5.3.3",
"uvu": "^0.5.6"
},
"engines": {
"node": "^16.14 || >=18"
},
"peerDependencies": {
"svelte": "^3.44.0 || ^4.0.0 || ^5.0.0-next.1"
}
},
"../../node_modules/.pnpm/@sveltejs+vite-plugin-svelte@3.0.2_svelte@4.2.2_vite@5.1.6/node_modules/@sveltejs/vite-plugin-svelte": {
"version": "3.0.2",
"dev": true,
"license": "MIT",
"dependencies": {
"@sveltejs/vite-plugin-svelte-inspector": "^2.0.0",
"debug": "^4.3.4",
"deepmerge": "^4.3.1",
"kleur": "^4.1.5",
"magic-string": "^0.30.5",
"svelte-hmr": "^0.15.3",
"vitefu": "^0.2.5"
},
"devDependencies": {
"@types/debug": "^4.1.12",
"esbuild": "^0.19.12",
"sass": "^1.70.0",
"svelte": "^4.2.9",
"vite": "^5.0.11"
},
"engines": {
"node": "^18.0.0 || >=20"
},
"peerDependencies": {
"svelte": "^4.0.0 || ^5.0.0-next.0",
"vite": "^5.0.0"
}
},
"../../node_modules/.pnpm/prismjs@1.29.0/node_modules/prismjs": {
"version": "1.29.0",
"license": "MIT",
"devDependencies": {
"@types/node-fetch": "^2.5.5",
"benchmark": "^2.1.4",
"chai": "^4.2.0",
"danger": "^10.5.0",
"del": "^4.1.1",
"docdash": "^1.2.0",
"eslint": "^7.22.0",
"eslint-plugin-jsdoc": "^32.3.0",
"eslint-plugin-regexp": "^1.6.0",
"gulp": "^4.0.2",
"gulp-clean-css": "^4.3.0",
"gulp-concat": "^2.3.4",
"gulp-header": "^2.0.7",
"gulp-jsdoc3": "^3.0.0",
"gulp-rename": "^1.2.0",
"gulp-replace": "^1.0.0",
"gulp-terser": "^2.1.0",
"gzip-size": "^5.1.1",
"htmlparser2": "^4.0.0",
"http-server": "^0.12.3",
"jsdom": "^16.7.0",
"mocha": "^9.2.2",
"node-fetch": "^3.1.1",
"npm-run-all": "^4.1.5",
"prettier": "^2.4.1",
"pump": "^3.0.0",
"refa": "^0.9.1",
"regexp-ast-analysis": "^0.2.4",
"regexpp": "^3.2.0",
"scslre": "^0.1.6",
"simple-git": "^3.3.0",
"webfont": "^9.0.0",
"yargs": "^13.2.2"
},
"engines": {
"node": ">=6"
}
},
"../../node_modules/.pnpm/rehype-stringify@10.0.0/node_modules/rehype-stringify": {
"version": "10.0.0",
"license": "MIT",
"dependencies": {
"@types/hast": "^3.0.0",
"hast-util-to-html": "^9.0.0",
"unified": "^11.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"../../node_modules/.pnpm/remark-gfm@4.0.0/node_modules/remark-gfm": {
"version": "4.0.0",
"license": "MIT",
"dependencies": {
"@types/mdast": "^4.0.0",
"mdast-util-gfm": "^3.0.0",
"micromark-extension-gfm": "^3.0.0",
"remark-parse": "^11.0.0",
"remark-stringify": "^11.0.0",
"unified": "^11.0.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
"c8": "^8.0.0",
"is-hidden": "^2.0.0",
"prettier": "^3.0.0",
"remark": "^15.0.0",
"remark-cli": "^11.0.0",
"remark-preset-wooorm": "^9.0.0",
"string-width": "^6.0.0",
"to-vfile": "^8.0.0",
"type-coverage": "^2.0.0",
"typescript": "^5.0.0",
"xo": "^0.56.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"../../node_modules/.pnpm/remark-parse@11.0.0/node_modules/remark-parse": {
"version": "11.0.0",
"license": "MIT",
"dependencies": {
"@types/mdast": "^4.0.0",
"mdast-util-from-markdown": "^2.0.0",
"micromark-util-types": "^2.0.0",
"unified": "^11.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"../../node_modules/.pnpm/remark-rehype@11.1.0/node_modules/remark-rehype": {
"version": "11.1.0",
"license": "MIT",
"dependencies": {
"@types/hast": "^3.0.0",
"@types/mdast": "^4.0.0",
"mdast-util-to-hast": "^13.0.0",
"unified": "^11.0.0",
"vfile": "^6.0.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
"c8": "^9.0.0",
"prettier": "^3.0.0",
"rehype-stringify": "^10.0.0",
"remark-cli": "^11.0.0",
"remark-parse": "^11.0.0",
"remark-preset-wooorm": "^9.0.0",
"remark-stringify": "^11.0.0",
"type-coverage": "^2.0.0",
"typescript": "^5.0.0",
"xo": "^0.56.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"../../node_modules/.pnpm/shiki@1.4.0/node_modules/shiki": {
"version": "1.4.0",
"license": "MIT",
"dependencies": {
"@shikijs/core": "1.4.0"
},
"devDependencies": {
"tm-grammars": "^1.7.2",
"tm-themes": "^1.4.1",
"vscode-oniguruma": "^1.7.0"
}
},
"../../node_modules/.pnpm/svelte-check@3.6.7_postcss@8.4.31_svelte@4.2.2/node_modules/svelte-check": {
"version": "3.6.7",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.17",
"chokidar": "^3.4.1",
"fast-glob": "^3.2.7",
"import-fresh": "^3.2.1",
"picocolors": "^1.0.0",
"sade": "^1.7.4",
"svelte-preprocess": "^5.1.3",
"typescript": "^5.0.3"
},
"bin": {
"svelte-check": "bin/svelte-check"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^24.0.0",
"@rollup/plugin-json": "^6.0.0",
"@rollup/plugin-node-resolve": "^15.0.0",
"@rollup/plugin-replace": "5.0.2",
"@rollup/plugin-typescript": "^10.0.0",
"@types/sade": "^1.7.2",
"builtin-modules": "^3.3.0",
"rollup": "3.7.5",
"rollup-plugin-cleanup": "^3.2.0",
"rollup-plugin-copy": "^3.4.0",
"svelte-language-server": "0.16.0",
"vscode-languageserver": "8.0.2",
"vscode-languageserver-protocol": "3.17.2",
"vscode-languageserver-types": "3.17.2",
"vscode-uri": "~3.0.0"
},
"peerDependencies": {
"svelte": "^3.55.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0"
}
},
"../../node_modules/.pnpm/svelte@4.2.2/node_modules/svelte": {
"version": "4.2.2",
"license": "MIT",
"peer": true,
"dependencies": {
"@ampproject/remapping": "^2.2.1",
"@jridgewell/sourcemap-codec": "^1.4.15",
"@jridgewell/trace-mapping": "^0.3.18",
"acorn": "^8.9.0",
"aria-query": "^5.3.0",
"axobject-query": "^3.2.1",
"code-red": "^1.0.3",
"css-tree": "^2.3.1",
"estree-walker": "^3.0.3",
"is-reference": "^3.0.1",
"locate-character": "^3.0.0",
"magic-string": "^0.30.4",
"periscopic": "^3.1.0"
},
"devDependencies": {
"@playwright/test": "^1.35.1",
"@rollup/plugin-commonjs": "^24.1.0",
"@rollup/plugin-json": "^6.0.0",
"@rollup/plugin-node-resolve": "^15.1.0",
"@sveltejs/eslint-config": "^6.0.4",
"@types/aria-query": "^5.0.1",
"@types/estree": "^1.0.1",
"@types/node": "^14.18.51",
"agadoo": "^3.0.0",
"dts-buddy": "^0.1.7",
"esbuild": "^0.18.11",
"eslint-plugin-lube": "^0.1.7",
"happy-dom": "^9.20.3",
"jsdom": "22.0.0",
"kleur": "^4.1.5",
"rollup": "^3.26.2",
"source-map": "^0.7.4",
"tiny-glob": "^0.2.9",
"typescript": "^5.1.3",
"vitest": "^0.33.0"
},
"engines": {
"node": ">=16"
}
},
"../../node_modules/.pnpm/tslib@2.5.0/node_modules/tslib": {
"version": "2.5.0",
"dev": true,
"license": "0BSD"
},
"../../node_modules/.pnpm/typescript@5.1.6/node_modules/typescript": {
"version": "5.1.6",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"devDependencies": {
"@esfx/canceltoken": "^1.0.0",
"@octokit/rest": "latest",
"@types/chai": "^4.3.4",
"@types/fs-extra": "^9.0.13",
"@types/glob": "^8.1.0",
"@types/microsoft__typescript-etw": "^0.1.1",
"@types/minimist": "^1.2.2",
"@types/mocha": "^10.0.1",
"@types/ms": "^0.7.31",
"@types/node": "latest",
"@types/source-map-support": "^0.5.6",
"@types/which": "^2.0.1",
"@typescript-eslint/eslint-plugin": "^5.33.1",
"@typescript-eslint/parser": "^5.33.1",
"@typescript-eslint/utils": "^5.33.1",
"azure-devops-node-api": "^12.0.0",
"chai": "^4.3.7",
"chalk": "^4.1.2",
"chokidar": "^3.5.3",
"del": "^6.1.1",
"diff": "^5.1.0",
"esbuild": "^0.17.2",
"eslint": "^8.22.0",
"eslint-formatter-autolinkable-stylish": "^1.2.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-local": "^1.0.0",
"eslint-plugin-no-null": "^1.0.2",
"eslint-plugin-simple-import-sort": "^10.0.0",
"fast-xml-parser": "^4.0.11",
"fs-extra": "^9.1.0",
"glob": "^8.1.0",
"hereby": "^1.6.4",
"jsonc-parser": "^3.2.0",
"minimist": "^1.2.8",
"mocha": "^10.2.0",
"mocha-fivemat-progress-reporter": "^0.1.0",
"ms": "^2.1.3",
"node-fetch": "^3.2.10",
"source-map-support": "^0.5.21",
"tslib": "^2.5.0",
"typescript": "^5.0.2",
"which": "^2.0.2"
},
"engines": {
"node": ">=14.17"
}
},
"../../node_modules/.pnpm/unified@11.0.4/node_modules/unified": {
"version": "11.0.4",
"license": "MIT",
"dependencies": {
"@types/unist": "^3.0.0",
"bail": "^2.0.0",
"devlop": "^1.0.0",
"extend": "^3.0.0",
"is-plain-obj": "^4.0.0",
"trough": "^2.0.0",
"vfile": "^6.0.0"
},
"devDependencies": {
"@types/extend": "^3.0.0",
"@types/hast": "^3.0.0",
"@types/mdast": "^4.0.0",
"@types/node": "^20.0.0",
"c8": "^8.0.0",
"prettier": "^3.0.0",
"remark-cli": "^11.0.0",
"remark-preset-wooorm": "^9.0.0",
"tsd": "^0.29.0",
"type-coverage": "^2.0.0",
"typescript": "^5.0.0",
"xo": "^0.56.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"../../node_modules/.pnpm/vite@5.1.6_@types+node@18.16.3_sass@1.69.5/node_modules/vite": {
"version": "5.1.6",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "^0.19.3",
"postcss": "^8.4.35",
"rollup": "^4.2.0"
},
"bin": {
"vite": "bin/vite.js"
},
"devDependencies": {
"@ampproject/remapping": "^2.3.0",
"@babel/parser": "^7.24.0",
"@jridgewell/trace-mapping": "^0.3.25",
"@rollup/plugin-alias": "^5.1.0",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-dynamic-import-vars": "^2.1.2",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "15.2.3",
"@rollup/plugin-typescript": "^11.1.6",
"@rollup/pluginutils": "^5.1.0",
"@types/escape-html": "^1.0.4",
"@types/pnpapi": "^0.0.5",
"acorn": "^8.11.3",
"acorn-walk": "^8.3.2",
"artichokie": "^0.2.0",
"cac": "^6.7.14",
"chokidar": "^3.6.0",
"connect": "^3.7.0",
"convert-source-map": "^2.0.0",
"cors": "^2.8.5",
"cross-spawn": "^7.0.3",
"debug": "^4.3.4",
"dep-types": "link:./src/types",
"dotenv": "^16.4.5",
"dotenv-expand": "^11.0.6",
"es-module-lexer": "^1.4.1",
"escape-html": "^1.0.3",
"estree-walker": "^3.0.3",
"etag": "^1.8.1",
"fast-glob": "^3.3.2",
"http-proxy": "^1.18.1",
"launch-editor-middleware": "^2.6.1",
"lightningcss": "^1.24.0",
"magic-string": "^0.30.8",
"micromatch": "^4.0.5",
"mlly": "^1.6.1",
"mrmime": "^2.0.0",
"open": "^8.4.2",
"parse5": "^7.1.2",
"pathe": "^1.1.2",
"periscopic": "^4.0.2",
"picocolors": "^1.0.0",
"picomatch": "^2.3.1",
"postcss-import": "^16.0.1",
"postcss-load-config": "^4.0.2",
"postcss-modules": "^6.0.0",
"resolve.exports": "^2.0.2",
"rollup-plugin-dts": "^6.1.0",
"rollup-plugin-esbuild": "^6.1.1",
"rollup-plugin-license": "^3.3.1",
"sirv": "^2.0.4",
"source-map-support": "^0.5.21",
"strip-ansi": "^7.1.0",
"strip-literal": "^2.0.0",
"tsconfck": "^3.0.3",
"tslib": "^2.6.2",
"types": "link:./types",
"ufo": "^1.4.0",
"ws": "^8.16.0"
},
"engines": {
"node": "^18.0.0 || >=20.0.0"
},
"funding": {
"url": "https://github.com/vitejs/vite?sponsor=1"
},
"optionalDependencies": {
"fsevents": "~2.3.3"
},
"peerDependencies": {
"@types/node": "^18.0.0 || >=20.0.0",
"less": "*",
"lightningcss": "^1.21.0",
"sass": "*",
"stylus": "*",
"sugarss": "*",
"terser": "^5.4.0"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
},
"less": {
"optional": true
},
"lightningcss": {
"optional": true
},
"sass": {
"optional": true
},
"stylus": {
"optional": true
},
"sugarss": {
"optional": true
},
"terser": {
"optional": true
}
}
},
"node_modules/@sveltejs/adapter-auto": {
"resolved": "../../node_modules/.pnpm/@sveltejs+adapter-auto@3.1.1_@sveltejs+kit@2.5.4/node_modules/@sveltejs/adapter-auto",
"link": true
},
"node_modules/@sveltejs/kit": {
"resolved": "../../node_modules/.pnpm/@sveltejs+kit@2.5.4_@sveltejs+vite-plugin-svelte@3.0.2_svelte@4.2.2_vite@5.1.6/node_modules/@sveltejs/kit",
"link": true
},
"node_modules/@sveltejs/package": {
"resolved": "../../node_modules/.pnpm/@sveltejs+package@2.3.0_svelte@4.2.2_typescript@5.1.6/node_modules/@sveltejs/package",
"link": true
},
"node_modules/@sveltejs/vite-plugin-svelte": {
"resolved": "../../node_modules/.pnpm/@sveltejs+vite-plugin-svelte@3.0.2_svelte@4.2.2_vite@5.1.6/node_modules/@sveltejs/vite-plugin-svelte",
"link": true
},
"node_modules/@types/prismjs": {
"version": "1.26.4",
"resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.4.tgz",
"integrity": "sha512-rlAnzkW2sZOjbqZ743IHUhFcvzaGbqijwOu8QZnZCjfQzBqFE3s4lOTJEsxikImav9uzz/42I+O7YUs1mWgMlg==",
"dev": true
},
"node_modules/prismjs": {
"resolved": "../../node_modules/.pnpm/prismjs@1.29.0/node_modules/prismjs",
"link": true
},
"node_modules/rehype-stringify": {
"resolved": "../../node_modules/.pnpm/rehype-stringify@10.0.0/node_modules/rehype-stringify",
"link": true
},
"node_modules/remark-gfm": {
"resolved": "../../node_modules/.pnpm/remark-gfm@4.0.0/node_modules/remark-gfm",
"link": true
},
"node_modules/remark-parse": {
"resolved": "../../node_modules/.pnpm/remark-parse@11.0.0/node_modules/remark-parse",
"link": true
},
"node_modules/remark-rehype": {
"resolved": "../../node_modules/.pnpm/remark-rehype@11.1.0/node_modules/remark-rehype",
"link": true
},
"node_modules/shiki": {
"resolved": "../../node_modules/.pnpm/shiki@1.4.0/node_modules/shiki",
"link": true
},
"node_modules/svelte": {
"resolved": "../../node_modules/.pnpm/svelte@4.2.2/node_modules/svelte",
"link": true
},
"node_modules/svelte-check": {
"resolved": "../../node_modules/.pnpm/svelte-check@3.6.7_postcss@8.4.31_svelte@4.2.2/node_modules/svelte-check",
"link": true
},
"node_modules/tslib": {
"resolved": "../../node_modules/.pnpm/tslib@2.5.0/node_modules/tslib",
"link": true
},
"node_modules/typescript": {
"resolved": "../../node_modules/.pnpm/typescript@5.1.6/node_modules/typescript",
"link": true
},
"node_modules/unified": {
"resolved": "../../node_modules/.pnpm/unified@11.0.4/node_modules/unified",
"link": true
},
"node_modules/vite": {
"resolved": "../../node_modules/.pnpm/vite@5.1.6_@types+node@18.16.3_sass@1.69.5/node_modules/vite",
"link": true
}
}
}

View file

@ -16,7 +16,7 @@
},
"./default.css": "./dist/default.css"
},
"version": "1.0.0",
"version": "3.0.0",
"scripts": {
"dev": "vite dev",
"build": "svelte-kit sync && svelte-package",
@ -28,7 +28,6 @@
"@sveltejs/kit": "^2.5.4",
"@sveltejs/package": "^2.3.0",
"@sveltejs/vite-plugin-svelte": "^3.0.2",
"@types/prismjs": "^1.26.4",
"svelte-check": "^3.6.7",
"tslib": "^2.4.1",
"typescript": "^5.1.6",
@ -36,11 +35,11 @@
},
"type": "module",
"dependencies": {
"prismjs": "^1.29.0",
"rehype-stringify": "^10.0.0",
"remark-gfm": "^4.0.0",
"remark-parse": "^11.0.0",
"remark-rehype": "^11.1.0",
"shiki": "^1.4.0",
"unified": "^11.0.4"
},
"peerDependencies": {

View file

@ -1,22 +1,30 @@
<script lang="ts">
import { onMount } from 'svelte';
import { type Carta } from '.';
import { loadNestedLanguages, type Carta } from '.';
export let carta: Carta;
export let value: string;
export let theme = 'default';
export let renderCls = 'prose';
let elem: HTMLDivElement;
let mounted = false;
$: rendered = carta.renderSSR(value);
let rendered = carta.renderSSR(value);
onMount(async () => {
carta.$setRenderer(elem);
// Load highlighting languages
const highlighter = await carta.highlighter();
await loadNestedLanguages(highlighter, value);
// Render using asynchronous renderer
rendered = await carta.render(value);
mounted = true;
});
</script>
<div bind:this={elem} class="carta-viewer carta-theme__{theme} {renderCls}">
<div bind:this={elem} class="carta-viewer carta-theme__{theme} markdown-body">
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html rendered}
{#if mounted}

View file

@ -15,7 +15,6 @@
export let scroll: 'sync' | 'async' = 'sync';
export let disableToolbar = false;
export let placeholder = '';
export let renderCls = 'prose';
export let textarea: TextAreaProps = {};
let userLabels: Partial<Labels> = {};
@ -125,7 +124,7 @@
</Input>
{/if}
{#if windowMode == 'split' || selectedTab == 'preview'}
<Renderer {carta} {handleScroll} bind:value bind:elem={rendererElem} {renderCls}>
<Renderer {carta} {handleScroll} bind:value bind:elem={rendererElem}>
<!-- Renderer extensions components -->
{#if mounted}
{#each carta.components.filter(({ parent }) => [parent]

View file

@ -1,88 +0,0 @@
.carta-highlight {
color: #333333;
--hl-dark: #f8f8f2;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata,
.token.punctuation {
color: #6a737d;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.url {
color: #2396e3;
--hl-dark: #71d58a;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.url > .token.content {
color: #5abd60;
--hl-dark: #4dacfa;
}
.token.entity {
color: #6f42c1;
--hl-dark: #b392f0;
}
.token.atrule,
.token.attr-value,
.token.keyword,
.token.class-name,
.token.function,
.token.italic,
.token.deleted,
.token.list {
color: #e16;
--hl-dark: #ff7cc6;
}
.token.inserted {
color: #5abd60;
--hl-dark: #71d58a;
}
.token.regex,
.token.variable,
.token.bold {
color: #f60;
--hl-dark: #b581fd;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.strike {
text-decoration: line-through;
color: #f44;
--hl-dark: #ff5261;
}
.token.title {
font-weight: bold;
color: #212121;
--hl-dark: #e8e8e8;
}
.token.blockquote {
color: #999;
--hl-dark: #7d828b
}
.token.code-snippet {
color: #2396e3;
--hl-dark: #4dacfa;
}

View file

@ -5,6 +5,7 @@ export type { Icon } from '$lib/internal/icons';
export type { KeyboardShortcut } from '$lib/internal/shortcuts';
export type { Prefix } from '$lib/internal/prefixes';
export * from '$lib/internal/carta';
export * from '$lib/internal/highlight';
export * from '$lib/internal/textarea-props';
export * from '$lib/internal/labels';
export * from './default.css?inline';

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,330 @@
import { type ThemeInput } from 'shiki';
const theme = {
displayName: 'Carta Dark' as const,
name: 'carta-dark' as const,
semanticHighlighting: true,
fg: '#f8f8f2',
bg: 'transparent',
tokenColors: [
{
scope: ['comment', 'punctuation.definition.comment', 'string.comment'],
settings: {
foreground: '#6a737d'
}
},
{
scope: ['variable.other.constant', 'variable.other.enummember', 'variable.language'],
settings: {
foreground: '#fff'
}
},
{
scope: ['constant', 'entity.name.constant'],
settings: {
foreground: '#71d58a'
}
},
{
scope: ['entity', 'entity.name'],
settings: {
foreground: '#b392f0'
}
},
{
scope: 'variable.parameter.function',
settings: {
foreground: '#e1e4e8'
}
},
{
scope: 'entity.name.tag',
settings: {
foreground: '#85e89d'
}
},
{
scope: ['keyword', 'punctuation.definition.template-expression'],
settings: {
foreground: '#ff7cc6'
}
},
{
scope: ['storage', 'storage.type'],
settings: {
foreground: '#ff7cc6'
}
},
{
scope: ['storage.modifier.package', 'storage.modifier.import', 'storage.type.java'],
settings: {
foreground: '#e1e4e8'
}
},
{
scope: [
'string',
'punctuation.definition.string',
'string punctuation.section.embedded source'
],
settings: {
foreground: '#4dacfa'
}
},
{
scope: 'support',
settings: {
foreground: '#71d58a'
}
},
{
scope: 'meta.property-name',
settings: {
foreground: '#71d58a'
}
},
{
scope: 'variable',
settings: {
foreground: '#b581fd'
}
},
{
scope: 'variable.other',
settings: {
foreground: '#e1e4e8'
}
},
{
scope: 'invalid.broken',
settings: {
fontStyle: 'italic',
foreground: '#fdaeb7'
}
},
{
scope: 'invalid.deprecated',
settings: {
fontStyle: 'italic',
foreground: '#fdaeb7'
}
},
{
scope: 'invalid.illegal',
settings: {
fontStyle: 'italic',
foreground: '#fdaeb7'
}
},
{
scope: 'invalid.unimplemented',
settings: {
fontStyle: 'italic',
foreground: '#fdaeb7'
}
},
{
scope: 'carriage-return',
settings: {
background: '#ff7cc6',
fontStyle: 'italic underline',
foreground: '#24292e'
}
},
{
scope: 'message.error',
settings: {
foreground: '#fdaeb7'
}
},
{
scope: 'string variable',
settings: {
foreground: '#71d58a'
}
},
{
scope: ['source.regexp', 'string.regexp'],
settings: {
foreground: '#4dacfa'
}
},
{
scope: [
'string.regexp.character-class',
'string.regexp constant.character.escape',
'string.regexp source.ruby.embedded',
'string.regexp string.regexp.arbitrary-repitition'
],
settings: {
foreground: '#4dacfa'
}
},
{
scope: 'string.regexp constant.character.escape',
settings: {
fontStyle: 'bold',
foreground: '#85e89d'
}
},
{
scope: 'support.constant',
settings: {
foreground: '#71d58a'
}
},
{
scope: 'support.variable',
settings: {
foreground: '#71d58a'
}
},
{
scope: 'meta.module-reference',
settings: {
foreground: '#71d58a'
}
},
{
scope: 'punctuation.definition.list.begin.markdown',
settings: {
foreground: '#ff7cc6'
}
},
{
scope: ['markup.heading', 'markup.heading entity.name'],
settings: {
fontStyle: 'bold',
foreground: '#e8e8e8'
}
},
{
scope: 'markup.quote',
settings: {
foreground: '#7d828b'
}
},
{
scope: 'markup.italic',
settings: {
fontStyle: 'italic',
foreground: '#ff7cc6'
}
},
{
scope: 'markup.bold',
settings: {
foreground: '#b581fd'
}
},
{
scope: ['markup.underline'],
settings: {
foreground: '#71d58a',
fontStyle: 'underline'
}
},
{
scope: ['markup.strikethrough'],
settings: {
foreground: '#ff5261',
fontStyle: 'strikethrough'
}
},
{
scope: 'markup.inline.raw',
settings: {
foreground: '#4dacfa'
}
},
{
scope: ['markup.deleted', 'meta.diff.header.from-file', 'punctuation.definition.deleted'],
settings: {
background: '#86181d',
foreground: '#fdaeb7'
}
},
{
scope: ['markup.inserted', 'meta.diff.header.to-file', 'punctuation.definition.inserted'],
settings: {
background: '#144620',
foreground: '#85e89d'
}
},
{
scope: ['markup.changed', 'punctuation.definition.changed'],
settings: {
background: '#c24e00',
foreground: '#b581fd'
}
},
{
scope: ['markup.ignored', 'markup.untracked'],
settings: {
background: '#71d58a',
foreground: '#2f363d'
}
},
{
scope: 'meta.diff.range',
settings: {
fontStyle: 'bold',
foreground: '#b392f0'
}
},
{
scope: 'meta.diff.header',
settings: {
foreground: '#71d58a'
}
},
{
scope: 'meta.separator',
settings: {
fontStyle: 'bold',
foreground: '#71d58a'
}
},
{
scope: 'meta.output',
settings: {
foreground: '#71d58a'
}
},
{
scope: [
'brackethighlighter.tag',
'brackethighlighter.curly',
'brackethighlighter.round',
'brackethighlighter.square',
'brackethighlighter.angle',
'brackethighlighter.quote'
],
settings: {
foreground: '#d1d5da'
}
},
{
scope: 'brackethighlighter.unmatched',
settings: {
foreground: '#fdaeb7'
}
},
{
scope: ['constant.other.reference.link', 'string.other.link'],
settings: {
fontStyle: 'underline',
foreground: '#4dacfa'
}
},
{
scope: ['punctuation.definition.markdown', 'fenced_code.block.language'],
settings: {
foreground: '#ff7cc6'
}
}
],
type: 'light'
} satisfies ThemeInput;
export default theme;

View file

@ -0,0 +1,329 @@
import { type ThemeInput } from 'shiki';
const theme = {
displayName: 'Carta Light' as const,
name: 'carta-light' as const,
semanticHighlighting: true,
fg: '#333333',
bg: 'transparent',
tokenColors: [
{
scope: ['comment', 'punctuation.definition.comment', 'string.comment'],
settings: {
foreground: '#6a737d'
}
},
{
scope: ['variable.other.constant', 'variable.other.enummember', 'variable.language'],
settings: {
foreground: '#000'
}
},
{
scope: ['constant', 'entity.name.constant'],
settings: {
foreground: '#3bf'
}
},
{
scope: ['entity', 'entity.name'],
settings: {
foreground: '#6f42c1'
}
},
{
scope: 'variable.parameter.function',
settings: {
foreground: '#24292e'
}
},
{
scope: 'entity.name.tag',
settings: {
foreground: '#22863a'
}
},
{
scope: ['keyword', 'punctuation.definition.template-expression'],
settings: {
foreground: '#e16'
}
},
{
scope: ['storage', 'storage.type'],
settings: {
foreground: '#e16'
}
},
{
scope: ['storage.modifier.package', 'storage.modifier.import', 'storage.type.java'],
settings: {
foreground: '#24292e'
}
},
{
scope: [
'string',
'punctuation.definition.string',
'string punctuation.section.embedded source'
],
settings: {
foreground: '#7d8'
}
},
{
scope: 'support',
settings: {
foreground: '#3bf'
}
},
{
scope: 'meta.property-name',
settings: {
foreground: '#3bf'
}
},
{
scope: 'variable',
settings: {
foreground: '#f60'
}
},
{
scope: 'variable.other',
settings: {
foreground: '#24292e'
}
},
{
scope: 'invalid.broken',
settings: {
fontStyle: 'italic',
foreground: '#b31d28'
}
},
{
scope: 'invalid.deprecated',
settings: {
fontStyle: 'italic',
foreground: '#b31d28'
}
},
{
scope: 'invalid.illegal',
settings: {
fontStyle: 'italic',
foreground: '#b31d28'
}
},
{
scope: 'invalid.unimplemented',
settings: {
fontStyle: 'italic',
foreground: '#b31d28'
}
},
{
scope: 'carriage-return',
settings: {
background: '#e16',
fontStyle: 'italic underline',
foreground: '#fafbfc'
}
},
{
scope: 'message.error',
settings: {
foreground: '#b31d28'
}
},
{
scope: 'string variable',
settings: {
foreground: '#3bf'
}
},
{
scope: ['source.regexp', 'string.regexp'],
settings: {
foreground: '#7d8'
}
},
{
scope: [
'string.regexp.character-class',
'string.regexp constant.character.escape',
'string.regexp source.ruby.embedded',
'string.regexp string.regexp.arbitrary-repitition'
],
settings: {
foreground: '#7d8'
}
},
{
scope: 'string.regexp constant.character.escape',
settings: {
fontStyle: 'bold',
foreground: '#22863a'
}
},
{
scope: 'support.constant',
settings: {
foreground: '#3bf'
}
},
{
scope: 'support.variable',
settings: {
foreground: '#3bf'
}
},
{
scope: 'meta.module-reference',
settings: {
foreground: '#3bf'
}
},
{
scope: 'punctuation.definition.list.begin.markdown',
settings: {
foreground: '#e16'
}
},
{
scope: ['markup.heading', 'markup.heading entity.name'],
settings: {
fontStyle: 'bold',
foreground: '#212121'
}
},
{
scope: 'markup.quote',
settings: {
foreground: '#999'
}
},
{
scope: 'markup.italic',
settings: {
foreground: '#e16'
}
},
{
scope: 'markup.bold',
settings: {
foreground: '#f60'
}
},
{
scope: ['markup.underline'],
settings: {
foreground: '#84f',
fontStyle: 'underline'
}
},
{
scope: ['markup.strikethrough'],
settings: {
foreground: '#f44',
fontStyle: 'strikethrough'
}
},
{
scope: 'markup.inline.raw',
settings: {
foreground: '#5af'
}
},
{
scope: ['markup.deleted', 'meta.diff.header.from-file', 'punctuation.definition.deleted'],
settings: {
background: '#ffeef0',
foreground: '#b31d28'
}
},
{
scope: ['markup.inserted', 'meta.diff.header.to-file', 'punctuation.definition.inserted'],
settings: {
background: '#f0fff4',
foreground: '#22863a'
}
},
{
scope: ['markup.changed', 'punctuation.definition.changed'],
settings: {
background: '#ffebda',
foreground: '#f60'
}
},
{
scope: ['markup.ignored', 'markup.untracked'],
settings: {
background: '#3bf',
foreground: '#f6f8fa'
}
},
{
scope: 'meta.diff.range',
settings: {
fontStyle: 'bold',
foreground: '#6f42c1'
}
},
{
scope: 'meta.diff.header',
settings: {
foreground: '#3bf'
}
},
{
scope: 'meta.separator',
settings: {
fontStyle: 'bold',
foreground: '#3bf'
}
},
{
scope: 'meta.output',
settings: {
foreground: '#3bf'
}
},
{
scope: [
'brackethighlighter.tag',
'brackethighlighter.curly',
'brackethighlighter.round',
'brackethighlighter.square',
'brackethighlighter.angle',
'brackethighlighter.quote'
],
settings: {
foreground: '#586069'
}
},
{
scope: 'brackethighlighter.unmatched',
settings: {
foreground: '#b31d28'
}
},
{
scope: ['constant.other.reference.link', 'string.other.link'],
settings: {
fontStyle: 'underline',
foreground: '#5af'
}
},
{
scope: ['punctuation.definition.markdown', 'fenced_code.block.language'],
settings: {
foreground: '#e16'
}
}
],
type: 'light'
} satisfies ThemeInput;
export default theme;

View file

@ -15,6 +15,16 @@ import { defaultIcons, type Icon, type DefaultIconId } from './icons';
import { defaultPrefixes, type DefaultPrefixId, type Prefix } from './prefixes';
import { Renderer } from './renderer';
import { CustomEvent, type MaybeArray } from './utils';
import {
loadHighlighter,
loadDefaultTheme,
type Highlighter,
type GrammarRule,
type ShikiOptions,
type DualTheme,
type Theme,
type HighlightingRule
} from './highlight';
/**
* Carta-specific event with extra payload.
@ -101,12 +111,12 @@ export interface Options {
/**
* Highlighter options.
*/
// shikiOptions?: ShikiOptions;
shikiOptions?: ShikiOptions;
/**
* ShikiJS theme
* @default 'carta-light' for light mode and 'carta-dark' for dark mode.
*/
// theme?: Theme | DualTheme;
theme?: Theme | DualTheme;
}
/**
@ -157,6 +167,14 @@ export interface Plugin {
* elements absolutely.
*/
components?: ExtensionComponents;
/**
* Custom markdown grammar highlight rules for ShiKi.
*/
grammarRules?: GrammarRule[];
/**
* Custom markdown highlighting rules for ShiKi.
*/
highlightingRules?: HighlightingRule[];
/**
* Use this callback to execute code when one Carta instance loads the extension.
* @param data General Carta related data.
@ -167,11 +185,14 @@ export interface Plugin {
export class Carta {
public readonly sanitizer?: (html: string) => string;
public readonly historyOptions?: TextAreaHistoryOptions;
// public readonly theme?: Theme | DualTheme;
public readonly theme?: Theme | DualTheme;
public readonly shikiOptions?: ShikiOptions;
public readonly rendererDebounce: number;
public readonly keyboardShortcuts: KeyboardShortcut[];
public readonly icons: Icon[];
public readonly prefixes: Prefix[];
public readonly grammarRules: GrammarRule[];
public readonly highlightingRules: HighlightingRule[];
public readonly textareaListeners: Listeners;
public readonly cartaListeners: Listeners;
public readonly components: ExtensionComponents;
@ -182,6 +203,7 @@ export class Carta {
private mElement: HTMLDivElement | undefined;
private mInput: InputEnhancer | undefined;
private mRenderer: Renderer | undefined;
private mHighlighter: Highlighter | Promise<Highlighter> | undefined;
private mSyncTransformers: UnifiedTransformer<'sync'>[] = [];
private mAsyncTransformers: UnifiedTransformer<'async'>[] = [];
@ -195,6 +217,22 @@ export class Carta {
return this.mRenderer;
}
public async highlighter(): Promise<Highlighter> {
if (!this.mHighlighter) {
const promise = async () => {
return loadHighlighter({
theme: this.theme ?? (await loadDefaultTheme()),
grammarRules: this.grammarRules,
highlightingRules: this.highlightingRules,
shiki: this.shikiOptions
});
};
this.mHighlighter = promise();
this.mHighlighter = await this.mHighlighter;
}
return this.mHighlighter;
}
private elementsToBind: {
elem: HTMLElement;
portal: HTMLElement;
@ -204,7 +242,8 @@ export class Carta {
public constructor(options?: Options) {
this.sanitizer = options?.sanitizer || undefined;
this.historyOptions = options?.historyOptions;
// this.theme = options?.theme;
this.theme = options?.theme;
this.shikiOptions = options?.shikiOptions;
this.rendererDebounce = options?.rendererDebounce ?? 300;
// Load plugins
@ -214,6 +253,8 @@ export class Carta {
this.textareaListeners = [];
this.cartaListeners = [];
this.components = [];
this.grammarRules = [];
this.highlightingRules = [];
const listeners = [];
for (const ext of options?.extensions ?? []) {
@ -221,6 +262,8 @@ export class Carta {
this.icons.push(...(ext.icons ?? []));
this.prefixes.push(...(ext.prefixes ?? []));
this.components.push(...(ext.components ?? []));
this.grammarRules.push(...(ext.grammarRules ?? []));
this.highlightingRules.push(...(ext.highlightingRules ?? []));
listeners.push(...(ext.listeners ?? []));
}

View file

@ -2,8 +2,8 @@
import { onMount } from 'svelte';
import type { Carta } from '../carta';
import type { TextAreaProps } from '../textarea-props';
import { Prism, MARKDOWN } from '../highlight';
import { debounce } from '../utils';
import { isSingleTheme, loadNestedLanguages } from '../highlight';
export let carta: Carta;
export let value = '';
@ -34,21 +34,43 @@
const setInput = () => {
carta.$setInput(textarea, elem, () => {
value = textarea.value;
highlightPrism(value);
highlight(value);
});
};
const highlightPrism = (text: string) => {
const html = Prism.highlight(text, MARKDOWN, 'md');
const highlight = async (text: string) => {
const highlighter = await carta.highlighter();
let html: string;
if (isSingleTheme(highlighter.theme)) {
// Single theme
html = highlighter.codeToHtml(text, {
lang: highlighter.lang,
theme: highlighter.theme
});
} else {
// Dual theme
html = highlighter.codeToHtml(text, {
lang: highlighter.lang,
themes: highlighter.theme
});
}
if (carta.sanitizer) {
highlighted = carta.sanitizer(html);
} else {
highlighted = html;
}
resize();
};
$: highlightPrism(value);
const highlightNestedLanguages = debounce(async (text: string) => {
const highlighter = await carta.highlighter();
const { updated } = await loadNestedLanguages(highlighter, text);
if (updated) highlight(text);
}, 300);
$: highlight(value).then(resize);
$: highlightNestedLanguages(value);
onMount(() => {
mounted = true;
@ -69,7 +91,7 @@
class="carta-input"
bind:this={elem}
>
<div class="carta-input-wrapper" class:mounted>
<div class="carta-input-wrapper">
<div
class="carta-highlight carta-font-code"
tabindex="-1"
@ -116,25 +138,22 @@
width: 100%;
max-width: 100%;
min-height: 100%;
overflow-y: hidden;
resize: none;
padding: 0;
margin: 0;
border: 0;
color: transparent;
background: transparent;
outline: none;
tab-size: 4;
}
.mounted > textarea {
overflow-y: hidden;
outline: none;
color: transparent;
background: transparent;
}
.carta-highlight {
display: none;
position: absolute;
left: 0;
right: 0;
@ -152,11 +171,7 @@
word-break: break-word;
}
.mounted > .carta-highlight {
display: block;
}
:global(.carta-highlight) {
:global(.carta-highlight .shiki) {
margin: 0;
tab-size: 4;
background-color: transparent !important;

View file

@ -6,7 +6,6 @@
export let carta: Carta;
export let value: string;
export let elem: HTMLDivElement;
export let renderCls = 'prose';
export let handleScroll: (e: UIEvent) => void;
let mounted = false;
@ -26,7 +25,7 @@
onMount(() => (mounted = true));
</script>
<div bind:this={elem} on:scroll={handleScroll} class="carta-renderer {renderCls}">
<div bind:this={elem} on:scroll={handleScroll} class="carta-renderer markdown-body">
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html renderedHtml}
{#if mounted}

View file

@ -1,15 +1,268 @@
// @ts-expect-error no type definitions
import PrismImport from "prismjs/components/prism-core";
import type PrismType from "prismjs";
import {
getHighlighter,
type BundledTheme,
type ThemeInput,
type StringLiteralUnion,
type BundledLanguage,
type SpecialLanguage,
type LanguageInput,
type LanguageRegistration,
type HighlighterGeneric,
bundledLanguages,
bundledThemes,
type ThemeRegistration
} from 'shiki';
import type { Intellisense } from './utils';
const Prism: typeof PrismType = PrismImport;
/**
* Custom TextMate grammar rule for the highlighter.
*/
export type GrammarRule = {
name: string;
type: 'block' | 'inline';
definition: LanguageRegistration['repository'][string];
};
globalThis.Prism = Prism;
import "prismjs/components/prism-markup";
import prismMarkdown from "./prism-markdown";
prismMarkdown(Prism);
/**
* Custom TextMate highlighting rule for the highlighter.
*/
export type HighlightingRule = {
light: NonNullable<ThemeRegistration['tokenColors']>[number];
dark: NonNullable<ThemeRegistration['tokenColors']>[number];
};
const MARKDOWN = Prism.languages["md"];
/**
* Shiki options for the highlighter.
*/
export type ShikiOptions = {
themes?: Array<ThemeInput | StringLiteralUnion<BundledTheme>>;
langs?: (LanguageInput | StringLiteralUnion<BundledLanguage> | SpecialLanguage)[];
};
type CustomMarkdownLangName = Awaited<(typeof import('./assets/markdown'))['default']['name']>;
type DefaultLightThemeName = Awaited<(typeof import('./assets/theme-light'))['default']['name']>;
type DefaultDarkThemeName = Awaited<(typeof import('./assets/theme-dark'))['default']['name']>;
export const customMarkdownLangName: CustomMarkdownLangName = 'cartamd';
export const defaultLightThemeName: DefaultLightThemeName = 'carta-light';
export const defaultDarkThemeName: DefaultDarkThemeName = 'carta-dark';
export const loadDefaultTheme = async (): Promise<{
light: ThemeRegistration;
dark: ThemeRegistration;
}> => ({
light: structuredClone((await import('./assets/theme-light')).default),
dark: structuredClone((await import('./assets/theme-dark')).default)
});
export {Prism, MARKDOWN};
/**
* Language for the highlighter.
*/
export type Language = Intellisense<BundledLanguage | CustomMarkdownLangName>;
/**
* Theme name for the highlighter.
*/
export type ThemeName = Intellisense<BundledTheme | DefaultLightThemeName | DefaultDarkThemeName>;
/**
* Theme for the highlighter.
*/
export type Theme = ThemeName | ThemeRegistration;
/**
* Dual theme for light and dark mode.
*/
export type DualTheme = {
light: Theme;
dark: Theme;
};
/**
* Options for the highlighter.
*/
export type HighlighterOptions = {
grammarRules: GrammarRule[];
highlightingRules: HighlightingRule[];
theme: Theme | DualTheme;
shiki?: ShikiOptions;
};
/**
* Loads the highlighter instance, with custom rules and options. Uses Shiki under the hood.
* @param rules Custom rules for the highlighter, from plugins.
* @param options Custom options for the highlighter.
* @returns The highlighter instance.
*/
export async function loadHighlighter({
grammarRules,
highlightingRules,
theme,
shiki
}: HighlighterOptions): Promise<Highlighter> {
// Inject rules into the custom markdown language
const injectGrammarRules = (
lang: Awaited<(typeof import('./assets/markdown'))['default']>,
rules: GrammarRule[]
) => {
lang.repository = {
...langDefinition.repository,
...Object.fromEntries(rules.map(({ name, definition }) => [name, definition]))
};
for (const rule of rules) {
if (rule.type === 'block') {
lang.repository.block.patterns.unshift({ include: `#${rule.name}` });
} else {
lang.repository.inline.patterns.unshift({ include: `#${rule.name}` });
}
}
};
const injectHighlightRules = (theme: ThemeRegistration, rules: HighlightingRule[]) => {
if (theme.type === 'light') {
theme.tokenColors ||= [];
theme.tokenColors.unshift(...rules.map(({ light }) => light));
} else {
theme.tokenColors ||= [];
theme.tokenColors.unshift(...rules.map(({ dark }) => dark));
}
};
// Additional themes and languages provided by the user
const themes = shiki?.themes ?? [];
const langs = shiki?.langs ?? [];
const highlighter: HighlighterGeneric<BundledLanguage, BundledTheme> = await getHighlighter({
themes,
langs
});
// Custom markdown language
const langDefinition = (await import('./assets/markdown')).default;
injectGrammarRules(langDefinition, grammarRules);
await highlighter.loadLanguage(langDefinition);
// Custom themes
if (isSingleTheme(theme)) {
let registration: ThemeRegistration;
if (isThemeRegistration(theme)) {
registration = theme;
} else {
registration = (await bundledThemes[theme as BundledTheme]()).default;
}
injectHighlightRules(registration, highlightingRules);
await highlighter.loadTheme(registration);
} else {
const { light, dark } = theme;
let lightRegistration: ThemeRegistration;
let darkRegistration: ThemeRegistration;
if (isThemeRegistration(light)) {
lightRegistration = light;
} else {
lightRegistration = (await bundledThemes[light as BundledTheme]()).default;
}
if (isThemeRegistration(dark)) {
darkRegistration = dark;
} else {
darkRegistration = (await bundledThemes[dark as BundledTheme]()).default;
}
injectHighlightRules(lightRegistration, highlightingRules);
injectHighlightRules(darkRegistration, highlightingRules);
await highlighter.loadTheme(lightRegistration);
await highlighter.loadTheme(darkRegistration);
}
return {
theme,
lang: customMarkdownLangName,
...highlighter
};
}
export interface Highlighter extends HighlighterGeneric<BundledLanguage, BundledTheme> {
/**
* The language specified for the highlighter.
*/
theme: Theme | DualTheme;
/**
* The theme specified for the highlighter.
*/
lang: Language;
}
/**
* Checks if a language is a bundled language.
* @param lang The language to check.
* @returns Whether the language is a bundled language.
*/
export const isBundleLanguage = (lang: string): lang is BundledLanguage =>
Object.keys(bundledLanguages).includes(lang);
/**
* Checks if a theme is a bundled theme.
* @param theme The theme to check.
* @returns Whether the theme is a bundled theme.
*/
export const isBundleTheme = (theme: string): theme is BundledTheme =>
Object.keys(bundledThemes).includes(theme);
/**
* Checks if a theme is a dual theme.
* @param theme The theme to check.
* @returns Whether the theme is a dual theme.
*/
export const isDualTheme = (theme: Theme | DualTheme): theme is DualTheme =>
typeof theme == 'object' && 'light' in theme && 'dark' in theme;
/**
* Checks if a theme is a single theme.
* @param theme The theme to check.
* @returns Whether the theme is a single theme.
*/
export const isSingleTheme = (theme: Theme | DualTheme): theme is Theme => !isDualTheme(theme);
/**
* Checks if a theme is a theme registration.
* @param theme The theme to check.
* @returns Whether the theme is a theme registration.
*/
export const isThemeRegistration = (theme: Theme): theme is ThemeRegistration =>
typeof theme == 'object';
/**
* Find all nested languages in the markdown text and load them into the highlighter.
* @param text Markdown text to parse for nested languages.
* @returns The set of nested languages found in the text.
*/
const findNestedLanguages = (text: string) => {
const languages = new Set<string>();
const regex = /```([a-z]+)\n([\s\S]+?)\n```/g;
let match: RegExpExecArray | null;
while ((match = regex.exec(text))) {
languages.add(match[1]);
}
return languages;
};
/**
* Load all nested languages found in the markdown text into the highlighter.
* @param highlighter The highlighter instance.
* @param text The text to parse for nested languages.
* @returns Whether the highlighter was updated with new languages.
*/
export const loadNestedLanguages = async (highlighter: Highlighter, text: string) => {
text = text.replaceAll('\r\n', '\n'); // Normalize line endings
const languages = findNestedLanguages(text);
const loadedLanguages = highlighter.getLoadedLanguages();
let updated = false;
for (const lang of languages) {
if (isBundleLanguage(lang) && !loadedLanguages.includes(lang)) {
await highlighter.loadLanguage(lang);
loadedLanguages.push(lang);
updated = true;
}
}
return {
updated
};
};

View file

@ -1,420 +0,0 @@
// Original source: https://github.com/PrismJS/prism/blob/master/components/prism-markdown.js
export default function (Prism) {
// Allow only one line break
const inner = /(?:\\.|[^\\\n\r]|(?:\n|\r\n?)(?![\r\n]))/.source;
/**
* This function is intended for the creation of the bold or italic pattern.
*
* This also adds a lookbehind group to the given pattern to ensure that the pattern is not backslash-escaped.
*
* _Note:_ Keep in mind that this adds a capturing group.
*
* @param {string} pattern
* @returns {RegExp}
*/
function createInline(pattern) {
pattern = pattern.replace(/<inner>/g, function () { return inner; });
return RegExp(/((?:^|[^\\])(?:\\{2})*)/.source + '(?:' + pattern + ')');
}
const tableCell = /(?:\\.|``(?:[^`\r\n]|`(?!`))+``|`[^`\r\n]+`|[^\\|\r\n`])+/.source;
const tableRow = /\|?__(?:\|__)+\|?(?:(?:\n|\r\n?)|(?![\s\S]))/.source.replace(/__/g, function () { return tableCell; });
const tableLine = /\|?[ \t]*:?-{3,}:?[ \t]*(?:\|[ \t]*:?-{3,}:?[ \t]*)+\|?(?:\n|\r\n?)/.source;
Prism.languages.markdown = Prism.languages.extend('markup', {});
Prism.languages.insertBefore('markdown', 'prolog', {
'front-matter-block': {
pattern: /(^(?:\s*[\r\n])?)---(?!.)[\s\S]*?[\r\n]---(?!.)/,
lookbehind: true,
greedy: true,
inside: {
'punctuation': /^---|---$/,
'front-matter': {
pattern: /\S+(?:\s+\S+)*/,
alias: ['yaml', 'language-yaml'],
inside: Prism.languages.yaml
}
}
},
'blockquote': {
// > ...
pattern: /^>(?:[\t ]*>)*/m,
alias: 'punctuation'
},
'table': {
pattern: RegExp('^' + tableRow + tableLine + '(?:' + tableRow + ')*', 'm'),
inside: {
'table-data-rows': {
pattern: RegExp('^(' + tableRow + tableLine + ')(?:' + tableRow + ')*$'),
lookbehind: true,
inside: {
'table-data': {
pattern: RegExp(tableCell),
inside: Prism.languages.markdown
},
'punctuation': /\|/
}
},
'table-line': {
pattern: RegExp('^(' + tableRow + ')' + tableLine + '$'),
lookbehind: true,
inside: {
'punctuation': /\||:?-{3,}:?/
}
},
'table-header-row': {
pattern: RegExp('^' + tableRow + '$'),
inside: {
'table-header': {
pattern: RegExp(tableCell),
alias: 'important',
inside: Prism.languages.markdown
},
'punctuation': /\|/
}
}
}
},
'code': [
{
// Prefixed by 4 spaces or 1 tab and preceded by an empty line
pattern: /((?:^|\n)[ \t]*\n|(?:^|\r\n?)[ \t]*\r\n?)(?: {4}|\t).+(?:(?:\n|\r\n?)(?: {4}|\t).+)*/,
lookbehind: true,
alias: 'keyword'
},
{
// ```optional language
// code block
// ```
pattern: /^```[\s\S]*?^```$/m,
greedy: true,
inside: {
'code-block': {
pattern: /^(```.*(?:\n|\r\n?))[\s\S]+?(?=(?:\n|\r\n?)^```$)/m,
lookbehind: true
},
'code-language': {
pattern: /^(```).+/,
lookbehind: true
},
'punctuation': /```/
}
}
],
'title': [
{
// title 1
// =======
// title 2
// -------
pattern: /\S.*(?:\n|\r\n?)(?:==+|--+)(?=[ \t]*$)/m,
alias: 'important',
inside: {
punctuation: /==+$|--+$/
}
},
{
// # title 1
// ###### title 6
pattern: /(^\s*)#.+/m,
lookbehind: true,
alias: 'important',
inside: {
punctuation: /^#+|#+$/
}
}
],
'hr': {
// ***
// ---
// * * *
// -----------
pattern: /(^\s*)([*-])(?:[\t ]*\2){2,}(?=\s*$)/m,
lookbehind: true,
alias: 'punctuation'
},
'list': {
// * item
// + item
// - item
// 1. item
pattern: /(^\s*)(?:[*+-]|\d+\.)(?=[\t ].)/m,
lookbehind: true,
alias: 'punctuation'
},
'url-reference': {
// [id]: http://example.com "Optional title"
// [id]: http://example.com 'Optional title'
// [id]: http://example.com (Optional title)
// [id]: <http://example.com> "Optional title"
pattern: /!?\[[^\]]+\]:[\t ]+(?:\S+|<(?:\\.|[^>\\])+>)(?:[\t ]+(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\)))?/,
inside: {
'variable': {
pattern: /^(!?\[)[^\]]+/,
lookbehind: true
},
'string': /(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\))$/,
'punctuation': /^[\[\]!:]|[<>]/
},
alias: 'url'
},
'bold': {
// **strong**
// __strong__
// allow one nested instance of italic text using the same delimiter
pattern: createInline(/\b__(?:(?!_)<inner>|_(?:(?!_)<inner>)+_)+__\b|\*\*(?:(?!\*)<inner>|\*(?:(?!\*)<inner>)+\*)+\*\*/.source),
lookbehind: true,
greedy: true,
inside: {
'content': {
pattern: /(^..)[\s\S]+(?=..$)/,
lookbehind: true,
inside: {} // see below
},
'punctuation': /\*\*|__/
}
},
'italic': {
// *em*
// _em_
// allow one nested instance of bold text using the same delimiter
pattern: createInline(/\b_(?:(?!_)<inner>|__(?:(?!_)<inner>)+__)+_\b|\*(?:(?!\*)<inner>|\*\*(?:(?!\*)<inner>)+\*\*)+\*/.source),
lookbehind: true,
greedy: true,
inside: {
'content': {
pattern: /(^.)[\s\S]+(?=.$)/,
lookbehind: true,
inside: {} // see below
},
'punctuation': /[*_]/
}
},
'strike': {
// ~~strike through~~
// ~strike~
pattern: createInline(/(~~?)(?:(?!~)<inner>)+\2/.source),
lookbehind: true,
greedy: true,
inside: {
'content': {
pattern: /(^~~?)[\s\S]+(?=\1$)/,
lookbehind: true,
inside: {} // see below
},
'punctuation': /~~?/
}
},
'code-snippet': {
// `code`
// ``code``
pattern: /(^|[^\\`])(?:``[^`\r\n]+(?:`[^`\r\n]+)*``(?!`)|`[^`\r\n]+`(?!`))/,
lookbehind: true,
greedy: true,
alias: ['code', 'keyword']
},
'url': {
// [example](http://example.com "Optional title")
// [example][id]
// [example] [id]
pattern: createInline(/!?\[(?:(?!\])<inner>)+\](?:\([^\s)]+(?:[\t ]+"(?:\\.|[^"\\])*")?\)|[ \t]?\[(?:(?!\])<inner>)+\])/.source),
lookbehind: true,
greedy: true,
inside: {
'operator': /^!/,
'content': {
pattern: /(^\[)[^\]]+(?=\])/,
lookbehind: true,
inside: {} // see below
},
'variable': {
pattern: /(^\][ \t]?\[)[^\]]+(?=\]$)/,
lookbehind: true
},
'url': {
pattern: /(^\]\()[^\s)]+/,
lookbehind: true
},
'string': {
pattern: /(^[ \t]+)"(?:\\.|[^"\\])*"(?=\)$)/,
lookbehind: true
}
}
},
'url-autolink': {
pattern: /https?:\/\/(?:[^\\\n\r >])+/,
lookbehind: true,
greedy: true,
alias: 'url',
}
});
['url', 'bold', 'italic', 'strike'].forEach(function (token) {
['url', 'bold', 'italic', 'strike', 'code-snippet'].forEach(function (inside) {
if (token !== inside) {
Prism.languages.markdown[token].inside.content.inside[inside] = Prism.languages.markdown[inside];
}
});
});
Prism.hooks.add('after-tokenize', function (env) {
if (env.language !== 'markdown' && env.language !== 'md') {
return;
}
function walkTokens(tokens) {
if (!tokens || typeof tokens === 'string') {
return;
}
for (let i = 0, l = tokens.length; i < l; i++) {
const token = tokens[i];
if (token.type !== 'code') {
walkTokens(token.content);
continue;
}
/*
* Add the correct `language-xxxx` class to this code block. Keep in mind that the `code-language` token
* is optional. But the grammar is defined so that there is only one case we have to handle:
*
* token.content = [
* <span class="punctuation">```</span>,
* <span class="code-language">xxxx</span>,
* '\n', // exactly one new lines (\r or \n or \r\n)
* <span class="code-block">...</span>,
* '\n', // exactly one new lines again
* <span class="punctuation">```</span>
* ];
*/
const codeLang = token.content[1];
const codeBlock = token.content[3];
if (codeLang && codeBlock &&
codeLang.type === 'code-language' && codeBlock.type === 'code-block' &&
typeof codeLang.content === 'string') {
// this might be a language that Prism does not support
// do some replacements to support C++, C#, and F#
let lang = codeLang.content.replace(/\b#/g, 'sharp').replace(/\b\+\+/g, 'pp');
// only use the first word
lang = (/[a-z][\w-]*/i.exec(lang) || [''])[0].toLowerCase();
const alias = 'language-' + lang;
// add alias
if (!codeBlock.alias) {
codeBlock.alias = [alias];
} else if (typeof codeBlock.alias === 'string') {
codeBlock.alias = [codeBlock.alias, alias];
} else {
codeBlock.alias.push(alias);
}
}
}
}
walkTokens(env.tokens);
});
Prism.hooks.add('wrap', function (env) {
if (env.type !== 'code-block') {
return;
}
let codeLang = '';
for (let i = 0, l = env.classes.length; i < l; i++) {
const cls = env.classes[i];
const match = /language-(.+)/.exec(cls);
if (match) {
codeLang = match[1];
break;
}
}
const grammar = Prism.languages[codeLang];
if (!grammar) {
if (codeLang && codeLang !== 'none' && Prism.plugins.autoloader) {
const id = 'md-' + new Date().valueOf() + '-' + Math.floor(Math.random() * 1e16);
env.attributes['id'] = id;
Prism.plugins.autoloader.loadLanguages(codeLang, function () {
const ele = document.getElementById(id);
if (ele) {
ele.innerHTML = Prism.highlight(ele.textContent, Prism.languages[codeLang], codeLang);
}
});
}
} else {
env.content = Prism.highlight(textContent(env.content), grammar, codeLang);
}
});
const tagPattern = RegExp(Prism.languages.markup.tag.pattern.source, 'gi');
/**
* A list of known entity names.
*
* This will always be incomplete to save space. The current list is the one used by lowdash's unescape function.
*
* @see {@link https://github.com/lodash/lodash/blob/2da024c3b4f9947a48517639de7560457cd4ec6c/unescape.js#L2}
*/
const KNOWN_ENTITY_NAMES = {
'amp': '&',
'lt': '<',
'gt': '>',
'quot': '"',
};
// IE 11 doesn't support `String.fromCodePoint`
const fromCodePoint = String.fromCodePoint || String.fromCharCode;
/**
* Returns the text content of a given HTML source code string.
*
* @param {string} html
* @returns {string}
*/
function textContent(html) {
// remove all tags
let text = html.replace(tagPattern, '');
// decode known entities
text = text.replace(/&(\w{1,8}|#x?[\da-f]{1,8});/gi, function (m, code) {
code = code.toLowerCase();
if (code[0] === '#') {
let value;
if (code[1] === 'x') {
value = parseInt(code.slice(2), 16);
} else {
value = Number(code.slice(1));
}
return fromCodePoint(value);
} else {
const known = KNOWN_ENTITY_NAMES[code];
if (known) {
return known;
}
// unable to decode
return m;
}
});
return text;
}
Prism.languages.md = Prism.languages.markdown;
}

View file

@ -4,7 +4,6 @@
import ToggleTheme from './ToggleTheme.svelte';
import sampleText from './sample.md?raw';
import '$lib/default.css';
import '$lib/highlight.css';
const carta = new Carta();
</script>

View file

@ -50,7 +50,7 @@
background: #1b1b1f;
}
:global(html.dark .prose) {
:global(html.dark .markdown-body) {
color: #fff;
}
@ -67,8 +67,8 @@
/* Code dark mode */
:global(html.dark .carta-highlight),
:global(html.dark .carta-highlight span) {
color: var(--hl-dark) !important;
:global(html.dark .shiki),
:global(html.dark .shiki span) {
color: var(--shiki-dark) !important;
}
</style>

View file

@ -159,9 +159,6 @@ importers:
packages/carta-md:
dependencies:
prismjs:
specifier: ^1.29.0
version: 1.29.0
rehype-stringify:
specifier: ^10.0.0
version: 10.0.0
@ -174,6 +171,9 @@ importers:
remark-rehype:
specifier: ^11.1.0
version: 11.1.0
shiki:
specifier: ^1.4.0
version: 1.4.0
svelte:
specifier: ^3.54.0 || ^4.0.0
version: 4.2.2
@ -193,9 +193,6 @@ importers:
'@sveltejs/vite-plugin-svelte':
specifier: ^3.0.2
version: 3.0.2(svelte@4.2.2)(vite@5.1.6)
'@types/prismjs':
specifier: ^1.26.4
version: 1.26.4
svelte-check:
specifier: ^3.6.7
version: 3.6.7(postcss@8.4.31)(svelte@4.2.2)
@ -1738,10 +1735,6 @@ packages:
resolution: {integrity: sha512-ehPtgRgaULsFG8x0NeYJvmyH1hmlfsNLujHe9dQEia/7MAJYdzMSi19JtchUHjmBA6XC/75dK55mzZH+RyieSg==}
dev: true
/@types/prismjs@1.26.4:
resolution: {integrity: sha512-rlAnzkW2sZOjbqZ743IHUhFcvzaGbqijwOu8QZnZCjfQzBqFE3s4lOTJEsxikImav9uzz/42I+O7YUs1mWgMlg==}
dev: true
/@types/pug@2.0.6:
resolution: {integrity: sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==}
dev: true
@ -5881,6 +5874,7 @@ packages:
/prismjs@1.29.0:
resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==}
engines: {node: '>=6'}
dev: true
/process-nextick-args@2.0.1:
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}