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
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:
@@ -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
|
||||
@@ -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, "<").replace(/>/g, ">");
|
||||
}
|
||||
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, "<").replace(/>/g, ">")
|
||||
);
|
||||
result = result.replace(pattern.close, (match) =>
|
||||
match.replace(/</g, "<").replace(/>/g, ">")
|
||||
);
|
||||
});
|
||||
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>
|
||||
@@ -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;
|
||||
}[];
|
||||
```
|
||||
@@ -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))
|
||||
];
|
||||
@@ -0,0 +1 @@
|
||||
export { default as MarkdownCode } from "./MarkdownCode.svelte";
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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%);
|
||||
}
|
||||
@@ -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> = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': """,
|
||||
"'": "'"
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user