@quillmark/svelte

中文文档
0.30.0发布于 2026/11/02Apache-2.0TypeScriptES Module构建来源已验证

编辑器,实时预览和源表面在Quillmark WASM会话。

中文使用文档
文档目录16 节
  1. 安装
  2. 子路径
  3. 打开会话
  4. 羽毛笔从文档中解析
  5. 多么值得信任的文件啊
  6. 预览
  7. 可视化编辑器
  8. 从外面开车
  9. 措辞
  10. 字体
  11. 重新编译
  12. 插入式桥
  13. 错误
  14. 规范的减价
  15. 主题
  16. 开发

@quillmark /苗条的

编辑器+实时预览组件 Quillmark WASM消费者。一个所见即所得 VisualEditor,还有一块帆布。 预览 这将绘制编译后的文档,并将单击往返到编辑器 @quillmark/wasm 会话。香草- ts芯与薄苗条5包装。

安装

npm install @quillmark/svelte

svelte@^5.33 和 @quillmark/wasm 是对等依赖关系:会话的句柄跨越包边界,因此使用者提供双方生成它们的一个副本。

子路径

每个子路径都是它自己的模块根;捆绑器只提取您导入的条目所到达的内容。

进口表面
@quillmark/svelte/core表面的共同点是: DocPath/Place 地址词汇, EditorError 频道, init. 没有UI框架。
@quillmark/svelte/preview现场预览: createPreview + <Preview>. 编辑器端没有达到任何值。
@quillmark/svelte/visual联邦所见即所得: <VisualEditor>,编解码器 createField 散文叶。
@quillmark/svelte再出口 /core.
@quillmark/svelte/preset表面周围页面的可选样式表(THEMING.md)。

打开会话

这个 消费者 拥有会话和句柄,并驱动每个编辑;表面是它的视图。手柄来自 @quillmark/wasm Peer,从来没有在这里再出口过。

import { Engine } from '@quillmark/wasm';
import { init } from '@quillmark/svelte/core';

// Quill and Document are exported only through init().
const { Quill, Document } = await init();

const quill = Quill.fromTree(tree); // tree: Map<string, Uint8Array> of the quill dir

// A NEW document: seeded from the quill's schema.
const doc = quill.seedDocument();

// An EXISTING document: parsed back from what you stored.
const doc = Document.fromMarkdown(markdown); // canonical Quillmark markdown
const doc = Document.fromStored(json); // the versioned storage DTO (`doc.toStored()`)

const session = await new Engine().open(quill, doc);
// free() on teardown the handles you MINTED: this quill, this doc, this session.

<VisualEditor> 不需要会议,和第一 open 加载渲染构建,因此安装编辑器的主机 doc 和 quill 首先显示它,而不等待负载。会议在它后面开始: await engine.load(quill),然后 engine.open(quill, doc),因此它的第一次编译读取与此同时输入的内容(HOSTING.md)。

羽毛笔从文档中解析

存储的文档不携带其笔的字节,只携带一个引用: doc.quillRef 是 name@version,坚持降价本身。分辨率是 主机代码:读取ref,将其映射到a Quill、开放。没有表面分辨率,因此每个文档按结构保留一个分辨率。

const doc = Document.fromMarkdown(stored); // Document, from the gate above
const quill = await registry.getQuill(doc.quillRef); // your ref → Quill mapping
const session = await new Engine().open(quill, doc);

@quillmark/quiver 有这样的登记处吗?它交还的羽毛笔是这样的吗 借来的:它被缓存到每个引用并与每个调用者共享,因此释放它会影响下一个调用者。

