chore: import upstream snapshot with attribution
publish / version_or_publish (push) Has been cancelled
storybook-build / changes (push) Has been cancelled
storybook-build / :storybook-build (push) Has been cancelled
Sync Gradio Skills to Hugging Face / sync-skills (push) Has been cancelled
functional / changes (push) Has been cancelled
functional / build-frontend (push) Has been cancelled
functional / functional-test-SSR=false (push) Has been cancelled
functional / functional-reload (push) Has been cancelled
js / changes (push) Has been cancelled
js / js-test (push) Has been cancelled
docs-build / changes (push) Has been cancelled
docs-build / docs-build (push) Has been cancelled
docs-build / website-build (push) Has been cancelled
functional / functional-test-SSR=true (push) Has been cancelled
hygiene / hygiene-test (push) Has been cancelled
python / changes (push) Has been cancelled
python / build (push) Has been cancelled
python / test-ubuntu-latest-flaky (push) Has been cancelled
python / test-ubuntu-latest-not-flaky (push) Has been cancelled
python / test-windows-latest-flaky (push) Has been cancelled
python / test-windows-latest-not-flaky (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:17:32 +08:00
commit adf0d17497
3085 changed files with 456962 additions and 0 deletions
+126
View File
@@ -0,0 +1,126 @@
# @gradio/markdown-code
## 0.9.0
### Features
- [#13526](https://github.com/gradio-app/gradio/pull/13526) [`53cb4ca`](https://github.com/gradio-app/gradio/commit/53cb4cae1ec3521e9170d12867253516413ba37a) - Run `pnpm lint` and `pnpm ts:check` on CI. Thanks @abidlabs!
### Fixes
- [#13541](https://github.com/gradio-app/gradio/pull/13541) [`1d19f2b`](https://github.com/gradio-app/gradio/commit/1d19f2b21f6b12285089abc73468ac67b5b93e27) - Fix long unbroken text overflowing in the Markdown component. Thanks @hysts!
## 0.8.0
### Features
- [#13408](https://github.com/gradio-app/gradio/pull/13408) [`6b0c8af`](https://github.com/gradio-app/gradio/commit/6b0c8aff7a9688ad605e4ca52a083c5754fe1610) - Markdown link fix. Thanks @dawoodkhan82!
### Dependency updates
- @gradio/sanitize@0.4.0
## 0.7.0
### Features
- [#13176](https://github.com/gradio-app/gradio/pull/13176) [`45c4ecd`](https://github.com/gradio-app/gradio/commit/45c4ecd25fc83c7ee0210d216df1bc4bd509e622) - Add `@gr.cache()` decorator for caching deterministic functions, as as well as a lower-level `gr.Cache` that uses dependency injection. Thanks @abidlabs!
## 0.6.1
### Fixes
- [#12800](https://github.com/gradio-app/gradio/pull/12800) [`7a1c321`](https://github.com/gradio-app/gradio/commit/7a1c321b6546ba05a353488f5133e8262c4a8a39) - Bump svelte/kit for security reasons. Thanks @freddyaboulton!
- [#12771](https://github.com/gradio-app/gradio/pull/12771) [`404f6a5`](https://github.com/gradio-app/gradio/commit/404f6a55be4cd0c1ece4af6edb8a7769473fb650) - Migrate Markdown components to svelte 5. Thanks @freddyaboulton!
- [#12777](https://github.com/gradio-app/gradio/pull/12777) [`65f94fb`](https://github.com/gradio-app/gradio/commit/65f94fbff75dffe5399c9663745106e959866a3f) - Fix markdown migration. Thanks @aliabid94!
## 0.6.0
### Features
- [#12438](https://github.com/gradio-app/gradio/pull/12438) [`25ffc03`](https://github.com/gradio-app/gradio/commit/25ffc0398f8feb43d817c02b2ab970c16de6d797) - Svelte5 migration and bugfix
### Dependencies
- @gradio/sanitize@0.3.0
## 0.5.2
### Fixes
- [#11939](https://github.com/gradio-app/gradio/pull/11939) [`4e4b0bc`](https://github.com/gradio-app/gradio/commit/4e4b0bc5e0c434e3fda021af65f95dede301ae9b) - Add Syntax Highlighting for more languages in Markdown Component. Thanks @freddyaboulton!
- [#11940](https://github.com/gradio-app/gradio/pull/11940) [`13fdfa6`](https://github.com/gradio-app/gradio/commit/13fdfa6d549ee4e31308af69b75140085ea51fc4) - Upgrade Prismjs. Thanks @freddyaboulton!
## 0.5.1
### Fixes
- [#11782](https://github.com/gradio-app/gradio/pull/11782) [`e132283`](https://github.com/gradio-app/gradio/commit/e132283b2289d3689927a8fa7f0b4ac8f8628a18) - remove dompurify, update mermaid. Thanks @pngwn!
## 0.5.0
### Features
- [#11427](https://github.com/gradio-app/gradio/pull/11427) [`6b2bcd0`](https://github.com/gradio-app/gradio/commit/6b2bcd097ae5ef999a7fb273ecf7c7e4c0eab305) - Improve load times of the Gradio front-end. Thanks @pngwn!
### Dependency updates
- @gradio/sanitize@0.2.0
## 0.4.4
### Fixes
- [#11387](https://github.com/gradio-app/gradio/pull/11387) [`8245afc`](https://github.com/gradio-app/gradio/commit/8245afc669501e1e5f0d619f452455f68a3b7667) - Define root URL in frontend. Thanks @aliabid94!
### Dependency updates
- @gradio/sanitize@0.1.4
## 0.4.3
### Fixes
- [#11010](https://github.com/gradio-app/gradio/pull/11010) [`9219e62`](https://github.com/gradio-app/gradio/commit/9219e62cd21243fd9f5abecbe991e3b29039c76e) - fix: add svg tags for markdown when `allow_tags=True`. Thanks @Col0ring!
## 0.4.2
### Features
- [#10854](https://github.com/gradio-app/gradio/pull/10854) [`1649b00`](https://github.com/gradio-app/gradio/commit/1649b0038482402d7f0ccdbf86ba65d82b4a12c9) - Add support for `mermaid.js` in `Markdown` component (as well as components like `gr.Chatbot` that use Markdown). Thanks @abidlabs!
## 0.4.1
### Fixes
- [#10765](https://github.com/gradio-app/gradio/pull/10765) [`3232cdd`](https://github.com/gradio-app/gradio/commit/3232cddc00be4a8ac441177e4fe408fb193fdb0f) - fix: latex rendering of markdown. Thanks @Col0ring!
## 0.4.0
### Features
- [#10507](https://github.com/gradio-app/gradio/pull/10507) [`3748e4c`](https://github.com/gradio-app/gradio/commit/3748e4c902683ff406fdd246fa8c07dbbbed3ad5) - Chatbot `allow_tags`. Thanks @dawoodkhan82!
## 0.3.0
### Features
- [#10192](https://github.com/gradio-app/gradio/pull/10192) [`4fc7fb7`](https://github.com/gradio-app/gradio/commit/4fc7fb777c42af537e4af612423fa44029657d41) - Ensure components can be remounted with their previous data. Thanks @pngwn!
## 0.2.1
### Dependency updates
- @gradio/sanitize@0.1.3
## 0.2.0
### Features
- [#9756](https://github.com/gradio-app/gradio/pull/9756) [`92f337c`](https://github.com/gradio-app/gradio/commit/92f337cc85d545060ea343f1cee85c22b85f6444) - Fix website build issue. Thanks @aliabd!
### Dependency updates
- @gradio/sanitize@0.1.2
# @gradio/markdown
+263
View File
@@ -0,0 +1,263 @@
<script lang="ts">
import { onMount, tick } from "svelte";
import { create_marked } from "./utils";
import { sanitize } from "@gradio/sanitize";
import "./prism.css";
import { standardHtmlAndSvgTags } from "./html-tags";
import type { ThemeMode } from "@gradio/core";
let {
chatbot = true,
message,
sanitize_html = true,
latex_delimiters = [],
render_markdown = true,
line_breaks = true,
header_links = false,
allow_tags = false,
theme_mode = "system",
onload
}: {
chatbot?: boolean;
message: string;
sanitize_html?: boolean;
latex_delimiters?: {
left: string;
right: string;
display: boolean;
}[];
render_markdown?: boolean | undefined;
line_breaks?: boolean;
header_links?: boolean;
allow_tags?: string[] | boolean | undefined;
theme_mode?: ThemeMode;
onload?: () => void;
} = $props();
let el: HTMLSpanElement;
const marked = create_marked({
header_links,
line_breaks,
latex_delimiters: latex_delimiters || []
});
let html: string = $derived.by(() => {
if (message && message.trim()) {
return process_message(message);
} else {
return "";
}
});
let katex_loaded = false;
function has_math_syntax(text: string): boolean {
if (!latex_delimiters || latex_delimiters.length === 0) {
return false;
}
return latex_delimiters.some(
(delimiter) =>
text.includes(delimiter.left) && text.includes(delimiter.right)
);
}
function escapeRegExp(string: string): string {
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function escapeTags(
content: string,
tagsToEscape: string[] | boolean
): string {
if (tagsToEscape === true) {
// https://www.w3schools.com/tags/
const tagRegex = /<\/?([a-zA-Z][a-zA-Z0-9-]*)([\s>])/g;
return content.replace(tagRegex, (match, tagName, endChar) => {
if (!standardHtmlAndSvgTags.includes(tagName.toLowerCase())) {
return match.replace(/</g, "&lt;").replace(/>/g, "&gt;");
}
return match;
});
}
if (Array.isArray(tagsToEscape)) {
const tagPattern = tagsToEscape.map((tag) => ({
open: new RegExp(`<(${tag})(\\s+[^>]*)?>`, "gi"),
close: new RegExp(`</(${tag})>`, "gi")
}));
let result = content;
tagPattern.forEach((pattern) => {
result = result.replace(pattern.open, (match) =>
match.replace(/</g, "&lt;").replace(/>/g, "&gt;")
);
result = result.replace(pattern.close, (match) =>
match.replace(/</g, "&lt;").replace(/>/g, "&gt;")
);
});
return result;
}
return content;
}
function process_message(value: string): string {
let parsedValue = value;
if (render_markdown) {
const latexBlocks: string[] = [];
latex_delimiters.forEach((delimiter, index) => {
const leftDelimiter = escapeRegExp(delimiter.left);
const rightDelimiter = escapeRegExp(delimiter.right);
const regex = new RegExp(
`${leftDelimiter}([\\s\\S]+?)${rightDelimiter}`,
"g"
);
parsedValue = parsedValue.replace(regex, (match, p1) => {
latexBlocks.push(match);
return `%%%LATEX_BLOCK_${latexBlocks.length - 1}%%%`;
});
});
parsedValue = marked.parse(parsedValue) as string;
parsedValue = parsedValue.replace(
/%%%LATEX_BLOCK_(\d+)%%%/g,
(match, p1) => latexBlocks[parseInt(p1, 10)]
);
}
if (allow_tags) {
parsedValue = escapeTags(parsedValue, allow_tags);
}
if (sanitize_html && sanitize) {
parsedValue = sanitize(parsedValue);
}
return parsedValue;
}
async function render_html(value: string): Promise<void> {
if (latex_delimiters.length > 0 && value && has_math_syntax(value)) {
if (!katex_loaded) {
await Promise.all([
import("katex/dist/katex.min.css"),
import("katex/contrib/auto-render")
]).then(([, { default: render_math_in_element }]) => {
katex_loaded = true;
render_math_in_element(el, {
delimiters: latex_delimiters,
throwOnError: false
});
});
} else {
const { default: render_math_in_element } =
await import("katex/contrib/auto-render");
render_math_in_element(el, {
delimiters: latex_delimiters,
throwOnError: false
});
}
}
if (el) {
const mermaidDivs = el.querySelectorAll(".mermaid");
if (mermaidDivs.length > 0) {
await tick();
const { default: mermaid } = await import("mermaid");
mermaid.initialize({
startOnLoad: false,
theme: theme_mode === "dark" ? "dark" : "default",
securityLevel: "antiscript"
});
await mermaid.run({
nodes: Array.from(mermaidDivs).map((node) => node as HTMLElement)
});
}
}
}
$effect(() => {
if (el && document.body.contains(el)) {
render_html(message).then(() => onload?.());
} else {
console.error("Element is not in the DOM");
}
});
</script>
<span class:chatbot bind:this={el} class="md" class:prose={render_markdown}>
{@html html}
</span>
<style>
span {
/* `anywhere`, not `break-word`: it also lowers min-content width, so
markdown in a table/flex container shrinks to fit instead of forcing
the container to overflow. */
overflow-wrap: anywhere;
}
span :global(div[class*="code_wrap"]) {
position: relative;
}
/* KaTeX */
span :global(span.katex) {
font-size: var(--text-lg);
direction: ltr;
}
span :global(div[class*="code_wrap"] > button) {
z-index: 1;
cursor: pointer;
border-bottom-left-radius: var(--radius-sm);
padding: var(--spacing-md);
width: 25px;
height: 25px;
position: absolute;
right: 0;
}
span :global(.check) {
opacity: 0;
z-index: var(--layer-top);
transition: opacity 0.2s;
background: var(--code-background-fill);
color: var(--body-text-color);
position: absolute;
top: var(--size-1-5);
left: var(--size-1-5);
}
span :global(p:not(:first-child)) {
margin-top: var(--spacing-xxl);
}
span :global(.md-header-anchor) {
/* position: absolute; */
margin-left: -25px;
padding-right: 8px;
line-height: 1;
color: var(--body-text-color-subdued);
opacity: 0;
}
span :global(h1:hover .md-header-anchor),
span :global(h2:hover .md-header-anchor),
span :global(h3:hover .md-header-anchor),
span :global(h4:hover .md-header-anchor),
span :global(h5:hover .md-header-anchor),
span :global(h6:hover .md-header-anchor) {
opacity: 1;
}
span.md :global(.md-header-anchor > svg) {
color: var(--body-text-color-subdued);
}
span :global(table) {
word-break: break-word;
}
</style>
+52
View File
@@ -0,0 +1,52 @@
# `@gradio/markdown`
```html
<script>
import { BaseMarkdown, MarkdownCode, BaseExample } from `@gradio/markdown`;
</script>
```
BaseMarkdown
```javascript
export let elem_id = "";
export let elem_classes: string[] = [];
export let visible = true;
export let value: string;
export let min_height = false;
export let rtl = false;
export let sanitize_html = true;
export let line_breaks = false;
export let latex_delimiters: {
left: string;
right: string;
display: boolean;
}[];
```
MarkdownCode
```javascript
export let chatbot = true;
export let message: string;
export let sanitize_html = true;
export let latex_delimiters: {
left: string;
right: string;
display: boolean;
}[] = [];
export let render_markdown = true;
export let line_breaks = true;
```
BaseExample
```javascript
export let value: string;
export let type: "gallery" | "table";
export let selected = false;
export let sanitize_html: boolean;
export let line_breaks: boolean;
export let latex_delimiters: {
left: string;
right: string;
display: boolean;
}[];
```
+209
View File
@@ -0,0 +1,209 @@
// https://www.w3schools.com/tags/
export const standardHtmlTags = [
"!--",
"!doctype",
"a",
"abbr",
"acronym",
"address",
"applet",
"area",
"article",
"aside",
"audio",
"b",
"base",
"basefont",
"bdi",
"bdo",
"big",
"blockquote",
"body",
"br",
"button",
"canvas",
"caption",
"center",
"cite",
"code",
"col",
"colgroup",
"data",
"datalist",
"dd",
"del",
"details",
"dfn",
"dialog",
"dir",
"div",
"dl",
"dt",
"em",
"embed",
"fieldset",
"figcaption",
"figure",
"font",
"footer",
"form",
"frame",
"frameset",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"head",
"header",
"hgroup",
"hr",
"html",
"i",
"iframe",
"img",
"input",
"ins",
"kbd",
"label",
"legend",
"li",
"link",
"main",
"map",
"mark",
"menu",
"meta",
"meter",
"nav",
"noframes",
"noscript",
"object",
"ol",
"optgroup",
"option",
"output",
"p",
"param",
"picture",
"pre",
"progress",
"q",
"rp",
"rt",
"ruby",
"s",
"samp",
"script",
"search",
"section",
"select",
"small",
"source",
"span",
"strike",
"strong",
"style",
"sub",
"summary",
"sup",
"svg",
"table",
"tbody",
"td",
"template",
"textarea",
"tfoot",
"th",
"thead",
"time",
"title",
"tr",
"track",
"tt",
"u",
"ul",
"var",
"video",
"wbr"
];
// SVG tags
export const svgTags = [
// Base structural elements
"g",
"defs",
"use",
"symbol",
// Shape elements
"rect",
"circle",
"ellipse",
"line",
"polyline",
"polygon",
"path",
"image",
// Text elements
"text",
"tspan",
"textPath",
// Gradient and effects
"linearGradient",
"radialGradient",
"stop",
"pattern",
"clipPath",
"mask",
"filter",
// Filter effects
"feBlend",
"feColorMatrix",
"feComponentTransfer",
"feComposite",
"feConvolveMatrix",
"feDiffuseLighting",
"feDisplacementMap",
"feGaussianBlur",
"feMerge",
"feMorphology",
"feOffset",
"feSpecularLighting",
"feTurbulence",
"feMergeNode",
"feFuncR",
"feFuncG",
"feFuncB",
"feFuncA",
"feDistantLight",
"fePointLight",
"feSpotLight",
"feFlood",
"feTile",
// Animation elements
"animate",
"animateTransform",
"animateMotion",
"mpath",
"set",
// Interactive and other elements
"view",
"cursor",
"foreignObject",
"desc",
"title",
"metadata",
"switch"
];
export const standardHtmlAndSvgTags = [
...standardHtmlTags,
...svgTags.filter((tag) => !standardHtmlTags.includes(tag))
];
+1
View File
@@ -0,0 +1 @@
export { default as MarkdownCode } from "./MarkdownCode.svelte";
+42
View File
@@ -0,0 +1,42 @@
{
"name": "@gradio/markdown-code",
"version": "0.9.0",
"description": "Gradio UI packages",
"type": "module",
"author": "",
"license": "ISC",
"private": false,
"main_changeset": true,
"main": "Index.svelte",
"exports": {
".": {
"gradio": "./index.ts",
"svelte": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"./package.json": "./package.json"
},
"dependencies": {
"@gradio/sanitize": "workspace:^",
"@types/katex": "^0.16.7",
"@types/prismjs": "1.26.5",
"github-slugger": "^2.0.0",
"katex": "^0.16.23",
"marked": "^12.0.0",
"marked-gfm-heading-id": "^3.1.2",
"marked-highlight": "^2.0.1",
"mermaid": "^11.12.0",
"prismjs": "^1.30.0"
},
"devDependencies": {
"@gradio/preview": "workspace:^"
},
"peerDependencies": {
"svelte": "^5.48.0"
},
"repository": {
"type": "git",
"url": "git+https://github.com/gradio-app/gradio.git",
"directory": "js/markdown"
}
}
+68
View File
@@ -0,0 +1,68 @@
.dark .md .token.comment,
.dark .md .token.prolog,
.dark .md .token.doctype,
.dark .md .token.cdata {
color: hsl(30, 20%, 50%);
}
.dark .md .token.punctuation {
opacity: 0.7;
}
.dark .md .token.namespace {
opacity: 0.7;
}
.dark .md .token.property,
.dark .md .token.tag,
.dark .md .token.boolean,
.dark .md .token.number,
.dark .md .token.constant,
.dark .md .token.symbol {
color: hsl(350, 40%, 70%);
}
.dark .md .token.selector,
.dark .md .token.attr-name,
.dark .md .token.string,
.dark .md .token.char,
.dark .md .token.builtin,
.dark .md .token.inserted {
color: hsl(75, 70%, 60%);
}
.dark .md .token.operator,
.dark .md .token.entity,
.dark .md .token.url,
.dark .md .language-css .token.string,
.dark .md .style .token.string,
.dark .md .token.variable {
color: hsl(40, 90%, 60%);
}
.dark .md .token.atrule,
.dark .md .token.attr-value,
.dark .md .token.keyword {
color: hsl(350, 40%, 70%);
}
.dark .md .token.regex,
.dark .md .token.important {
color: #e90;
}
.dark .md .token.important,
.dark .md .token.bold {
font-weight: bold;
}
.dark .md .token.italic {
font-style: italic;
}
.dark .md .token.entity {
cursor: help;
}
.dark .md .token.deleted {
color: red;
}
+226
View File
@@ -0,0 +1,226 @@
Tables */ table,
tr,
td,
th {
margin-top: var(--spacing-sm);
margin-bottom: var(--spacing-sm);
padding: var(--spacing-xl);
}
/* .message-wrap :global(pre[class*="language-"]),
.message-wrap :global(pre) {
border: none;
background: none;
position: relative;
direction: ltr;
white-space: no-wrap;
overflow-x: auto;
}
.message-wrap :global(code) {
} */
/* .message-wrap :global(div[class*="code_wrap"]) {
} */
.md code,
.md pre {
background: none;
font-family: var(--font-mono);
font-size: var(--text-sm);
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
tab-size: 2;
hyphens: none;
}
.md pre[class*="language-"]::-moz-selection,
.md pre[class*="language-"] ::-moz-selection,
.md code[class*="language-"]::-moz-selection,
.md code[class*="language-"] ::-moz-selection {
}
.md pre[class*="language-"]::selection,
.md pre[class*="language-"] ::selection,
.md code[class*="language-"]::selection,
.md code[class*="language-"] ::selection {
text-shadow: none;
background: #b3d4fc;
}
/* Code blocks */
.md pre {
padding: 1em;
margin: 0.5em 0;
overflow: auto;
position: relative;
margin-top: var(--spacing-sm);
margin-bottom: var(--spacing-sm);
box-shadow: none;
border: none;
border-radius: var(--radius-md);
background: var(--code-background-fill);
padding: var(--spacing-xxl);
font-family: var(--font-mono);
/* font-size: var(--text-sm) !important; */
display: block;
white-space: pre;
border-radius: var(--radius-sm);
text-shadow: none;
border-radius: var(--radius-sm);
/* font-size: 85%; */
white-space: nowrap;
display: block;
white-space: pre;
}
.prose code {
}
.prose pre > code {
}
/* Inline code */
.md :not(pre) > code {
padding-block: var(--spacing-xxs);
padding-inline: var(--spacing-xs);
border-radius: var(--radius-xs);
white-space: normal;
background: var(--code-background-fill);
border: 1px solid var(--panel-border-color);
display: inline-flex;
align-items: center;
line-height: 1.4;
}
.md h1 > code,
.md h2 > code,
.md h3 > code,
.md h4 > code,
.md h5 > code,
.md h6 > code {
font-size: inherit;
line-height: inherit;
}
.md .token.comment,
.md .token.prolog,
.md .token.doctype,
.md .token.cdata {
color: slategray;
}
.md .token.punctuation {
color: #999;
}
.md .token.namespace {
opacity: 0.7;
}
.md .token.property,
.md .token.tag,
.md .token.boolean,
.md .token.number,
.md .token.constant,
.md .token.symbol,
.md .token.deleted {
color: #905;
}
.md .token.selector,
.md .token.attr-name,
.md .token.string,
.md .token.char,
.md .token.builtin,
.md .token.inserted {
color: #690;
}
.md .token.atrule,
.md .token.attr-value,
.md .token.keyword {
color: #07a;
}
.md .token.function,
.md .token.class-name {
color: #dd4a68;
}
.md .token.regex,
.md .token.important,
.md .token.variable {
color: #e90;
}
.md .token.important,
.md .token.bold {
font-weight: bold;
}
.md .token.italic {
font-style: italic;
}
.md .token.entity {
cursor: help;
}
.dark .md .token.comment,
.dark .md .token.prolog,
.dark .md .token.cdata {
color: hsl(220, 10%, 40%);
}
.dark .md .token.doctype,
.dark .md .token.punctuation,
.dark .md .token.entity {
color: hsl(220, 14%, 71%);
}
.dark .md .token.attr-name,
.dark .md .token.class-name,
.dark .md .token.boolean,
.dark .md .token.constant,
.dark .md .token.number,
.dark .md .token.atrule {
color: hsl(29, 54%, 61%);
}
.dark .md .token.keyword {
color: hsl(286, 60%, 67%);
}
.dark .md .token.property,
.dark .md .token.tag,
.dark .md .token.symbol,
.dark .md .token.deleted,
.dark .md .token.important {
color: hsl(355, 65%, 65%);
}
.dark .md .token.selector,
.dark .md .token.string,
.dark .md .token.char,
.dark .md .token.builtin,
.dark .md .token.inserted,
.dark .md .token.regex,
.dark .md .token.attr-value,
.dark .md .token.attr-value > .token.punctuation {
color: hsl(95, 38%, 62%);
}
.dark .md .token.variable,
.dark .md .token.operator,
.dark .md .token.function {
color: hsl(207, 82%, 66%);
}
.dark .md .token.url {
color: hsl(187, 47%, 55%);
}
+329
View File
@@ -0,0 +1,329 @@
import { Marked, Renderer } from "marked";
import { markedHighlight } from "marked-highlight";
import { gfmHeadingId } from "marked-gfm-heading-id";
import * as Prism from "prismjs";
import "prismjs/components/prism-python";
import "prismjs/components/prism-latex";
import "prismjs/components/prism-bash";
// Add additional Prism languages here
import "prismjs/components/prism-c";
import "prismjs/components/prism-cpp";
import "prismjs/components/prism-json";
import "prismjs/components/prism-sql";
import "prismjs/components/prism-java";
import "prismjs/components/prism-go";
import "prismjs/components/prism-rust";
import "prismjs/components/prism-php";
import "prismjs/components/prism-yaml";
import "prismjs/components/prism-markup-templating";
import GithubSlugger from "github-slugger";
const LINK_ICON_CODE = `<svg class="md-link-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true" fill="currentColor"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg>`;
const COPY_ICON_CODE = `
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 15" color="currentColor" aria-hidden="true" aria-label="Copy" stroke-width="1.3" width="15" height="15">
<path fill="currentColor" d="M12.728 4.545v8.182H4.545V4.545zm0 -0.909H4.545a0.909 0.909 0 0 0 -0.909 0.909v8.182a0.909 0.909 0 0 0 0.909 0.909h8.182a0.909 0.909 0 0 0 0.909 -0.909V4.545a0.909 0.909 0 0 0 -0.909 -0.909"/>
<path fill="currentColor" d="M1.818 8.182H0.909V1.818a0.909 0.909 0 0 1 0.909 -0.909h6.364v0.909H1.818Z"/>
</svg>
`;
const CHECK_ICON_CODE = `<svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 17 17" aria-hidden="true" aria-label="Copied" fill="none" stroke="currentColor" stroke-width="1.3">
<path d="m13.813 4.781 -7.438 7.438 -3.188 -3.188"/>
</svg>
`;
const COPY_BUTTON_CODE = `<button title="copy" class="copy_code_button">
<span class="copy-text">${COPY_ICON_CODE}</span>
<span class="check">${CHECK_ICON_CODE}</span>
</button>`;
const default_renderer = new Renderer();
function should_open_link_in_new_tab(href: string): boolean {
const trimmed_href = href.trim();
return trimmed_href !== "" && !trimmed_href.startsWith("#");
}
const escape_test = /[&<>"']/;
const escape_replace = new RegExp(escape_test.source, "g");
const escape_test_no_encode =
/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/;
const escape_replace_no_encode = new RegExp(escape_test_no_encode.source, "g");
const escape_replacements: Record<string, any> = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#39;"
};
const get_escape_replacement = (ch: string): string =>
escape_replacements[ch] || "";
function escape(html: string, encode?: boolean): string {
if (encode) {
if (escape_test.test(html)) {
return html.replace(escape_replace, get_escape_replacement);
}
} else {
if (escape_test_no_encode.test(html)) {
return html.replace(escape_replace_no_encode, get_escape_replacement);
}
}
return html;
}
interface Tokenizer {
name: string;
level: string;
start: (src: string) => number | undefined;
tokenizer: (src: string, tokens: any) => any;
renderer: (token: any) => string;
}
function createLatexTokenizer(
delimiters: { left: string; right: string; display: boolean }[]
): Tokenizer {
const delimiterPatterns = delimiters.map((delimiter) => ({
start: new RegExp(delimiter.left.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&")),
end: new RegExp(delimiter.right.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&"))
}));
return {
name: "latex",
level: "block",
start(src: string) {
for (const pattern of delimiterPatterns) {
const match = src.match(pattern.start);
if (match) {
return match.index;
}
}
return -1;
},
tokenizer(src: string, tokens: any) {
for (const pattern of delimiterPatterns) {
const match = new RegExp(
`${pattern.start.source}([\\s\\S]+?)${pattern.end.source}`
).exec(src);
if (match) {
return {
type: "latex",
raw: match[0],
text: match[1].trim()
};
}
}
},
renderer(token: any) {
return `<div class="latex-block">${token.text}</div>`;
}
};
}
function createMermaidTokenizer(): Tokenizer {
return {
name: "mermaid",
level: "block",
start(src) {
return src.match(/^```mermaid\s*\n/)?.index;
},
tokenizer(src) {
const match = /^```mermaid\s*\n([\s\S]*?)```\s*(?:\n|$)/.exec(src);
if (match) {
return {
type: "mermaid",
raw: match[0],
text: match[1].trim()
};
}
return undefined;
},
renderer(token) {
return `<div class="mermaid">${token.text}</div>\n`;
}
};
}
const renderer: Partial<Omit<Renderer, "constructor" | "options">> = {
code(
this: Renderer,
code: string,
infostring: string | undefined,
escaped: boolean
) {
const lang = (infostring ?? "").match(/\S*/)?.[0] ?? "";
code = code.replace(/\n$/, "") + "\n";
if (!lang || lang === "mermaid") {
// We include lang === "mermaid" to handle mermaid blocks that don't match our custom tokenizer
// (i.e., those without closing ```). This handles mermaid blocks that have started streaming
// but haven't finished yet.
return (
'<div class="code_wrap">' +
COPY_BUTTON_CODE +
"<pre><code>" +
(escaped ? code : escape(code, true)) +
"</code></pre></div>\n"
);
}
return (
'<div class="code_wrap">' +
COPY_BUTTON_CODE +
'<pre><code class="' +
"language-" +
escape(lang) +
'">' +
(escaped ? code : escape(code, true)) +
"</code></pre></div>\n"
);
},
link(
this: Renderer,
href: string,
title: string | null | undefined,
text: string
) {
const rendered_link = default_renderer.link(href, title, text);
if (
!should_open_link_in_new_tab(href) ||
!rendered_link.startsWith("<a ")
) {
return rendered_link;
}
return rendered_link.replace(
/^<a /,
'<a target="_blank" rel="noopener noreferrer" '
);
}
};
const slugger = new GithubSlugger();
export function create_marked({
header_links,
line_breaks,
latex_delimiters
}: {
header_links: boolean;
line_breaks: boolean;
latex_delimiters: { left: string; right: string; display: boolean }[];
}): typeof marked {
const marked = new Marked();
marked.use(
{
gfm: true,
pedantic: false,
breaks: line_breaks
},
markedHighlight({
highlight: (code: string, lang: string) => {
if (Prism?.languages?.[lang]) {
return Prism.highlight(code, Prism.languages[lang], lang);
}
return code;
}
}),
{ renderer }
);
if (header_links) {
marked.use(gfmHeadingId());
marked.use({
extensions: [
{
name: "heading",
level: "block",
renderer(token) {
const raw = token.raw
.toLowerCase()
.trim()
.replace(/<[!\/a-z].*?>/gi, "");
const id = "h" + slugger.slug(raw);
const level = token.depth;
const text = this.parser.parseInline(token.tokens!);
return `<h${level} id="${id}"><a class="md-header-anchor" href="#${id}">${LINK_ICON_CODE}</a>${text}</h${level}>\n`;
}
}
]
});
}
const mermaidTokenizer = createMermaidTokenizer();
const latexTokenizer = createLatexTokenizer(latex_delimiters);
marked.use({
extensions: [mermaidTokenizer, latexTokenizer]
});
return marked;
}
export function copy(node: HTMLDivElement): any {
node.addEventListener("click", handle_copy);
async function handle_copy(event: MouseEvent): Promise<void> {
const path = event.composedPath() as HTMLButtonElement[];
const [copy_button] = path.filter(
(e) => e?.tagName === "BUTTON" && e.classList.contains("copy_code_button")
);
if (copy_button) {
event.stopImmediatePropagation();
const copy_text = copy_button.parentElement!.innerText.trim();
const copy_sucess_button = Array.from(
copy_button.children
)[1] as HTMLDivElement;
const copied = await copy_to_clipboard(copy_text);
if (copied) copy_feedback(copy_sucess_button);
function copy_feedback(_copy_sucess_button: HTMLDivElement): void {
_copy_sucess_button.style.opacity = "1";
setTimeout(() => {
_copy_sucess_button.style.opacity = "0";
}, 2000);
}
}
}
return {
destroy(): void {
node.removeEventListener("click", handle_copy);
}
};
}
async function copy_to_clipboard(value: string): Promise<boolean> {
let copied = false;
if ("clipboard" in navigator) {
await navigator.clipboard.writeText(value);
copied = true;
} else {
const textArea = document.createElement("textarea");
textArea.value = value;
textArea.style.position = "absolute";
textArea.style.left = "-999999px";
document.body.prepend(textArea);
textArea.select();
try {
document.execCommand("copy");
copied = true;
} catch (error) {
console.error(error);
copied = false;
} finally {
textArea.remove();
}
}
return copied;
}