@mai/meditor (0.6.2)
Installation
@mai:registry=https://mgit.flexsiebels.de/api/packages/mAi/npm/npm install @mai/meditor@0.6.2"@mai/meditor": "0.6.2"About this package
mEditor
A markdown editor as a Svelte package: a textarea/preview pair with wikilink and tag autocomplete, keyboard shortcuts, and a provider interface so the host supplies content resolution instead of the editor knowing about any one product's data.
Extracted from mBrian/src/lib/MarkdownEditor.svelte for m/mWiki#4. See
docs/provider-interface.md for what a consumer must supply.
Lives at packages/meditor/ in the m/mkit workspace (moved from the
standalone m/mEditor repo 2026-09-14, m/mkit#3), alongside packages/mkit
(@mai/mkit). The root bunfig.toml and .gitea/workflows/publish.yml are
shared across both packages; see the workspace root README.
Install
Published as @mai/meditor on this Gitea instance's own npm registry, not on
the public npm registry — under the mAi account's namespace, since that is
the account that cuts every release.
bun add @mai/meditor
Point the @mai scope at the registry in a .npmrc (project root or user
level):
@mai:registry=https://mgit.msbls.de/api/packages/mAi/npm/
The package is readable without a credential, so an install (a Dokploy build included) needs that line and no token. Never add an _authToken line to a consumer: with GITEA_NPM_TOKEN unset bun sends an empty bearer and Gitea answers 401. Publishing needs write:package and runs from this repo's workflow (root README § Publishing). Gitea's package registry ties write access to the owning account and takes no collaborators, which is why the package lives under mAi, the account that mints every release.
Versioning
Semantic versioning against the exported surface (Editor, EditorProviders,
renderMarkdown, the wikilinks helpers) documented in
docs/provider-interface.md:
- patch — a fix with no change to what is exported or how it is called.
- minor — additive only: a new optional
EditorProvidersmember, a new export, a new optional prop. Existing callers keep compiling unchanged. - major — anything a consumer must react to: a removed or renamed export, a new required provider member, a changed function signature.
This applies from the first published version onward, including while the version is below 1.0.0.
A release is followed by a sync commit in m/mWiki (m/mWiki#111, scripts/sync-meditor.ts), since mWiki vendors the built package rather than installing it from this registry.
Use
<script lang="ts">
import { Editor, type EditorProviders } from '@mai/meditor';
let value = $state('');
const providers: EditorProviders = {
async search(trigger) { /* … */ return []; },
resolveLink(kind, target, display) { /* … */ return undefined; }
};
</script>
<Editor bind:value {providers} onsave={() => save(value)} renderOptions={{ blockIds: true }} />
renderOptions (optional) is passed straight through to the same
renderMarkdown options a host's own server-side render uses, so the
editor's preview stamps data-block/data-lines the same way. Omitted,
the preview renders exactly as before.
wikilinks.ts's pure functions (parseWikilinks, extractWikilinkSlugs,
rewriteWikilinkTarget) are exported alongside Editor for a consumer's own
backlink and rename logic — see docs/provider-interface.md's "Renaming"
section for why the rename itself is not part of this package.
Formatting toolbar
A row of insert-action buttons above the textarea, shown by default; pass toolbar={false} to hide it. Overflow beyond 320px collapses into a "More" menu.
| Action | Shortcut |
|---|---|
| Bold | Ctrl/Cmd+B |
| Italic | Ctrl/Cmd+I |
Heading (cycles ##→###→####→plain) |
Ctrl/Cmd+Shift+H |
| Link | Ctrl/Cmd+K |
Wikilink (opens the [[ search) |
Ctrl/Cmd+Shift+K |
| Inline code | Ctrl/Cmd+E |
| Code block | Ctrl/Cmd+Shift+E |
| Quote | Ctrl/Cmd+Shift+. |
| Bullet list | Ctrl/Cmd+Shift+8 |
| Numbered list | Ctrl/Cmd+Shift+7 |
| Table | — |
| Footnote | Ctrl/Cmd+Shift+F |
Task (cycles text→- →- [ ] →- [x] →plain) |
Ctrl/Cmd+Enter |
| Task state (steps the nine states) | Ctrl/Cmd+Shift+Enter |
Every button and shortcut runs the same pure text transform, in toolbar-actions.ts — wrapBold, wrapItalic, wrapInlineCode, insertLink, insertWikilink, insertTable, toggleQuote, toggleBulletList, toggleNumberedList, toggleCodeBlock, cycleHeading, insertFootnote, cycleTask, cycleTaskState — each taking { text, selectionStart, selectionEnd } and returning the same shape, exported from both @mai/meditor and @mai/meditor/headless for a consumer that wants to drive them without the UI (a bulk-reformat script, a test).
A line action applies to every line the selection touches, decided once from the first of them, so a block moves together. A caret keeps its place in its line; a selection ends up around the changed block.
Task icons
A task reads as an icon while the markdown under it stays what the file holds (m/mkit#51): the editor draws the kit's .mk-task over each - [c] marker, and the preview renders the same icon through renderMarkdown. The states are Obsidian's nine — [ ], [x], [/], [?], [!], [-], [>], [<], [*] — defined in @mai/mkit/markdown and themed from the kit's --mk-task-* tokens (the kit's README, § Task states).
Ctrl/Cmd+Enter is the task key: text → - text → - [ ] text → - [x] text → text, with a line in any other state counting as an open task, so one press marks it done. Ctrl/Cmd+Shift+Enter steps the state through the nine and round again, and turns a line that is no task yet into one.
The icon is drawn on a copy of the document laid out over the textarea on the textarea's own metrics, transparent but for the icons, and it never takes the pointer — the caret, the selection and every keystroke stay the textarea's, and the markdown text is untouched. taskIcons={false} leaves the raw brackets visible, for a plain source view or a document large enough that a per-keystroke copy costs something; the preview renders icons either way.
Plain resolvers (no Svelte, no Vite)
The package root (@mai/meditor) resolves only through the svelte export
condition, because Editor is a Svelte component and no export condition
makes a .svelte file loadable by a plain resolver. A server-side consumer
that only needs renderMarkdown or the wikilinks helpers — bun test, a
plain Node ESM script, anything without a Svelte-aware bundler — imports
from the @mai/meditor/headless subpath instead, which never touches
Editor.svelte:
import { renderMarkdown } from '@mai/meditor/headless';
Develop
bun install
bun run test:unit # wikilinks.ts, preview.ts unit tests, plus one proving the @mai/mkit/markdown re-export matches the kit directly
bun run dev # src/routes is a standalone test consumer, no other repo needed
bun run build # builds the package into dist/
Dependencies
Dependencies
| ID | Version |
|---|---|
| @mai/mkit | ^0.1.5 |
Development Dependencies
| ID | Version |
|---|---|
| @sveltejs/adapter-auto | ^4.0.0 |
| @sveltejs/kit | ^2.15.0 |
| @sveltejs/package | ^2.3.0 |
| @sveltejs/vite-plugin-svelte | ^5.0.0 |
| @types/bun | ^1.3.9 |
| @types/jsdom | ^30.0.0 |
| jsdom | ^30.0.1 |
| publint | ^0.2.0 |
| svelte | ^5.0.0 |
| svelte-check | ^4.0.0 |
| typescript | ^5.0.0 |
| vite | ^6.0.0 |
Peer Dependencies
| ID | Version |
|---|---|
| svelte | ^5.0.0 |