diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index c9c1775..842d40c 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/setup-node@v3 - uses: pnpm/action-setup@v2 with: - version: 6 + version: 8 - name: Install dependendencies run: pnpm i diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index ad310ac..731c3a1 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/setup-node@v3 - uses: pnpm/action-setup@v2 with: - version: 6 + version: 8 - name: Install dependencies run: pnpm i diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index be810b1..84a2e6e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,7 +16,7 @@ jobs: - uses: actions/setup-node@v3 - uses: pnpm/action-setup@v2 with: - version: 6 + version: 8 - name: Install dependendencies run: pnpm i @@ -45,7 +45,7 @@ jobs: - uses: actions/setup-node@v3 - uses: pnpm/action-setup@v2 with: - version: 6 + version: 8 - name: Install dependendencies run: pnpm i @@ -53,9 +53,6 @@ jobs: - name: Build all packages run: pnpm build - - name: Verify the integrity of provenance attestations and registry signatures for installed dependencies - run: npm audit signatures - - name: Release run: npm run publish env: diff --git a/.vscode/settings.json b/.vscode/settings.json index 7fd34f4..85e3f0d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,11 +5,15 @@ "coldark", "dompurify", "flexsearch", + "Gemoji", "gruvbox", + "iconify", "Katex", "mdsvex", "oldschool", "rehype", + "shiki", + "shikijs", "tikz", "tikzjax", "typeof" @@ -17,5 +21,6 @@ "typescript.tsdk": "node_modules\\typescript\\lib", "[svelte]": { "editor.defaultFormatter": "svelte.svelte-vscode" - } + }, + "css.customData": [".vscode/tailwind.json"] } diff --git a/.vscode/tailwind.json b/.vscode/tailwind.json new file mode 100644 index 0000000..a45bccc --- /dev/null +++ b/.vscode/tailwind.json @@ -0,0 +1,55 @@ +{ + "version": 1.1, + "atDirectives": [ + { + "name": "@tailwind", + "description": "Use the `@tailwind` directive to insert Tailwind's `base`, `components`, `utilities` and `screens` styles into your CSS.", + "references": [ + { + "name": "Tailwind Documentation", + "url": "https://tailwindcss.com/docs/functions-and-directives#tailwind" + } + ] + }, + { + "name": "@apply", + "description": "Use the `@apply` directive to inline any existing utility classes into your own custom CSS. This is useful when you find a common utility pattern in your HTML that youโ€™d like to extract to a new component.", + "references": [ + { + "name": "Tailwind Documentation", + "url": "https://tailwindcss.com/docs/functions-and-directives#apply" + } + ] + }, + { + "name": "@responsive", + "description": "You can generate responsive variants of your own classes by wrapping their definitions in the `@responsive` directive:\n```css\n@responsive {\n .alert {\n background-color: #E53E3E;\n }\n}\n```\n", + "references": [ + { + "name": "Tailwind Documentation", + "url": "https://tailwindcss.com/docs/functions-and-directives#responsive" + } + ] + }, + { + "name": "@screen", + "description": "The `@screen` directive allows you to create media queries that reference your breakpoints by **name** instead of duplicating their values in your own CSS:\n```css\n@screen sm {\n /* ... */\n}\n```\nโ€ฆgets transformed into this:\n```css\n@media (min-width: 640px) {\n /* ... */\n}\n```\n", + "references": [ + { + "name": "Tailwind Documentation", + "url": "https://tailwindcss.com/docs/functions-and-directives#screen" + } + ] + }, + { + "name": "@variants", + "description": "Generate `hover`, `focus`, `active` and other **variants** of your own utilities by wrapping their definitions in the `@variants` directive:\n```css\n@variants hover, focus {\n .btn-brand {\n background-color: #3182CE;\n }\n}\n```\n", + "references": [ + { + "name": "Tailwind Documentation", + "url": "https://tailwindcss.com/docs/functions-and-directives#variants" + } + ] + } + ] +} diff --git a/README.md b/README.md index e8ea005..38235d5 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,25 @@
- npm + npm - bundle + bundle - license + license - docs + docs
-
- - banner - -
+[![Carta.png](https://i.postimg.cc/nV6DMXKM/Carta.png)](https://beartocode.github.io/carta/) -
- -
Carta
-
Swiftly edit and render Markdown, with no overhead.
+

Carta

+
Modern, lightweight, powerful Markdown Editor.

-Documentation +๐Ÿ“š Documentation ยท GitHub
@@ -34,22 +28,30 @@ # Introduction -Carta is a **lightweight**, **fast** and **extensible** Svelte Markdown editor and viewer, based on [Marked](https://github.com/markedjs/marked). Check out the [examples](http://beartocode.github.io/carta/examples) to see it in action. -Differently from most editors, Carta includes neither ProseMirror nor CodeMirror, allowing for an extremely small bundle size and fast loading time. +> [!NOTE] +> Carta has recently been updated to `v4`, which features numerous major changes. +> +> Follow the [Migration Guide](http://beartocode.github.io/carta/migration) to update your project. + +Carta is a **lightweight**, **fast** and **extensible** Svelte Markdown editor and viewer. It is powered by [unified](https://github.com/unifiedjs/unified), [remark](https://github.com/remarkjs/remark) and [rehype](https://github.com/rehypejs/rehype). Check out the [examples](http://beartocode.github.io/carta/examples) to see it in action. +Differently from most editors, Carta does not include a code editor, but it is _just_ a textarea with syntax highlighting, shortcuts and more. ## Features -- Keyboard **shortcuts** (extensible); -- Toolbar (extensible); -- Markdown syntax highlighting; -- Scroll sync; -- **SSR** compatible; -- **Katex** support (plugin); -- **Slash** commands (plugin); -- **Emojis**, with included search (plugin); -- **Tikz** support(plugin); -- **Attachment** support(plugin); -- Code blocks **syntax highlighting** (plugin). +- ๐ŸŒˆ Markdown syntax highlighting ([Shiki](https://shiki.style/)); +- ๐Ÿ› ๏ธ Toolbar (extensible); +- โŒจ๏ธ Keyboard **shortcuts** (extensible); +- ๐Ÿ“ฆ Supports **[150+ plugins](https://github.com/remarkjs/remark/blob/main/doc/plugins.md#list-of-plugins)** thanks to remark; +- ๐Ÿ”€ Scroll sync; +- โœ… Accessibility friendly; +- ๐Ÿ–ฅ๏ธ **SSR** compatible; +- โš—๏ธ **KaTeX** support (plugin); +- ๐Ÿ”จ **Slash** commands (plugin); +- ๐Ÿ˜„ **Emojis**, with included search (plugin); +- โœ๏ธ **TikZ** support (plugin); +- ๐Ÿ“‚ **Attachment** support (plugin); +- โš“ **Anchor** links in headings (plugin); +- ๐ŸŒˆ Code blocks **syntax highlighting** (plugin). ## Packages @@ -62,12 +64,23 @@ Differently from most editors, Carta includes neither ProseMirror nor CodeMirror | [plugin-slash](https://www.npmjs.com/package/@cartamd/plugin-slash) | ![plugin-slash](https://img.shields.io/npm/v/@cartamd/plugin-slash) | [/plugins/slash](https://beartocode.github.io/carta/plugins/slash) | | [plugin-tikz](https://www.npmjs.com/package/@cartamd/plugin-tikz) | ![plugin-tikz](https://img.shields.io/npm/v/@cartamd/plugin-tikz) | [/plugins/tikz](https://beartocode.github.io/carta/plugins/tikz) | | [plugin-attachment](https://www.npmjs.com/package/@cartamd/plugin-attachment) | ![plugin-attachment](https://img.shields.io/npm/v/@cartamd/plugin-attachment) | [/plugins/attachment](https://beartocode.github.io/carta/plugins/attachment) | +| [plugin-anchor](https://www.npmjs.com/package/@cartamd/plugin-anchor) | ![plugin-anchor](https://img.shields.io/npm/v/@cartamd/plugin-anchor) | [/plugins/anchor](https://beartocode.github.io/carta/plugins/anchor) | + +## Community plugins + +| Plugin | Description | +| ----------------------------------------------------------------------------- | ---------------------------------- | +| [carta-plugin-video](https://github.com/maisonsmd/carta-plugin-video) | Render online videos | +| [carta-plugin-imsize](https://github.com/maisonsmd/carta-plugin-imsize) | Render images in specific sizes | +| [carta-plugin-subscript](https://github.com/maisonsmd/carta-plugin-subscript) | Render subscripts and superscripts | +| [carta-plugin-ins-del](https://github.com/maisonsmd/carta-plugin-ins-del) | `` and `` tags support | # Getting started > [!WARNING] > Sanitization is not dealt with by Carta. You need to provide a `sanitizer` in the options. > Common sanitizers are [isomorphic-dompurify](https://www.npmjs.com/package/isomorphic-dompurify) (suggested) and [sanitize-html](https://www.npmjs.com/package/sanitize-html). +> Checkout the documentation for an example. ## Installation @@ -87,11 +100,9 @@ npm i @cartamd/plugin-name ```svelte - + ``` @@ -125,6 +137,7 @@ For the full documentation, examples, guides and more checkout the [website](htt - [Slash](https://beartocode.github.io/carta/plugins/slash) - [TikZ](https://beartocode.github.io/carta/plugins/tikz) - [Attachment](https://beartocode.github.io/carta/plugins/attachment) + - [Anchor](https://beartocode.github.io/carta/plugins/anchor) - API: - [Utilities](https://beartocode.github.io/carta/api/utilities) - [Core](https://beartocode.github.io/carta/api/core) @@ -145,3 +158,12 @@ npm run commit # or, if you have commitizen installed globally git cz ``` + +### Running docs + +If you want to preview the docs: + +``` +cd docs +npm run dev +``` diff --git a/docs/package.json b/docs/package.json index b8127ee..7a24ff5 100644 --- a/docs/package.json +++ b/docs/package.json @@ -15,9 +15,11 @@ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch" }, "devDependencies": { - "@sveltejs/adapter-auto": "^2.0.0", - "@sveltejs/adapter-static": "1.0.0-next.50", - "@sveltejs/kit": "^1.5.0", + "@sveltejs/adapter-auto": "^3.1.1", + "@sveltejs/adapter-static": "3.0.1", + "@sveltejs/kit": "^2.5.4", + "@sveltejs/package": "^2.3.0", + "@sveltejs/vite-plugin-svelte": "^3.0.2", "@types/flexsearch": "^0.7.6", "@types/katex": "^0.16.0", "autoprefixer": "^10.4.16", @@ -25,12 +27,12 @@ "rehype-autolink-headings": "^7.1.0", "rehype-slug": "^6.0.0", "sass": "^1.69.5", - "svelte": "^3.54.0 || ^4.0.0", - "svelte-check": "^3.0.1", + "svelte": "^4.2.12", + "svelte-check": "^3.6.7", "tailwindcss": "^3.3.5", "tslib": "^2.4.1", "typescript": "^5.0.0", - "vite": "^4.3.9" + "vite": "^5.1.6" }, "type": "module", "dependencies": { @@ -45,8 +47,8 @@ "clsx": "^2.0.0", "cmdk-sv": "^0.0.6", "flexsearch": "0.7.21", - "katex": "^0.16.7", - "radix-icons-svelte": "^1.2.1", + "iconify-icon": "^2.0.0", + "katex": "^0.16.10", "tailwind-merge": "^2.0.0" } } diff --git a/docs/src/lib/components/code/Code.svelte b/docs/src/lib/components/code/Code.svelte index f0b289b..8be7d1f 100644 --- a/docs/src/lib/components/code/Code.svelte +++ b/docs/src/lib/components/code/Code.svelte @@ -1,6 +1,4 @@ @@ -13,10 +11,10 @@ navigator.clipboard.writeText(elem.innerText); }} class=" - absolute right-4 top-[min(50%_,_32px)] -translate-y-1/2 transform - rounded p-2 hover:bg-neutral-800 hover:text-neutral-300 active:text-sky-300 + absolute right-4 top-[min(50%_,_32px)] aspect-square -translate-y-1/2 transform + rounded hover:bg-neutral-800 hover:text-neutral-300 active:text-sky-300 " > - + diff --git a/docs/src/lib/components/header-tracker/HeaderTracker.svelte b/docs/src/lib/components/header-tracker/HeaderTracker.svelte index f5a6b1d..d584c97 100644 --- a/docs/src/lib/components/header-tracker/HeaderTracker.svelte +++ b/docs/src/lib/components/header-tracker/HeaderTracker.svelte @@ -1,5 +1,7 @@ - + { + throttledHighlightHeader(); + debouncedHighlightHeader(); // So it is called at the end of the scroll event + }} +/>
{#each headers as header, i} {@const margin = Number(header.tagName.split('')[1]) - 1} - {@const nextHeader = headers[i + 1]} - {#if header.children[0] instanceof HTMLAnchorElement && header.children[0].href} - {#key scrollY} + {#key selectedHeaderIndex} + {#if header.children[0] instanceof HTMLAnchorElement && header.children[0].href} {header.innerText} - {/key} - {/if} + {/if} + {/key} {/each}
diff --git a/docs/src/lib/components/link/PluginLink.svelte b/docs/src/lib/components/link/PluginLink.svelte new file mode 100644 index 0000000..8318e91 --- /dev/null +++ b/docs/src/lib/components/link/PluginLink.svelte @@ -0,0 +1,22 @@ + + + + + diff --git a/docs/src/lib/components/mobile-sidebar/MobileSidebar.svelte b/docs/src/lib/components/mobile-sidebar/MobileSidebar.svelte index 220ed85..1ebb918 100644 --- a/docs/src/lib/components/mobile-sidebar/MobileSidebar.svelte +++ b/docs/src/lib/components/mobile-sidebar/MobileSidebar.svelte @@ -1,5 +1,4 @@
- + - + Close diff --git a/docs/src/lib/examples/DiscordExample.svelte b/docs/src/lib/examples/DiscordExample.svelte index 462bbd2..bfc048a 100644 --- a/docs/src/lib/examples/DiscordExample.svelte +++ b/docs/src/lib/examples/DiscordExample.svelte @@ -1,12 +1,13 @@ - + diff --git a/docs/src/lib/examples/GitHubExample.svelte b/docs/src/lib/examples/GitHubExample.svelte index 444f718..5de31c6 100644 --- a/docs/src/lib/examples/GitHubExample.svelte +++ b/docs/src/lib/examples/GitHubExample.svelte @@ -1,13 +1,14 @@ - + diff --git a/docs/src/lib/examples/MathStackExchangeExample.svelte b/docs/src/lib/examples/MathStackExchangeExample.svelte index 907ef09..c6057b1 100644 --- a/docs/src/lib/examples/MathStackExchangeExample.svelte +++ b/docs/src/lib/examples/MathStackExchangeExample.svelte @@ -1,13 +1,14 @@
- + {#key value} - + {/key}
diff --git a/docs/src/lib/examples/assets/PlusIcon.svelte b/docs/src/lib/examples/assets/PlusIcon.svelte new file mode 100644 index 0000000..aaa4ef9 --- /dev/null +++ b/docs/src/lib/examples/assets/PlusIcon.svelte @@ -0,0 +1,3 @@ +
+ +
diff --git a/docs/src/lib/search/index.ts b/docs/src/lib/search/index.ts index 511f862..7a1623f 100644 --- a/docs/src/lib/search/index.ts +++ b/docs/src/lib/search/index.ts @@ -1,4 +1,4 @@ -import { Document } from 'flexsearch'; +import flexsearch from 'flexsearch'; import type { SvelteComponent } from 'svelte'; export interface SearchResult { @@ -19,7 +19,7 @@ export type EnrichedSearchResult = SearchResult & { }; export async function initializeSearch() { - const indexedPages = new Document({ + const indexedPages = new flexsearch.Document({ tokenize: 'full', cache: true, context: true, diff --git a/docs/src/lib/styles/discord.scss b/docs/src/lib/styles/discord.scss index ad88fd9..60d8d3b 100644 --- a/docs/src/lib/styles/discord.scss +++ b/docs/src/lib/styles/discord.scss @@ -27,6 +27,7 @@ .carta-font-code { font-family: 'Fira Code', monospace; caret-color: white; + font-size: 1.1rem; } .carta-toolbar { @@ -41,10 +42,6 @@ height: 1.75rem; transform: translateX(-50%) translateY(-50%); } - - [class*='shj-lang-'] { - background: transparent; - } } // Plugin emoji @@ -79,3 +76,8 @@ background: $background-contrast; } } + +html.dark .shiki, +html.dark .shiki span { + color: var(--shiki-dark) !important; +} diff --git a/docs/src/lib/styles/github.scss b/docs/src/lib/styles/github.scss index b83daaf..7d2b961 100644 --- a/docs/src/lib/styles/github.scss +++ b/docs/src/lib/styles/github.scss @@ -28,15 +28,14 @@ .carta-font-code { font-family: 'Fira Code', monospace; caret-color: white; + font-size: 1.1rem; } .carta-toolbar { height: 2.5rem; background-color: $background-light; - border-bottom: 1px solid $border; - padding-right: 12px; border-top-left-radius: 0.5rem; border-top-right-radius: 0.5rem; @@ -51,6 +50,12 @@ } } + .carta-toolbar-left button, + .carta-toolbar-right, + .carta-filler { + border-bottom: 1px solid $border; + } + .carta-toolbar-left { & > *:first-child { border-top-left-radius: 0.5rem; @@ -62,10 +67,12 @@ font-size: 0.95rem; } - .carta-active { + button { height: 100%; + } + + .carta-active { background-color: $background; - transform: translateY(1px); color: white; border-right: 1px solid $border; @@ -77,8 +84,37 @@ } } - [class*='shj-lang-'] { - background: transparent; + .carta-toolbar-right { + padding-right: 12px; + } + + .carta-icons-menu { + padding: 8px; + border: 1px solid $border; + border-radius: 6px; + min-width: 180px; + background: $background; + + .carta-icon-full { + padding-left: 6px; + padding-right: 6px; + + margin-top: 2px; + &:first-child { + margin-top: 0; + } + + &:hover { + color: white; + background-color: $border; + } + + span { + margin-left: 6px; + color: white; + font-size: 0.85rem; + } + } } } @@ -160,3 +196,8 @@ text-overflow: ellipsis; } } + +html.dark .shiki, +html.dark .shiki span { + color: var(--shiki-dark) !important; +} diff --git a/docs/src/lib/styles/markdown.scss b/docs/src/lib/styles/markdown.scss index d42cd16..61eb539 100644 --- a/docs/src/lib/styles/markdown.scss +++ b/docs/src/lib/styles/markdown.scss @@ -43,9 +43,21 @@ } code { - @apply rounded; - &:not([class*='language-']) { - @apply bg-neutral-800 px-1 text-neutral-100; + @apply rounded bg-neutral-800 px-1 text-neutral-50; + } + + p, + h1, + h2, + h3 { + code { + @apply px-1; } } + + .carta-editor code { + font-family: 'Fira Code', monospace; + background: transparent; + padding: 0; + } } diff --git a/docs/src/lib/styles/math-stack-exchange.scss b/docs/src/lib/styles/math-stack-exchange.scss index 73ec59e..1354a4e 100644 --- a/docs/src/lib/styles/math-stack-exchange.scss +++ b/docs/src/lib/styles/math-stack-exchange.scss @@ -34,6 +34,7 @@ .carta-font-code { font-family: 'Fira Code', monospace; caret-color: white; + font-size: 1.1rem; } .carta-toolbar { @@ -64,9 +65,34 @@ .carta-toolbar-right { justify-content: flex-start; } + } - [class*='shj-lang-'] { - background: transparent; + .carta-icons-menu { + padding: 8px; + border: 1px solid $border; + border-radius: 6px; + min-width: 180px; + background: $background; + + .carta-icon-full { + padding-left: 6px; + padding-right: 6px; + + margin-top: 2px; + &:first-child { + margin-top: 0; + } + + &:hover { + color: white; + background-color: $border; + } + + span { + margin-left: 6px; + color: white; + font-size: 0.85rem; + } } } @@ -75,3 +101,8 @@ padding: 1rem; } } + +html.dark .shiki, +html.dark .shiki span { + color: var(--shiki-dark) !important; +} diff --git a/docs/src/lib/utils.ts b/docs/src/lib/utils.ts index eba19d8..d2b288e 100644 --- a/docs/src/lib/utils.ts +++ b/docs/src/lib/utils.ts @@ -54,3 +54,41 @@ export const flyAndScale = ( easing: cubicOut }; }; + +export const throttle = ( + fn: (...args: A) => R, + delay: number +): [(...args: A) => R | undefined, () => void] => { + let wait = false; + let timeout: undefined | number; + let cancelled = false; + + return [ + (...args: A) => { + if (cancelled) return undefined; + if (wait) return undefined; + + const val = fn(...args); + + wait = true; + + timeout = window.setTimeout(() => { + wait = false; + }, delay); + + return val; + }, + () => { + cancelled = true; + clearTimeout(timeout); + } + ]; +}; + +export function debounce(cb: (...args: T) => unknown, wait = 1000) { + let timeout: NodeJS.Timeout; + return (...args: T) => { + clearTimeout(timeout); + timeout = setTimeout(() => cb(...args), wait); + }; +} diff --git a/docs/src/pages/api/core.svelte.md b/docs/src/pages/api/core.svelte.md index 91dc87f..62163f9 100644 --- a/docs/src/pages/api/core.svelte.md +++ b/docs/src/pages/api/core.svelte.md @@ -17,9 +17,15 @@ new Carta({ }); ``` +### `gfmOptions` + +Type: `GfmOptions` + +GitHub Flavored Markdown options. + ### `extensions` -Type: `CartaExtension[]` +Type: `Extension[]` List of extensions(plugins) to use. @@ -34,13 +40,19 @@ Defaults to 300ms. Type: `DefaultShortcutId[] | true` -Remove shortcuts by id. You can use `true` to disable all of them. +Remove default shortcuts by id. You can use `true` to disable all of them. + +### `disableIcons` + +Type: `DefaultIconId[] | true` + +Remove default icons by id. You can use `true` to disable all of them. ### `disablePrefixes` Type: `DefaultPrefixId[] | true` -Remove prefixes by id. You can use `true` to disable all of them. +Remove default prefixes by id. You can use `true` to disable all of them. ### `historyOptions` @@ -66,15 +78,21 @@ Type: `(html: string) => void` HTML sanitizer. See [here]({base}/getting-started#sanitization) for more details. -### `labels` +### `shikiOptions` -Type: `Partial` +Type: `ShikiOptions` -Can be used to provide custom text for labels in the editor. +Highlighter(Shiki) options. -# `CartaEditor` options +### `theme` -List of options that can be used in the `` component. +Type: `Theme | DualTheme` + +Shiki theme to use to highlight Markdown. + +# `MarkdownEditor` options + +List of options that can be used in the `` component. ### `carta` @@ -126,9 +144,15 @@ Additional properties that will be used in the textarea used under the hood in t `class`, `placeholder` and `value` are not allowed. Use the corresponding editor properties instead. -# `CartaViewer` options +### `labels` -List of options that can be used in the `` component. +Type: `Partial` + +Can be used to provide custom text for labels in the editor. + +# `Markdown` options + +List of options that can be used in the `` component. ### `carta` diff --git a/docs/src/pages/api/extension.svelte.md b/docs/src/pages/api/extension.svelte.md index a871742..f39da6a 100644 --- a/docs/src/pages/api/extension.svelte.md +++ b/docs/src/pages/api/extension.svelte.md @@ -7,14 +7,14 @@ title: Extension import Code from '$lib/components/code/Code.svelte'; -# `CartaExtension` properties +# `Plugin` properties You can easily extend Carta by creating custom plugins. ```ts -const ext: CartaExtension = { +const ext: Plugin = { // ... }; @@ -25,11 +25,47 @@ const carta = new Carta({ -Here are all the `CartaExtension` properties: +Here are all the `Plugin` properties: -### `markedExtensions` +### `transformers` -List of marked extensions. For more information check out [Marked docs](https://marked.js.org/using_pro). +Type: `UnifiedTransformer` + +Remark or Rehype transformers. + +#### `UnifiedTransformer.execution` + +Type: `'sync' | 'async'` + +If you specify async, this transformer won't be available for SSR. + +#### `UnifiedTransformer.type` + +Type: `'remark' | 'rehype'` + +This determines at which step the transformer will operate, whether on Remark, on a Markdown-based syntax tree, or Rehype, on a HTML-based one. + +#### `UnifiedTransformer.transform` + +Type: `({ processor, carta }) => void` + +The actual processor, can be async if the execution is specified as such. + + + +```ts +{ + execution: 'sync', + type: 'rehype', + transform({ processor }) { + processor + .use(rehypeSlug) + .use(rehypeAutolinkHeadings); + } +} +``` + + ### `shortcuts` @@ -63,7 +99,7 @@ Set of keys, corresponding to the `e.key` of `KeyboardEvent`s, but lowercase. #### `KeyboardShortcut.action` -Type: `(input: CartaInput) => void` +Type: `(input: InputEnhancer) => void` Shortcut callback. @@ -73,14 +109,14 @@ Prevent saving the current state in history. ### `icons` -Type: `CartaIcon[]` +Type: `Icon[]` Additional toolbar icons. For example: ```ts -const icon: CartaIcon = { +const icon: Icon = { id: 'heading', action: (input) => input.toggleLinePrefix('###'), component: HeadingIcon @@ -89,19 +125,19 @@ const icon: CartaIcon = { -#### `CartaIcon.id` +#### `Icon.id` Type: `string` Id of the icon. -#### `CartaIcon.action` +#### `Icon.action` -Type: `(input: CartaInput) => void` +Type: `(input: InputEnhancer) => void` Click callback. -#### `CartaIcon.component` +#### `Icon.component` Type: `ComponentType` (SvelteComponent) @@ -162,15 +198,15 @@ const prefix: Prefix = { ### `listeners` -Type: `CartaListener[]` +Type: `Listener[]` Textarea event listeners. Has an additional `carta-render` and `carta-render-ssr` events keys. ```ts -const click: CartaListener = ['click', () => console.log('I was clicked!')]; -const render: CartaListener = [ +const click: Listener = ['click', () => console.log('I was clicked!')]; +const render: Listener = [ 'carta-render', (e) => { const carta = e.detail.carta; @@ -186,33 +222,39 @@ const render: CartaListener = [ ### `components` -Type: `CartaExtensionComponent[]` +Type: `ExtensionComponent[]` Additional components to be added to the editor or viewer. -#### `CartaExtensionComponent.component` +#### `ExtensionComponent.component` Type: `typeof SvelteComponentTyped` Svelte components that exports `carta: Carta` and all the other properties specified as the generic parameter and in `props`. -#### `CartaExtensionComponent.props` +#### `ExtensionComponent.props` Type: `T` Properties that will be handed to the component. -#### `CartaExtensionComponent.parent` +#### `ExtensionComponent.parent` Type: `MaybeArray<'editor' | 'input' | 'renderer' | 'preview'>` Where the element will be placed. +### `grammarRules` + +Type: `GrammarRule[]` + +Custom Markdown TextMate grammar rules for Shiki. They will be injected into the language. + ### `highlightRules` -Type: `ShjLanguageDefinition` +Type: `HighlightingRule[]` -Custom markdown highlighting rules. See [Speed-Highlight Wiki](https://github.com/speed-highlight/core/wiki/Create-or-suggest-new-languages) for more info. +Custom highlighting rules for ShiKi. They will be injected into the selected theme. ### `onLoad` diff --git a/docs/src/pages/api/utilities.svelte.md b/docs/src/pages/api/utilities.svelte.md index c641125..273796f 100644 --- a/docs/src/pages/api/utilities.svelte.md +++ b/docs/src/pages/api/utilities.svelte.md @@ -40,3 +40,52 @@ Svelte action that allows you to bind a specific element to the caret position.
``` + +## `Carta.highlighter` + +Get the Shiki highlighter. + +```ts +const highlighter = await carta.highlighter(); +const userTheme = carta.theme; +``` + +Here are some other highlight related utilities: + +### `isBundleLanguage` + +Checks if a language is a bundled language. + +```ts +export const isBundleLanguage = (lang: string): lang is BundledLanguage; +``` + +### `isBundleTheme` + +Checks if a theme is a bundled theme. + +```ts +export const isBundleTheme = (theme: string): theme is BundledTheme; +``` + +### `isDualTheme` + +Checks if a theme is a dual theme. + +```ts +export const isDualTheme = (theme: Theme | DualTheme): theme is DualTheme; +``` + +### `isSingleTheme` + +```ts +export const isSingleTheme = (theme: Theme | DualTheme): theme is Theme; +``` + +### `isThemeRegistration` + +Checks if a theme is a theme registration. + +```ts +export const isThemeRegistration = (theme: Theme): theme is ThemeRegistration; +``` diff --git a/docs/src/pages/community-plugins.svelte.md b/docs/src/pages/community-plugins.svelte.md new file mode 100644 index 0000000..c30ce68 --- /dev/null +++ b/docs/src/pages/community-plugins.svelte.md @@ -0,0 +1,83 @@ +--- +title: Community Plugins +section: Overview +--- + + + +Here are is a list of several plugins developed by the community: + + + +### `carta-plugin-video` + + + +> Adds ability to render online video from Youtube or Vimeo. + + + +``` +npm i carta-plugin-video +``` + + + + + +### `carta-plugin-imsize` + + + +> Adds ability to render images in specific sizes. + + + +``` +npm i carta-plugin-imsize +``` + + + + + +### `carta-plugin-ins-del` + + + +> `` and `` tags support + + + +``` +npm i carta-plugin-ins-del +``` + + + + + +### `carta-plugin-subscript` + + + +> Adds ability to render subscripts and superscripts. + + + +``` +npm i carta-plugin-subscript +``` + + diff --git a/docs/src/pages/editing-styles.svelte.md b/docs/src/pages/editing-styles.svelte.md index 1752008..81736ed 100644 --- a/docs/src/pages/editing-styles.svelte.md +++ b/docs/src/pages/editing-styles.svelte.md @@ -43,11 +43,68 @@ While the core styles are embedded in the Svelte components, the others can be s ### Using multiple themes -By using the `theme` property in the editor you can differentiate the themes of multiple editors. +By using the `theme` property in `` you can differentiate the themes of multiple editors. -## Changing Markdown color theme +## Dark mode -Carta uses [Speed Highlight JS](https://github.com/speed-highlight/core) for syntax highlighting. Two default themes are included in the core package, `light.css` and `dark.css`, and others can be found on the Speed Highlight [GitHub](https://github.com/speed-highlight/core/tree/main/src/themes), but you can also easily create your own. +When using dark mode, there are two different themes that have to be changed: the editor theme and the one used for syntax highlighting: + + + +```css +/* Editor dark mode */ +/* Only if you are using the default theme */ +html.dark .carta-theme__default { + --border-color: var(--border-color-dark); + --selection-color: var(--selection-color-dark); + --focus-outline: var(--focus-outline-dark); + --hover-color: var(--hover-color-dark); + --caret-color: var(--caret-color-dark); + --text-color: var(--text-color-dark); +} + +/* Code dark mode */ +/* Only if you didn't specify a custom code theme */ +html.dark .shiki, +html.dark .shiki span { + color: var(--shiki-dark) !important; +} +``` + + + +## Changing Markdown input color theme + +Carta uses [Shiki](https://shiki.matsu.io/) for syntax highlighting. Two default themes are included in the core package, which are set as a [dual theme](https://shiki.matsu.io/guide/dual-themes) to support light and dark mode. If you plan to use a custom one with light/dark modes, make sure to use a dual theme as well. + +You can change theme in the options: + + + +```ts +const carta = new Carta({ + // ... + theme: 'github-dark' +}); +``` + + + +If you use a [custom theme](https://shiki.matsu.io/guide/load-theme)(or a custom language), you need to provide it inside the options, so that it gets loaded into the highlighter: + + + +```ts +const carta = new Carta({ + // ... + shikiOptions: { + langs: // ... + themes: // ... + } +}) +``` + + ## Markdown stylesheets diff --git a/docs/src/pages/getting-started.svelte.md b/docs/src/pages/getting-started.svelte.md index 71ed52d..81b7662 100644 --- a/docs/src/pages/getting-started.svelte.md +++ b/docs/src/pages/getting-started.svelte.md @@ -37,9 +37,8 @@ Setup a basic editor: ```svelte - + ``` @@ -68,7 +68,7 @@ Or, if you just want to render content: ```svelte - + ``` @@ -93,16 +93,16 @@ Since Carta operates both on the server and the client, you'd need a sanitizer a ```svelte - + ``` diff --git a/docs/src/pages/introduction.svelte.md b/docs/src/pages/introduction.svelte.md index 27450dd..12e0af9 100644 --- a/docs/src/pages/introduction.svelte.md +++ b/docs/src/pages/introduction.svelte.md @@ -5,20 +5,21 @@ section: Overview -> Swiftly edit and render Markdown, with no overhead. +> Modern, lightweight, powerful Markdown Editor. Carta is a lightweight, fast and extensible Svelte Markdown editor and viewer, designed for flexibility. It works natively in SvelteKit, and supports Server Side Rendering. -## Core Features +## Features -- **Lightweight**: no code editor is included, just a textarea with syntax highlighting, with Markdown related utilities. -- **SSR compatible**: works great with SvelteKit. -- **Keyboard shortcuts**: extensible and configurable. -- **Toolbar**: add or remove buttons according to your needs. -- **Plugins friendly**: easily create your own extension. +- ๐ŸŒˆ Markdown syntax highlighting ([Shiki](https://shiki.style/)); +- ๐Ÿ› ๏ธ Toolbar (extensible); +- โŒจ๏ธ Keyboard **shortcuts** (extensible); +- ๐Ÿ“ฆ Supports **[+150 plugins](https://github.com/remarkjs/remark/blob/main/doc/plugins.md#list-of-plugins)** thanks to remark. +- ๐Ÿ”€ Scroll sync; +- โœ… Accessibility friendly; +- ๐Ÿ–ฅ๏ธ **SSR** compatible; ## Official Plugins @@ -30,7 +31,7 @@ Carta comes with a set of official plugins for the most common use cases. - + Math Support for KaTex expressions. @@ -38,7 +39,7 @@ Carta comes with a set of official plugins for the most common use cases. - + Code Code blocks syntax highlighting. @@ -46,7 +47,7 @@ Carta comes with a set of official plugins for the most common use cases. - + Emoji Embed emojis in Markdown. @@ -54,7 +55,7 @@ Carta comes with a set of official plugins for the most common use cases. - + Slash Support for slash commands. @@ -62,7 +63,7 @@ Carta comes with a set of official plugins for the most common use cases. - + TikZ Support for TikZ/PgfPlots diagrams. @@ -70,12 +71,28 @@ Carta comes with a set of official plugins for the most common use cases. - + Attachment Handle text attachments. + + + +Anchor +Add anchor links to headings. + + + + + + +Community Plugins +Explore plugins from the community. + + + ## Examples @@ -88,7 +105,7 @@ A list of examples inspired by popular platforms. - + GitHub Inspired by GitHub. @@ -96,7 +113,7 @@ A list of examples inspired by popular platforms. - + Discord Inspired by Discord. @@ -104,7 +121,7 @@ A list of examples inspired by popular platforms. - + Math Stack Exchange Inspired by Math Stack Exchange. diff --git a/docs/src/pages/migration.svelte.md b/docs/src/pages/migration.svelte.md new file mode 100644 index 0000000..72d25dc --- /dev/null +++ b/docs/src/pages/migration.svelte.md @@ -0,0 +1,57 @@ +--- +title: Migration Guide +section: Overview +--- + +# Major Changes + +## Removal of Marked + +Marked has been replaced with a combination of Unified, Remark and Rehype. If you previously used a custom plugin with it, you'll have to update it manually. Otherwise, all builtin plugins have already been updated. Make sure to **update** them! + +Some plugins now have a different implementation and their options have changed. Those plugins are [plugin-math](https://beartocode.github.io/carta/plugins/math) and [plugin-anchor](https://beartocode.github.io/carta/plugins/anchor). + +## Syntax highlighter update + +SpeedHighlight has been replaced with [Shiki](https://shiki.matsu.io/). It now offers support for more languages, themes, and extensibility. + +Make sure to remove previous themes imports, as Shiki uses JS based ones. + +```ts +import 'carta-md/light.css'; // ๐Ÿ‘ˆ To be removed! +``` + +And also update the default theme. Previous based selectors should be removed: + +```css +/* ๐Ÿ‘‡ To be removed! */ +[class*='shj-lang-'] { + /* ... */ +} +``` + +## Removed verbose prefixes + +Many exports have been renamed to make them less verbose: + +- `CartaEditor` -> `MarkdownEditor` (old one still supported); +- `CartaRenderer` -> `Markdown` (old one still supported); +- `CartaEvent` -> `Event`; +- `CartaEventType` -> `EventType`; +- `CartaExtension` -> `Plugin`; +- `CartaExtensionComponent` -> `ExtensionComponent`; +- `CartaOptions` -> `Options`; +- `CartaHistory` -> `TextAreaHistory`; +- `CartaHistoryOptions` -> `TextAreaHistoryOptions`; +- `CartaIcon` -> `Icon`; +- `CartaListener` -> `Listener`; +- `CartaInput` -> `InputEnhancer`; +- `CartaRenderer` -> `Renderer`; +- `CartaLabels` -> `Labels`; + +# Minor Changes + +- If you don't use a sanitizer, you need to explicitly set it to `false`; +- Removed deprecated option `cartaRef` and `shjRef` for extensions; +- Removed deprecated options `postProcess` for `plugin-tikz`; +- `Carta.options` are no longer available. diff --git a/docs/src/pages/plugins/anchor.svelte.md b/docs/src/pages/plugins/anchor.svelte.md new file mode 100644 index 0000000..4b17150 --- /dev/null +++ b/docs/src/pages/plugins/anchor.svelte.md @@ -0,0 +1,70 @@ +--- +section: Plugins +title: Anchor +--- + + + +This plugin adds `id` attributes and permalinks to headings. + +## Installation + + + +``` +npm i @cartamd/plugin-anchor +``` + + + +## Setup + +### Styles + +Import the default theme, or create you own: + + + +```ts +import '@cartamd/plugin-anchor/default.css'; +``` + + + +### Extension + + + +```svelte + + + +``` + + + +## Options + +Here are the options you can pass to `anchor()`: + +```ts +export interface AnchorExtensionOptions { + /** + * rehype-slug options. + */ + slug?: SlugOptions; + /** + * rehype-autolink-headings options. + */ + autolink?: AutolinkOptions; +} +``` diff --git a/docs/src/pages/plugins/attachment.svelte.md b/docs/src/pages/plugins/attachment.svelte.md index 2543c3d..bbd0eca 100644 --- a/docs/src/pages/plugins/attachment.svelte.md +++ b/docs/src/pages/plugins/attachment.svelte.md @@ -35,7 +35,7 @@ import '@cartamd/plugin-attachment/default.css'; ```svelte - + ``` diff --git a/docs/src/pages/plugins/code.svelte.md b/docs/src/pages/plugins/code.svelte.md index 1462531..d1c2c82 100644 --- a/docs/src/pages/plugins/code.svelte.md +++ b/docs/src/pages/plugins/code.svelte.md @@ -7,8 +7,7 @@ title: Code import Code from '$lib/components/code/Code.svelte'; -This plugin adds support for code blocks **syntax highlighting**. -This is done using [Speed-highlight JS](https://github.com/speed-highlight/core), which supports dynamic imports. This way, languages definitions are only imported at the moment of need. +This plugin adds support for code blocks **syntax highlighting**. It uses the same highlighter from the core package(Shiki). ## Installation @@ -36,34 +35,34 @@ import '@cartamd/plugin-code/default.css'; ### Using the default highlighter -Carta comes with a default highlighter that matches the one used to highlight Markdown in the editor and is used by default. -The theme is the same as the one used in the main Carta package (`carta-md/light.css` or `carta-md/dark.css`). -[Here](https://github.com/speed-highlight/core/tree/main/src/themes) you can find other themes. - -### Using a custom highlighter - -You can also provide a custom highlighter, that can be either sync or async. +Carta comes with a default highlighter that matches the one used to highlight markdown in the editor and is used by default (Shiki). If you want to use a theme different from the one used to highlight Markdown, you can specify it in the options. ```ts -code({ - customHighlight: { - highlighter: (code, lang) => myCustomHighlighter(code, lang), - langPrefix: 'my-highlighter-' - } +const carta = new Carta({ + // ... + extensions: [ + code({ + theme: 'ayu-light' + }) + ] }); ``` +### Using a custom highlighter + +It is no longer possible to specify a custom highlighter in this plugin. However, there are many different [Remark plugins](https://github.com/remarkjs/remark/blob/main/doc/plugins.md#list-of-plugins) that provide syntax highlighting. + ### Extension ```svelte - + ``` ## Options -Here are the options you can pass to `code()`: - -```ts -interface CodeExtensionOptions { - /** - * Default language when none is provided. - */ - defaultLanguage?: string; - /** - * Whether to autodetect a language when none is provided. - * Overwritten by `defaultLanguage`. - */ - autoDetect?: string; - /** - * Line numbering. - * @defaults false. - */ - lineNumbering?: boolean; - - /** - * Options for custom syntax highlighting. - */ - customHighlight?: { - /** - * Custom highlight function. Beware that you'll have to provide your own styles. - * This function needs to convert a string of code into html. - */ - highlighter: (code: string, lang: string) => string | Promise; - /** - * The language tag found immediately after the code block opening marker is - * appended to this to form the class attribute added to the `` element. - */ - langPrefix: string; - }; -} -``` +The options you can pass to `code()` extend the ones provided by [Shiki](https://shiki.matsu.io/guide/transformers). diff --git a/docs/src/pages/plugins/emoji.svelte.md b/docs/src/pages/plugins/emoji.svelte.md index df7fa01..e7162bd 100644 --- a/docs/src/pages/plugins/emoji.svelte.md +++ b/docs/src/pages/plugins/emoji.svelte.md @@ -39,7 +39,7 @@ import '@cartamd/plugin-emoji/default.css'; ```svelte - + ``` diff --git a/docs/src/pages/plugins/math.svelte.md b/docs/src/pages/plugins/math.svelte.md index a7691fd..7d4cc5c 100644 --- a/docs/src/pages/plugins/math.svelte.md +++ b/docs/src/pages/plugins/math.svelte.md @@ -5,7 +5,7 @@ title: Math - + ``` @@ -103,7 +103,7 @@ Pythagorean theorem: $a^2+b^2=c^2$ - +
@@ -119,7 +119,7 @@ $$ - + ## Options @@ -131,7 +131,6 @@ interface MathExtensionOptions { * Options for inline katex, eg: $a^2+b^2=c^2$ */ inline?: { - katexOptions?: KatexOptions; /** * @default control+m */ @@ -144,23 +143,18 @@ interface MathExtensionOptions { * $$ */ block?: { - /** - * Tag the generated katex will be put into. Must have `display: block`. - */ - tag?: string; - /** - * Whether to center the generated expression. - * @default true - */ - center?: boolean; - /** - * Class for generated katex. - */ - class?: string; /** * @default ctrl+shift+m */ shortcut?: Set; - katexOptions?: KatexOptions; }; + /** + * Options for remark-math + */ + remarkMath?: RemarkMathOptions; + /** + * Options for rehype-katex + */ + rehypeKatex?: RehypeKatexOptions; +} ``` diff --git a/docs/src/pages/plugins/slash.svelte.md b/docs/src/pages/plugins/slash.svelte.md index cc029cb..5593a72 100644 --- a/docs/src/pages/plugins/slash.svelte.md +++ b/docs/src/pages/plugins/slash.svelte.md @@ -39,7 +39,7 @@ import '@cartamd/plugin-slash/default.css'; ```svelte - + ``` diff --git a/docs/src/pages/plugins/tikz.svelte.md b/docs/src/pages/plugins/tikz.svelte.md index 7557c42..c878f8a 100644 --- a/docs/src/pages/plugins/tikz.svelte.md +++ b/docs/src/pages/plugins/tikz.svelte.md @@ -20,7 +20,7 @@ npm i @cartamd/plugin-tikz ## Important Notes 1. This plugin requires the import of a **heavy** library (~7Mb), which is dynamically imported at runtime; -2. Generated images are **not ssr compatible**, as they are rendered in the browser; +2. Generated images are **not SSR compatible**, as they are rendered in the browser; 3. You need to update your sanitizer to allow the specific tag: `
`. ## Setup @@ -29,7 +29,7 @@ npm i @cartamd/plugin-tikz ```svelte - + ``` diff --git a/docs/src/pages/using-components.svelte.md b/docs/src/pages/using-components.svelte.md new file mode 100644 index 0000000..f301c51 --- /dev/null +++ b/docs/src/pages/using-components.svelte.md @@ -0,0 +1,217 @@ +--- +title: Using Svelte Components +section: Overview +--- + + + +Svelte components can be embedded into the rendered HTML to make certain elements interactive. However, they require a bit more work, as Remark is configured to only render static HTML. To get around this, the idea is to do the following: + +1. Create a Unified plugin to isolate the targeted element; +2. Replace all the elements with the component, after every render. + +## Example + +Let's say we want to replace all hashtags, such as `#something`, with a custom component. Here is as example of how that could be achieved. + +### Parsing the hashtags + +First things first: we need to tell the parser that we want to parse hashtags as custom elements. To do this, it's useful to first install the following packages: + + + +```shell +npm i unist-util-visit +# Types +npm i -D unified hast +``` + + + +Let's create a Unified plugin. The basic structure of a plugin is the following: + + + +```ts +import type { Plugin as UnifiedPlugin } from 'unified' +import { SKIP, visit } from 'unist-util-visit' + +const unifiedPlugin: UnifiedPlugin<[], hast.Root> = () => { + return function (tree) { + // Visit every node in the syntax tree + visit(tree, (node, index, parent) => { + // Do something with the node + } + } +} +``` + + + +We now want to parse text nodes, so that words such as `#pizza` and `#123` are separated from the rest. This is a possible implementation: + + + +```ts +const unifiedPlugin: UnifiedPlugin<[], hast.Root> = () => { + return function (tree) { + visit(tree, (node, index, parent) => { + // Skip code blocks and their children + if (node.type === 'element' && node.tagName === 'pre') return [SKIP]; + // Skip non-text nodes + if (node.type !== 'text') return; + const text = node as hast.Text; + + // Parse the text node and replace hashtags with spans + const regex = /#(\w+)/g; + const children: (hast.Element | hast.Text)[] = []; + let lastIndex = 0; + let match; + while ((match = regex.exec(text.value))) { + const before = text.value.slice(lastIndex, match.index); + if (before) { + children.push({ type: 'text', value: before }); + } + children.push({ + type: 'element', + tagName: 'span', + properties: { type: 'hashtag', value: match[1] }, + children: [{ type: 'text', value: match[0] }] + }); + lastIndex = regex.lastIndex; + } + if (lastIndex < text.value.length) { + children.push({ type: 'text', value: text.value.slice(lastIndex) }); + } + + // Replace the text node with all the children + parent!.children.splice(index!, 1, ...children); + + // Skip the children + return [SKIP, index! + children.length]; + }); + }; +}; +``` + + + +If you want a more in-depth guide on writing Unified plugins, you can check out the official [documentation](https://unifiedjs.com/learn/guide/create-a-plugin/). + +Notice that hashtags are now replaced with the following: + +```html + #pizza +``` + +### Configuring the transformer + +Unified plugins need to be wrapped inside a `UnifiedTransformer` type, to be able to be used in Carta. + + + +```ts +import type { UnifiedTransformer } from 'carta-md'; + +const hashtagTransformer: UnifiedTransformer<'sync'> = { + execution: 'sync', // Sync, since the plugin is synchronous + type: 'rehype', // Rehype, since it operates on HTML + transform({ processor }) { + processor.use(unifiedPlugin); + } +}; +``` + + + +### Mounting the components + +We now want to replace the generated hashtag placeholders with the following element: + + + +```svelte + + + + +``` + + + +To do that, we create a listener that: + +1. Finds all the previous placeholders; +2. Mounts the component next to them; +3. Removes the placeholders. + + + +```ts +import type { Listener } from 'carta-md'; +import Hashtag from './Hashtag.svelte'; + +const convertHashtags: Listener<'carta-render'> = [ + 'carta-render', + function onRender({ detail: { carta } }) { + const rendererContainer = carta.renderer?.container; + if (!rendererContainer) return; + + // Find all hashtag spans and replace them with Svelte components + const hashtagSpans = rendererContainer.querySelectorAll('span[type="hashtag"]'); + for (const span of hashtagSpans) { + const hashtag = span.getAttribute('value') ?? ''; + + new Hashtag({ + target: span.parentElement!, + anchor: span, + props: { value: hashtag } + }); + + span.remove(); + } + } +]; +``` + + + +### Using the plugin + +Let's now create a Plugin with the transformer and the listener: + + + +```ts +import type { Plugin } from 'carta-md'; + +export const hashtag = (): Plugin => ({ + transformers: [hashtagTransformer], + listeners: [convertHashtags] +}); +``` + + + +We can now use the plugin with the following: + +```ts +import { Carta } from 'carta-md'; + +const carta = new Carta({ + // ... + extensions: [hashtag()] +}); +``` + +You can find the example source code [here](https://github.com/BearToCode/svelte-in-carta-example). diff --git a/docs/src/routes/+layout.svelte b/docs/src/routes/+layout.svelte index 91e45c3..723f83f 100644 --- a/docs/src/routes/+layout.svelte +++ b/docs/src/routes/+layout.svelte @@ -4,8 +4,8 @@ import Navbar from '$lib/components/navbar/Navbar.svelte'; import Sidebar from '$lib/components/sidebar/Sidebar.svelte'; import Footer from '$lib/components/footer/Footer.svelte'; - import '../app.postcss'; import { base } from '$app/paths'; + import '../app.postcss'; @@ -24,6 +24,6 @@
-
diff --git a/docs/src/routes/+layout.ts b/docs/src/routes/+layout.ts index 189f71e..a4c5a95 100644 --- a/docs/src/routes/+layout.ts +++ b/docs/src/routes/+layout.ts @@ -1 +1,3 @@ +import 'iconify-icon'; // Register iconify web components + export const prerender = true; diff --git a/docs/src/routes/[...slug]/+page.svelte b/docs/src/routes/[...slug]/+page.svelte index a0df694..449ea7b 100644 --- a/docs/src/routes/[...slug]/+page.svelte +++ b/docs/src/routes/[...slug]/+page.svelte @@ -2,10 +2,10 @@ import type { PageData } from './$types'; import { onMount, type SvelteComponent } from 'svelte'; import { page } from '$app/stores'; + import { base } from '$app/paths'; import '$lib/styles/markdown.scss'; import '$lib/styles/coldark.scss'; - import { base } from '$app/paths'; export let data: PageData; diff --git a/docs/svelte.config.js b/docs/svelte.config.js index 0cdd882..3f812da 100644 --- a/docs/svelte.config.js +++ b/docs/svelte.config.js @@ -1,7 +1,7 @@ import mdsvexConfig from './mdsvex.config.js'; import adapter from '@sveltejs/adapter-static'; import { mdsvex } from 'mdsvex'; -import { vitePreprocess } from '@sveltejs/kit/vite'; +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; /** @type {import('@sveltejs/kit').Config} */ const config = { diff --git a/package.json b/package.json index 26acf64..85b8d68 100644 --- a/package.json +++ b/package.json @@ -53,13 +53,5 @@ "@semantic-release/npm", "@semantic-release/github" ] - }, - "pnpm": { - "overrides": { - "@adobe/css-tools@<4.3.1": ">=4.3.1", - "semver@>=7.0.0 <7.5.2": ">=7.5.2", - "postcss@<8.4.31": ">=8.4.31", - "undici@<5.26.2": ">=5.26.2" - } } } diff --git a/packages/carta-md/README.md b/packages/carta-md/README.md index aadc55b..2b4495b 100644 --- a/packages/carta-md/README.md +++ b/packages/carta-md/README.md @@ -1,31 +1,25 @@ - +[![Carta.png](https://i.postimg.cc/nV6DMXKM/Carta.png)](https://beartocode.github.io/carta/) -
- -
Carta
-
Swiftly edit and render Markdown, with no overhead.
+

Carta

+
Modern, lightweight, powerful Markdown Editor.

@@ -34,38 +28,81 @@ # Introduction -Carta is a **lightweight**, **fast** and **extensible** Svelte Markdown editor and viewer, based on [Marked](https://github.com/markedjs/marked). Check out the [examples](http://beartocode.github.io/carta/examples) to see it in action. -Differently from most editors, Carta includes neither ProseMirror nor CodeMirror, allowing for an extremely small bundle size and fast loading time. +> **NOTE**: +> Carta has recently been updated to `v4`, which features numerous major changes. +> +> Follow the [Migration Guide](http://beartocode.github.io/carta/migration) to update your project. + +Carta is a **lightweight**, **fast** and **extensible** Svelte Markdown editor and viewer. It is powered by [unified](https://github.com/unifiedjs/unified), [remark](https://github.com/remarkjs/remark) and [rehype](https://github.com/rehypejs/rehype). Check out the [examples](http://beartocode.github.io/carta/examples) to see it in action. +Differently from most editors, Carta does not include a code editor, but it is _just_ a textarea with syntax highlighting, shortcuts and more. ## Features -- Keyboard **shortcuts** (extensible); -- Toolbar (extensible); -- Markdown syntax highlighting; -- Scroll sync; -- **SSR** compatible; -- **Katex** support (plugin); -- **Slash** commands (plugin); -- **Emojis**, with included search (plugin); -- **Tikz** support(plugin); -- **Attachment** support(plugin); -- Code blocks **syntax highlighting** (plugin). +- ๐ŸŒˆ Markdown syntax highlighting ([Shiki](https://shiki.style/)); +- ๐Ÿ› ๏ธ Toolbar (extensible); +- โŒจ๏ธ Keyboard **shortcuts** (extensible); +- ๐Ÿ“ฆ Supports **[150+ plugins](https://github.com/remarkjs/remark/blob/main/doc/plugins.md#list-of-plugins)** thanks to remark; +- ๐Ÿ”€ Scroll sync; +- โœ… Accessibility friendly; +- ๐Ÿ–ฅ๏ธ **SSR** compatible; +- โš—๏ธ **KaTeX** support (plugin); +- ๐Ÿ”จ **Slash** commands (plugin); +- ๐Ÿ˜„ **Emojis**, with included search (plugin); +- โœ๏ธ **TikZ** support (plugin); +- ๐Ÿ“‚ **Attachment** support (plugin); +- โš“ **Anchor** links in headings (plugin); +- ๐ŸŒˆ Code blocks **syntax highlighting** (plugin). + +## Packages + +| Package | Status | Docs | +| ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | +| [carta-md](https://www.npmjs.com/package/carta-md) | ![carta-md](https://img.shields.io/npm/v/carta-md) | [/](https://beartocode.github.io/carta/introduction) | +| [plugin-math](https://www.npmjs.com/package/@cartamd/plugin-math) | ![plugin-math](https://img.shields.io/npm/v/@cartamd/plugin-math) | [/plugins/math](https://beartocode.github.io/carta/plugins/math) | +| [plugin-code](https://www.npmjs.com/package/@cartamd/plugin-code) | ![plugin-code](https://img.shields.io/npm/v/@cartamd/plugin-code) | [/plugins/code](https://beartocode.github.io/carta/plugins/code) | +| [plugin-emoji](https://www.npmjs.com/package/@cartamd/plugin-emoji) | ![plugin-emoji](https://img.shields.io/npm/v/@cartamd/plugin-emoji) | [/plugins/emoji](https://beartocode.github.io/carta/plugins/emoji) | +| [plugin-slash](https://www.npmjs.com/package/@cartamd/plugin-slash) | ![plugin-slash](https://img.shields.io/npm/v/@cartamd/plugin-slash) | [/plugins/slash](https://beartocode.github.io/carta/plugins/slash) | +| [plugin-tikz](https://www.npmjs.com/package/@cartamd/plugin-tikz) | ![plugin-tikz](https://img.shields.io/npm/v/@cartamd/plugin-tikz) | [/plugins/tikz](https://beartocode.github.io/carta/plugins/tikz) | +| [plugin-attachment](https://www.npmjs.com/package/@cartamd/plugin-attachment) | ![plugin-attachment](https://img.shields.io/npm/v/@cartamd/plugin-attachment) | [/plugins/attachment](https://beartocode.github.io/carta/plugins/attachment) | +| [plugin-anchor](https://www.npmjs.com/package/@cartamd/plugin-anchor) | ![plugin-anchor](https://img.shields.io/npm/v/@cartamd/plugin-anchor) | [/plugins/anchor](https://beartocode.github.io/carta/plugins/anchor) | + +## Community plugins + +| Plugin | Description | +| ----------------------------------------------------------------------------- | ---------------------------------- | +| [carta-plugin-video](https://github.com/maisonsmd/carta-plugin-video) | Render online videos | +| [carta-plugin-imsize](https://github.com/maisonsmd/carta-plugin-imsize) | Render images in specific sizes | +| [carta-plugin-subscript](https://github.com/maisonsmd/carta-plugin-subscript) | Render subscripts and superscripts | +| [carta-plugin-ins-del](https://github.com/maisonsmd/carta-plugin-ins-del) | `` and `` tags support | # Getting started -> **Warning** +> **WARNING** > Sanitization is not dealt with by Carta. You need to provide a `sanitizer` in the options. > Common sanitizers are [isomorphic-dompurify](https://www.npmjs.com/package/isomorphic-dompurify) (suggested) and [sanitize-html](https://www.npmjs.com/package/sanitize-html). +> Checkout the documentation for an example. + +## Installation + +Core package: + +``` +npm i carta-md +``` + +Plugins: + +``` +npm i @cartamd/plugin-name +``` ## Basic configuration ```svelte - + ``` @@ -99,7 +137,33 @@ For the full documentation, examples, guides and more checkout the [website](htt - [Slash](https://beartocode.github.io/carta/plugins/slash) - [TikZ](https://beartocode.github.io/carta/plugins/tikz) - [Attachment](https://beartocode.github.io/carta/plugins/attachment) + - [Anchor](https://beartocode.github.io/carta/plugins/anchor) - API: - [Utilities](https://beartocode.github.io/carta/api/utilities) - [Core](https://beartocode.github.io/carta/api/core) - [Extension](https://beartocode.github.io/carta/api/extension) + +# Contributing & Development + +Every contribution is well accepted. If you have a feature request you can open a new issue. + +This package uses a [pnpm workspace](https://pnpm.io/workspaces), so pnpm is required to download and put everything together properly. + +### Committing + +This repository is [commitizen](https://github.com/commitizen/cz-cli) friendly. To commit use: + +``` +npm run commit +# or, if you have commitizen installed globally +git cz +``` + +### Running docs + +If you want to preview the docs: + +``` +cd docs +npm run dev +``` diff --git a/packages/carta-md/package.json b/packages/carta-md/package.json index 30eaff3..bc074ef 100644 --- a/packages/carta-md/package.json +++ b/packages/carta-md/package.json @@ -14,10 +14,7 @@ "svelte": "./dist/index.js", "import": "./dist/index.js" }, - "./default.css": "./dist/default.css", - "./default-theme.css": "./dist/default.css", - "./light.css": "./dist/light.css", - "./dark.css": "./dist/dark.css" + "./default.css": "./dist/default.css" }, "version": "3.0.0", "scripts": { @@ -27,18 +24,23 @@ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch" }, "devDependencies": { - "@sveltejs/adapter-auto": "^1.0.0-next.90", - "@sveltejs/kit": "^1.0.0-next.587", - "@sveltejs/package": "^2.0.2", - "svelte-check": "^3.2.0", + "@sveltejs/adapter-auto": "^3.1.1", + "@sveltejs/kit": "^2.5.4", + "@sveltejs/package": "^2.3.0", + "@sveltejs/vite-plugin-svelte": "^3.0.2", + "svelte-check": "^3.6.7", "tslib": "^2.4.1", "typescript": "^5.1.6", - "typescript-plugin-css-modules": "^5.0.1" + "vite": "^5.1.6" }, "type": "module", "dependencies": { - "@speed-highlight/core": "1.2.2", - "marked": "^9.1.5" + "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": { "svelte": "^3.54.0 || ^4.0.0" diff --git a/packages/carta-md/src/lib/CartaViewer.svelte b/packages/carta-md/src/lib/Markdown.svelte similarity index 76% rename from packages/carta-md/src/lib/CartaViewer.svelte rename to packages/carta-md/src/lib/Markdown.svelte index 87ba240..25d3971 100644 --- a/packages/carta-md/src/lib/CartaViewer.svelte +++ b/packages/carta-md/src/lib/Markdown.svelte @@ -1,6 +1,6 @@ +
- + > + {@html highlighted} +