@@ -34,24 +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;
-- 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;
-- 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
@@ -80,6 +80,7 @@ Differently from most editors, Carta includes neither ProseMirror nor CodeMirror
> [!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
@@ -99,11 +100,9 @@ npm i @cartamd/plugin-name
```svelte
-
+
```
diff --git a/docs/package.json b/docs/package.json
index 1fd851b..7a24ff5 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -47,8 +47,8 @@
"clsx": "^2.0.0",
"cmdk-sv": "^0.0.6",
"flexsearch": "0.7.21",
+ "iconify-icon": "^2.0.0",
"katex": "^0.16.10",
- "radix-icons-svelte": "^1.2.1",
"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
+ }}
+/>
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/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 fdc2798..7d2b961 100644
--- a/docs/src/lib/styles/github.scss
+++ b/docs/src/lib/styles/github.scss
@@ -28,6 +28,7 @@
.carta-font-code {
font-family: 'Fira Code', monospace;
caret-color: white;
+ font-size: 1.1rem;
}
.carta-toolbar {
@@ -115,10 +116,6 @@
}
}
}
-
- [class*='shj-lang-'] {
- background: transparent;
- }
}
// Plugin emoji
@@ -199,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 180786f..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,10 +65,6 @@
.carta-toolbar-right {
justify-content: flex-start;
}
-
- [class*='shj-lang-'] {
- background: transparent;
- }
}
.carta-icons-menu {
@@ -104,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 11ce208..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.
@@ -72,9 +78,21 @@ Type: `(html: string) => void`
HTML sanitizer. See [here]({base}/getting-started#sanitization) for more details.
-# `CartaEditor` options
+### `shikiOptions`
-List of options that can be used in the `` component.
+Type: `ShikiOptions`
+
+Highlighter(Shiki) options.
+
+### `theme`
+
+Type: `Theme | DualTheme`
+
+Shiki theme to use to highlight Markdown.
+
+# `MarkdownEditor` options
+
+List of options that can be used in the `` component.
### `carta`
@@ -128,13 +146,13 @@ instead.
### `labels`
-Type: `Partial`
+Type: `Partial`
Can be used to provide custom text for labels in the editor.
-# `CartaViewer` options
+# `Markdown` options
-List of options that can be used in the `` component.
+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/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 da14191..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
-
+
```
@@ -102,7 +102,7 @@ Since Carta operates both on the server and the client, you'd need a sanitizer a
let value = '';
-
+
```
diff --git a/docs/src/pages/introduction.svelte.md b/docs/src/pages/introduction.svelte.md
index e1cacd3..12e0af9 100644
--- a/docs/src/pages/introduction.svelte.md
+++ b/docs/src/pages/introduction.svelte.md
@@ -5,21 +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.
## 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.
-- **Accessibility**: includes ARIA roles, arrow keys navigation and labels.
+- ๐ 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
@@ -31,7 +31,7 @@ Carta comes with a set of official plugins for the most common use cases.
-
+MathSupport for KaTex expressions.
@@ -39,7 +39,7 @@ Carta comes with a set of official plugins for the most common use cases.
-
+CodeCode blocks syntax highlighting.
@@ -47,7 +47,7 @@ Carta comes with a set of official plugins for the most common use cases.
-
+EmojiEmbed emojis in Markdown.
@@ -55,7 +55,7 @@ Carta comes with a set of official plugins for the most common use cases.
-
+SlashSupport for slash commands.
@@ -63,7 +63,7 @@ Carta comes with a set of official plugins for the most common use cases.
-
+TikZSupport for TikZ/PgfPlots diagrams.
@@ -71,7 +71,7 @@ Carta comes with a set of official plugins for the most common use cases.
-
+AttachmentHandle text attachments.
@@ -79,7 +79,7 @@ Carta comes with a set of official plugins for the most common use cases.
-
+AnchorAdd anchor links to headings.
@@ -87,7 +87,7 @@ Carta comes with a set of official plugins for the most common use cases.
-
+Community PluginsExplore plugins from the community.
@@ -105,7 +105,7 @@ A list of examples inspired by popular platforms.
-
+GitHubInspired by GitHub.
@@ -113,7 +113,7 @@ A list of examples inspired by popular platforms.
-
+DiscordInspired by Discord.
@@ -121,7 +121,7 @@ A list of examples inspired by popular platforms.
-
+Math Stack ExchangeInspired 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
index 2f63dcb..4b17150 100644
--- a/docs/src/pages/plugins/anchor.svelte.md
+++ b/docs/src/pages/plugins/anchor.svelte.md
@@ -39,7 +39,7 @@ import '@cartamd/plugin-anchor/default.css';
```svelte
-
+
```
@@ -59,8 +59,12 @@ Here are the options you can pass to `anchor()`:
```ts
export interface AnchorExtensionOptions {
/**
- * Maximum depth of headers to generate anchors for. Defaults to 6.
+ * rehype-slug options.
*/
- maxDepth?: number;
+ 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 71950b5..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/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 @@
@@ -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) |  | [/](https://beartocode.github.io/carta/introduction) |
+| [plugin-math](https://www.npmjs.com/package/@cartamd/plugin-math) |  | [/plugins/math](https://beartocode.github.io/carta/plugins/math) |
+| [plugin-code](https://www.npmjs.com/package/@cartamd/plugin-code) |  | [/plugins/code](https://beartocode.github.io/carta/plugins/code) |
+| [plugin-emoji](https://www.npmjs.com/package/@cartamd/plugin-emoji) |  | [/plugins/emoji](https://beartocode.github.io/carta/plugins/emoji) |
+| [plugin-slash](https://www.npmjs.com/package/@cartamd/plugin-slash) |  | [/plugins/slash](https://beartocode.github.io/carta/plugins/slash) |
+| [plugin-tikz](https://www.npmjs.com/package/@cartamd/plugin-tikz) |  | [/plugins/tikz](https://beartocode.github.io/carta/plugins/tikz) |
+| [plugin-attachment](https://www.npmjs.com/package/@cartamd/plugin-attachment) |  | [/plugins/attachment](https://beartocode.github.io/carta/plugins/attachment) |
+| [plugin-anchor](https://www.npmjs.com/package/@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 b24e85d..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": {
@@ -38,8 +35,12 @@
},
"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 @@
@@ -56,11 +92,14 @@
bind:this={elem}
>