[{"data":1,"prerenderedAt":548},["ShallowReactive",2],{"quirekit-doc:\u002Fbuild\u002Fproject":3},{"id":4,"title":5,"body":6,"description":538,"extension":539,"keywords":540,"locale":541,"meta":542,"navigation":191,"order":36,"path":543,"section":544,"seo":545,"stem":546,"__hash__":547},"docs\u002Fbuild\u002Fproject.zh-CN.md","建立第一个编辑器",{"type":7,"value":8,"toc":532},"minimark",[9,13,16,21,58,77,81,92,392,410,419,423,434,496,499,503,506,520,528],[10,11,12],"p",{},"这一章先做一件事：在 Vue 页面中放入能输入、选择和撤销的编辑器。先用浏览器原生 DOM 视图理解 Core 的用法，后面的章节再加入 Canvas、分页和产品界面。",[10,14,15],{},"你需要了解 Vue 的组件和生命周期，并准备 Node.js 与 pnpm。已有 Vue 3 项目可以直接从安装依赖开始。",[17,18,20],"h2",{"id":19},"_1-准备-vue-项目","1. 准备 Vue 项目",[22,23,29],"pre",{"className":24,"code":25,"filename":26,"language":27,"meta":28,"style":28},"language-sh shiki shiki-themes github-light github-light github-dark","pnpm create vite my-editor --template vue-ts\ncd my-editor\npnpm install\npnpm add @quirekit\u002Fcore@alpha @quirekit\u002Fstarter-kit@alpha @quirekit\u002Fdom-view@alpha @quirekit\u002Fpm-view@alpha\n","终端","sh","",[30,31,32,40,46,52],"code",{"__ignoreMap":28},[33,34,37],"span",{"class":35,"line":36},"line",1,[33,38,39],{},"pnpm create vite my-editor --template vue-ts\n",[33,41,43],{"class":35,"line":42},2,[33,44,45],{},"cd my-editor\n",[33,47,49],{"class":35,"line":48},3,[33,50,51],{},"pnpm install\n",[33,53,55],{"class":35,"line":54},4,[33,56,57],{},"pnpm add @quirekit\u002Fcore@alpha @quirekit\u002Fstarter-kit@alpha @quirekit\u002Fdom-view@alpha @quirekit\u002Fpm-view@alpha\n",[10,59,60,61,64,65,68,69,72,73,76],{},"这几个包各负责一件事：",[30,62,63],{},"core"," 管理文档和命令，",[30,66,67],{},"starter-kit"," 提供段落、标题、粗体等常用能力，",[30,70,71],{},"dom-view"," 把文档显示为可编辑的 DOM。",[30,74,75],{},"pm-view"," 的基础样式保留正文空白和原生选区的显示方式。QuireKit 包应使用同一发布批次。",[17,78,80],{"id":79},"_2-创建编辑器组件","2. 创建编辑器组件",[10,82,83,84,87,88,91],{},"新建 ",[30,85,86],{},"src\u002FEditor.vue","。Vue 负责提供一个空元素；",[30,89,90],{},"Editor"," 在组件挂载后接管这个元素中的编辑内容。",[22,93,97],{"className":94,"code":95,"filename":86,"language":96,"meta":28,"style":28},"language-vue shiki shiki-themes github-light github-light github-dark","\u003Cscript setup lang=\"ts\">\nimport { onBeforeUnmount, onMounted, ref } from 'vue'\nimport { Editor } from '@quirekit\u002Fcore'\nimport { StarterKit } from '@quirekit\u002Fstarter-kit'\nimport { DomViewExtension } from '@quirekit\u002Fdom-view'\nimport '@quirekit\u002Fpm-view\u002Fstyle\u002Fprosemirror.css'\n\nconst element = ref\u003CHTMLElement>()\nlet editor: Editor | undefined\n\nonMounted(() => {\n  editor = new Editor({\n    element: element.value,\n    mode: 'dom',\n    extensions: [StarterKit, DomViewExtension],\n    content: '\u003Cp>我的第一个 QuireKit 编辑器。\u003C\u002Fp>',\n  })\n})\n\nonBeforeUnmount(() => editor?.destroy())\n\u003C\u002Fscript>\n\n\u003Ctemplate>\u003Cdiv ref=\"element\" \u002F>\u003C\u002Ftemplate>\n","vue",[30,98,99,126,141,153,165,178,186,193,217,238,243,258,274,280,292,298,309,315,321,326,345,355,360],{"__ignoreMap":28},[33,100,101,105,109,113,116,119,123],{"class":35,"line":36},[33,102,104],{"class":103},"sVPnS","\u003C",[33,106,108],{"class":107},"su0ou","script",[33,110,112],{"class":111},"sgQzM"," setup",[33,114,115],{"class":111}," lang",[33,117,118],{"class":103},"=",[33,120,122],{"class":121},"s-HEn","\"ts\"",[33,124,125],{"class":103},">\n",[33,127,128,132,135,138],{"class":35,"line":42},[33,129,131],{"class":130},"sDD9d","import",[33,133,134],{"class":103}," { onBeforeUnmount, onMounted, ref } ",[33,136,137],{"class":130},"from",[33,139,140],{"class":121}," 'vue'\n",[33,142,143,145,148,150],{"class":35,"line":48},[33,144,131],{"class":130},[33,146,147],{"class":103}," { Editor } ",[33,149,137],{"class":130},[33,151,152],{"class":121}," '@quirekit\u002Fcore'\n",[33,154,155,157,160,162],{"class":35,"line":54},[33,156,131],{"class":130},[33,158,159],{"class":103}," { StarterKit } ",[33,161,137],{"class":130},[33,163,164],{"class":121}," '@quirekit\u002Fstarter-kit'\n",[33,166,168,170,173,175],{"class":35,"line":167},5,[33,169,131],{"class":130},[33,171,172],{"class":103}," { DomViewExtension } ",[33,174,137],{"class":130},[33,176,177],{"class":121}," '@quirekit\u002Fdom-view'\n",[33,179,181,183],{"class":35,"line":180},6,[33,182,131],{"class":130},[33,184,185],{"class":121}," '@quirekit\u002Fpm-view\u002Fstyle\u002Fprosemirror.css'\n",[33,187,189],{"class":35,"line":188},7,[33,190,192],{"emptyLinePlaceholder":191},true,"\n",[33,194,196,199,203,206,209,211,214],{"class":35,"line":195},8,[33,197,198],{"class":130},"const",[33,200,202],{"class":201},"sUNQB"," element",[33,204,205],{"class":130}," =",[33,207,208],{"class":111}," ref",[33,210,104],{"class":103},[33,212,213],{"class":111},"HTMLElement",[33,215,216],{"class":103},">()\n",[33,218,220,223,226,229,232,235],{"class":35,"line":219},9,[33,221,222],{"class":130},"let",[33,224,225],{"class":103}," editor",[33,227,228],{"class":130},":",[33,230,231],{"class":111}," Editor",[33,233,234],{"class":130}," |",[33,236,237],{"class":201}," undefined\n",[33,239,241],{"class":35,"line":240},10,[33,242,192],{"emptyLinePlaceholder":191},[33,244,246,249,252,255],{"class":35,"line":245},11,[33,247,248],{"class":111},"onMounted",[33,250,251],{"class":103},"(() ",[33,253,254],{"class":130},"=>",[33,256,257],{"class":103}," {\n",[33,259,261,264,266,269,271],{"class":35,"line":260},12,[33,262,263],{"class":103},"  editor ",[33,265,118],{"class":130},[33,267,268],{"class":130}," new",[33,270,231],{"class":111},[33,272,273],{"class":103},"({\n",[33,275,277],{"class":35,"line":276},13,[33,278,279],{"class":103},"    element: element.value,\n",[33,281,283,286,289],{"class":35,"line":282},14,[33,284,285],{"class":103},"    mode: ",[33,287,288],{"class":121},"'dom'",[33,290,291],{"class":103},",\n",[33,293,295],{"class":35,"line":294},15,[33,296,297],{"class":103},"    extensions: [StarterKit, DomViewExtension],\n",[33,299,301,304,307],{"class":35,"line":300},16,[33,302,303],{"class":103},"    content: ",[33,305,306],{"class":121},"'\u003Cp>我的第一个 QuireKit 编辑器。\u003C\u002Fp>'",[33,308,291],{"class":103},[33,310,312],{"class":35,"line":311},17,[33,313,314],{"class":103},"  })\n",[33,316,318],{"class":35,"line":317},18,[33,319,320],{"class":103},"})\n",[33,322,324],{"class":35,"line":323},19,[33,325,192],{"emptyLinePlaceholder":191},[33,327,329,332,334,336,339,342],{"class":35,"line":328},20,[33,330,331],{"class":111},"onBeforeUnmount",[33,333,251],{"class":103},[33,335,254],{"class":130},[33,337,338],{"class":103}," editor?.",[33,340,341],{"class":111},"destroy",[33,343,344],{"class":103},"())\n",[33,346,348,351,353],{"class":35,"line":347},21,[33,349,350],{"class":103},"\u003C\u002F",[33,352,108],{"class":107},[33,354,125],{"class":103},[33,356,358],{"class":35,"line":357},22,[33,359,192],{"emptyLinePlaceholder":191},[33,361,363,365,368,371,374,376,378,381,385,388,390],{"class":35,"line":362},23,[33,364,104],{"class":103},[33,366,367],{"class":107},"template",[33,369,370],{"class":103},">\u003C",[33,372,373],{"class":107},"div",[33,375,208],{"class":111},[33,377,118],{"class":103},[33,379,380],{"class":121},"\"element\"",[33,382,384],{"class":383},"shEpO"," \u002F",[33,386,387],{"class":103},">\u003C\u002F",[33,389,367],{"class":107},[33,391,125],{"class":103},[10,393,394,397,398,401,402,405,406,409],{},[30,395,396],{},"element"," 指定显示位置，",[30,399,400],{},"extensions"," 决定可以编辑哪些内容，",[30,403,404],{},"content"," 是初始正文。",[30,407,408],{},"mode: 'dom'"," 选择刚安装的 DOM 视图。创建一次即可，文字输入时不需要重新创建 Editor。",[10,411,412,414,415,418],{},[30,413,248],{}," 保证挂载元素已经存在；",[30,416,417],{},"destroy()"," 释放编辑视图和监听器。因此这段组件也可以放在带 SSR 的应用中：服务端只渲染外层元素，编辑器在浏览器中创建。",[17,420,422],{"id":421},"_3-放到页面里","3. 放到页面里",[10,424,425,426,429,430,433],{},"在 ",[30,427,428],{},"src\u002FApp.vue"," 中导入并使用组件，然后运行 ",[30,431,432],{},"pnpm dev","。",[22,435,437],{"className":94,"code":436,"filename":428,"language":96,"meta":28,"style":28},"\u003Cscript setup lang=\"ts\">\nimport Editor from '.\u002FEditor.vue'\n\u003C\u002Fscript>\n\n\u003Ctemplate>\u003CEditor \u002F>\u003C\u002Ftemplate>\n",[30,438,439,455,467,475,479],{"__ignoreMap":28},[33,440,441,443,445,447,449,451,453],{"class":35,"line":36},[33,442,104],{"class":103},[33,444,108],{"class":107},[33,446,112],{"class":111},[33,448,115],{"class":111},[33,450,118],{"class":103},[33,452,122],{"class":121},[33,454,125],{"class":103},[33,456,457,459,462,464],{"class":35,"line":42},[33,458,131],{"class":130},[33,460,461],{"class":103}," Editor ",[33,463,137],{"class":130},[33,465,466],{"class":121}," '.\u002FEditor.vue'\n",[33,468,469,471,473],{"class":35,"line":48},[33,470,350],{"class":103},[33,472,108],{"class":107},[33,474,125],{"class":103},[33,476,477],{"class":35,"line":54},[33,478,192],{"emptyLinePlaceholder":191},[33,480,481,483,485,487,489,492,494],{"class":35,"line":167},[33,482,104],{"class":103},[33,484,367],{"class":107},[33,486,370],{"class":103},[33,488,90],{"class":107},[33,490,491],{"class":103}," \u002F>\u003C\u002F",[33,493,367],{"class":107},[33,495,125],{"class":103},[10,497,498],{},"你应该能点击正文输入，拖动选择文字，并通过系统快捷键撤销。下面运行的是同样的接入方式，可以直接试用；源码只有一个组件文件。",[500,501],"docs-demo",{"name":502},"tutorial\u002Feditor",[17,504,505],{"id":505},"接下来增加什么",[10,507,508,509,514,515,519],{},"现在有了编辑器的骨架，但还没有工具栏和分页工作区。下一章先解释",[510,511,513],"a",{"href":512},"\u002Fbuild\u002Fmodel","文档模型","：为什么正文要存成结构化数据，以及普通文章和 Word 文档为什么需要不同的扩展。随后",[510,516,518],{"href":517},"\u002Fbuild\u002Fview","挂载 View","会把 DOM 视图换成 Canvas\u002FSVG。",[10,521,522,523,527],{},"如果你已经熟悉这些基础，可以直接查阅",[510,524,526],{"href":525},"\u002Fbuild\u002Freference","完整工作区示例","。它包含文件操作和状态管理，每个文件的用途在该页单独说明。",[529,530,531],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sVPnS, html code.shiki .sVPnS{--shiki-default:#24292E;--shiki-light:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .su0ou, html code.shiki .su0ou{--shiki-default:#22863A;--shiki-light:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sgQzM, html code.shiki .sgQzM{--shiki-default:#6F42C1;--shiki-light:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s-HEn, html code.shiki .s-HEn{--shiki-default:#032F62;--shiki-light:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sDD9d, html code.shiki .sDD9d{--shiki-default:#D73A49;--shiki-light:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sUNQB, html code.shiki .sUNQB{--shiki-default:#005CC5;--shiki-light:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .shEpO, html code.shiki .shEpO{--shiki-default:#B31D28;--shiki-default-font-style:italic;--shiki-light:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic}",{"title":28,"searchDepth":42,"depth":42,"links":533},[534,535,536,537],{"id":19,"depth":42,"text":20},{"id":79,"depth":42,"text":80},{"id":421,"depth":42,"text":422},{"id":505,"depth":42,"text":505},"从一个 Vue 组件开始，理解创建、挂载和销毁。","md",null,"zh-CN",{},"\u002Fbuild\u002Fproject","构建产品",{"title":5,"description":538},"build\u002Fproject.zh-CN","ZdDNRseI25ihYn7Va0J4r7A0deHCIucFBM2IjtipLxU",1793045209085]