打开命名为 不同的 Quill是相同的序列,顺序是:解析新的ref,交换编辑器的道具,释放被替换的句柄,然后在编辑器后面打开下一个会话。 <VisualEditor> 在新的键盘上重新设置键 doc (见下图); <Preview> 按金额交换({#key session})。

多么值得信任的文件啊

一、 Document 也许是读者没有写的 —一个导入、一个共享文件、来自多租户存储的一行—以及两个属性,无论如何都要占用一个属性。Markdown变成了类型化节点,然后是DOM,而不是标记字符串:这个包没有 {@html},没有 innerHTML 也没有 eval,因此文档的文本不能变成标记。并且链接仅为 http, https, mailto, tel, ftp,或者一个根本不命名方案的值;每一个其他方案绘制作为一个惰性跨度,在可编辑的表面和提示卡,油漆 $ext.editor.tips 以外的任何 contenteditable. 这个门是渲染的,而不是模型的,所以拒绝的href停留在标记上并往返:打开文档永远不会编辑它。

留给你的是文本边缘和参考。两端的降价是主人的,而且 doc.quillRef 是 文档的 Own -存储的文件命名为打开它的笔,因此ref→ Quill 上面的映射是它可以到达的模板的边界。

预览

createPreview 提供会话忽略的层:视口、DOM、DPR、点击映射。这是一种纯粹的观点:它从不呼唤 session.apply;您驱动编辑并将结果交给它 ChangeSet.

import { createPreview } from '@quillmark/svelte/preview';

const preview = createPreview(session, {
    container: document.querySelector('#preview')!,
    onPick: (at) => {
        /* a click resolved to an address, with a caret where the compile has one */
    },
    onError: (err) => {
        /* a page paint the backend refused; the preview shows its error state either way */
    }
});

// after an edit lands on `doc`, from any source:
preview.refresh(session.update(doc)); // repaint dirtyPages ∩ visible

苗条的, <Preview {session} onPick={…} /> 暴露相同的动词(refresh, scrollToField, focusPosition, endFollow, setZoom)通过 bind:this.

<Preview> 结合 一次,在mount:交换 session, margin, onPick, onError 或者 strings 原地不改变屏幕和报告 rebind-ignored 通过 onError 在 dev 严重性,命名道具。重新挂载交换({#key session}); 驱动就地编辑 refresh(change). class 和 style 是例外,登陆到根元素并保持存活。

可视化编辑器

<VisualEditor> 是一个文档上许多小型编辑器的联合组合:每个内容页都是ProseMirror散文表面,每个标量都是一个表单控件,是编辑器自己的卡片。它直接提交给 doc 句柄。

<script lang="ts">
    import { VisualEditor } from '@quillmark/svelte/visual';
</script>

<VisualEditor
    {doc}
    {quill}
    onChange={(change) => {
        /* an edit LANDED; `change.source` is 'prose' | 'field' | 'structure' */
    }}
    onActiveLeafChange={(active) => {
        /* the active leaf: `active.field` (a DocPath), and `active.cardId` for its card */
    }}
    onCaretMove={(at) => {
        /* the caret moved to `at.field` (a DocPath) at `at.pos` (UTF-16) */
    }}
    onError={(err) => {
        /* a failure the editor recovered from; editing continues */
    }}
    diagnostics={external}
/>

换一种方式 doc 还有编辑 re-keys本身:每个叶子都重新装入新的句柄,id状态、提交-错误映射和活动地址重新装入。 quill 不是那个键的一部分-模式在每次派生时都会被重新读取,所以羽毛笔交换会自己重新投射;交换它 没有 医生报告说 rebind-ignored.

从外面开车

bind:this 到达卡片头调用的相同动词,因此工具栏、命令调色板或快捷方式不需要第二个路径进入文档。每个人都通过报告 onChange 就像滴答声一样。

<script lang="ts">
    let editor: ReturnType<typeof VisualEditor> | undefined = $state();
    let activeCard = $state<string | undefined>();
</script>

<VisualEditor
    bind:this={editor}
    {doc}
    {quill}
    onActiveLeafChange={(a) => (activeCard = a.cardId)}
/>

<button onclick={() => editor?.insertCard('indorsement')}>Add indorsement</button>
<button onclick={() => activeCard && editor?.moveCard(activeCard, -1)}>Move up</button>
<button onclick={() => editor?.focusField('main.subject')}>Jump to subject</button>

insertCard 把新卡递回去 cardId; removeCard, moveCard 和 setKind 拿一个。 focusField 到达任何已挂载的字段:一个散文页获取其视图的焦点,一个表单控件获取其标签上的点击所需要的相同切换,无论哪种方式,持有它的组都会首先显示。 washField 在一个字段上播放相同的到达洗涤,没有其他内容,主持人写道:焦点,滚动和封闭组保持原样,封闭组中的字段没有洗涤。卡钥匙或路径表面不支持是一个no-op报告 target-unknown 通过 onError 在 dev 严重性。

措辞

这个包裹是英文的,用一条缝代替。 strings 是键和 部分:设置你要翻译的内容,其余的保留在包中。几个关键是 访问的名字 而不是装饰(卡片控制,添加触发器),因此未翻译的表面将错误的语言读取到屏幕阅读器。 DEFAULT_VISUAL_STRINGS 是英文,导出后你可以对照它进行写作。 <Preview> 它有自己的三键 strings 当没有东西可画时,它所显示的状态。

<VisualEditor
    {doc}
    {quill}
    strings={{
        cardDelete: 'Supprimer la carte',
        addCard: '+ Ajouter une carte'
    }}
    formatDiagnostic={(d) =>
        d.code === 'validation::type_mismatch' ? `${d.path} : type incorrect` : undefined}
/>

formatDiagnostic 得到全部 Diagnostic 并返回 undefined 来传递自己的信息。验证和编辑诊断信息 code, path 和 args;解析错误和渲染警告在英文文本中包含详细信息,并保持原样(VISUAL_EDITOR.md)。

字体

可以将一系列文本设置为字体族 主人 供应,拼写 <qm-span family="…"> 在减价。使用字体文件(.ttf, .otf, .ttc, .otc),并将它列出的脸交给编辑器:然后弹出窗口提供每个族的格式,编辑器将这些脸注册到页面中,这样运行就可以绘制预览设置的脸。

const carlito = new Uint8Array(await (await fetch('/fonts/Carlito-Regular.ttf')).arrayBuffer());
const engine = new Engine({ fonts: [carlito] });
<script lang="ts">
    let fonts = $state<FontFace[]>([]);
    engine.fontFaces(quill).then((faces) => (fonts = faces));
</script>

<VisualEditor {doc} {quill} {fonts} />

@quillmark/fonts 是每个Quillmark客户端构建其引擎的库,因此一个家族的文档名称在每个文档中设置相同的方式;主机自己的文件跟踪它。 fontFaces 加载渲染构建,如 open 是的,所以先安装编辑器,让面孔到达它后面。文档已经设置了一个没有库的编辑,选择器提供了它,所以它可以被清除。

重新编译

onChange 信号是要重新编译的,它又覆盖了吗 三条车道:一个散文按键,一个标量写入,一个卡片操作。 onCaretMove 是一个 选择 信号,而不是更改信号:它在一个空箭头键上触发,因此挂起的重新编译会在每个箭头键上重新编译。

let timer: ReturnType<typeof setTimeout> | undefined;

onChange: (change) => {
    // a structure op happens once per gesture, so it applies at once;
    // prose and field edits arrive per keystroke, so they debounce.
    if (change.source === 'structure') recompileNow();
    else {
        clearTimeout(timer);
        timer = setTimeout(recompileNow, 120);
    }
};

function recompileNow() {
    timer = undefined;
    const change = session.update(doc);
    preview.refresh(change);
    source.refresh();
    diagnostics = [...session.warnings]; // → the editor's `diagnostics` prop
}

session.warnings 是一个 Svelte不跟踪的句柄上的getter:每次应用时拉出它,否则编辑器会显示前一次编译的错误。

调度是主机策略,因此包不提供调度程序。义务:您自己的编辑(导入,撤销,直接) doc Write)通过相同的调用重新编译,因为没有轮询文档;编译器重新读取诊断;然后拆除计时器 之前 释放手柄。

插入式桥

那座桥住在 消费者 层和是可选的;编辑不知道预览,预览也不知道编辑。两个跳点都是传递:两个表面已经使用一个地址语法,即规范 DocPath.

// preview → editor: a click resolved to an address lands in the field it names.
onPick: (at) => visualEditor.setCaret(at);

// editor → preview: a caret move scrolls the preview to follow it,
// and a focus into a leaf with no caret to report ends the follow.
onCaretMove: preview.focusPosition;
onActiveLeafChange: preview.endFollow;

编辑器→预览跳转或两者都不跳转。控件报告它的焦点,没有插入符号——没有日期字段可以命名的偏移量——所以如果没有插入符号,预览会一直跟随焦点离开的叶子,并且每次重新编译都会将窗格拉回它。一个散文页用它的下一个插入符号重新开始跟随。为自己的chrome调用读取活动叶子的主机 endFollow 从它的处理程序。

pick带有一个插入符号,编译器在其中跟踪该点下的内容,而只有一个字段,它只跟踪位置——一个标量,印版打印而不跟踪。 setCaret 两者皆有:缺席 pos 揭示和聚焦领域,这是整个什么点击板上放置的墨水可以意味着。该地址可以命名一个数组元素(main.keywords[0]),它落在那一行。

持有 Addr 它自己的地图 fieldPathForAddr (从 @quillmark/svelte/core). playground应用的分窗格路由是完整的引用shell:一个会话,两个桥接方向,编辑后的预览,以及内联路由的诊断。

错误

每个表面 onError. 它向地面报告故障 从提交边界被拒绝,卡片操作被抛出,页面绘制失败,序列化被抛出。他们都没有停止编辑。没有电线,每个都落在控制台。

onError: (err) => {
    // err.code: 'commit-refused' | 'paint-failed' | … (see EditorErrorCode)
    // err.severity: 'error' (runtime) | 'dev' (a contract violation)
    // err.cause: whatever was thrown, unwrapped
    if (err.severity === 'error') telemetry.capture(err);
};

这不是诊断通道。一个 Diagnostic 是关于 文档 在它所属的领域里画画;一个 EditorError 是关于 表面 画不出任何东西。被拒绝的标量提交会产生这两种结果。

规范的减价

doc.toMarkdown() 返回文档的规范Quillmark标记,并将其重新解析为等号 Document. 一个只读镜像是在 <pre>,在每次编辑完成后重新运行,所以它不需要这个包的表面:

el.textContent = doc.toMarkdown();

主题

表面承载着一种中性的、可超越的视觉基线:一组 --qm-* 您在任何祖先上重写的CSS自定义属性。看到 THEMING.md.

开发

这个包是一个工作空间 quillmark-js;门是根的,建筑就住在里面 prose/.

npm run build -w packages/svelte   # svelte-package → dist/
npm run test  -w packages/svelte   # Vitest
npm run check -w packages/svelte   # svelte-check
npm run dev                        # the playground, from the root

版本与发布记录

@quillmark/svelte 共发布 48 个版本,首次发布于 2026/08/07;过去 12 个月发布了 48 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.30.0latest2026/11/021 天前
0.29.32026/11/021 天前
0.29.22026/10/313 天前
0.29.12026/10/313 天前
0.29.02026/10/304 天前
0.28.02026/10/295 天前

关于 @quillmark/svelte 的常见问题

@quillmark/svelte 是什么?

编辑器,实时预览和源表面在Quillmark WASM会话。

如何安装 @quillmark/svelte?

在项目目录运行 npm install @quillmark/svelte,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:svelte(^5.33.0)、@quillmark/wasm(>=0.134.1-0 <0.135.0-0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@quillmark/svelte 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@quillmark/svelte 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@quillmark/svelte 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@quillmark/svelte 可以商用吗?

@quillmark/svelte 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@quillmark/svelte 还在维护吗?

最新版本 0.30.0 发布于 2026/11/02(1 天前);至今共发布 48 个版本,其中过去 12 个月发布了 48 个;首次发布于 2026/08/07。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。