ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

深入解析 Quasar QEditor:基于 contentEditable 的 WYSIWYG 富文本编辑器完整实战指南

深入解析 Quasar QEditor:基于 contentEditable 的 WYSIWYG 富文本编辑器完整实战指南 深入解析 Quasar QEditor基于 contentEditable 的 WYSIWYG 富文本编辑器完整实战指南【免费下载链接】quasarQuasar Framework - Build high-performance VueJS user interfaces in record time项目地址: https://gitcode.com/gh_mirrors/qu/quasarQuasar 的 QEditor 是一个所见即所得WYSIWYG的富文本编辑组件基于浏览器原生的 Design Mode 与跨浏览器的contentEditable接口实现允许用户直接编写甚至粘贴 HTML。本文以 Quasar 仓库中的官方文档 editor.md 为主线结合组件源码 QEditor.js 与 API 定义 QEditor.json 以及 docs/src/examples/QEditor 目录下的全套示例系统讲解工具栏定制、内置命令、下拉菜单、无障碍支持、链接编辑等核心能力。读完本文你将能够在一个 Vue 项目中完整配置 QEditor自定义专属工具栏命令与字体处理好粘贴、图片、只读展示与键盘可访问性等实际场景。QEditor 与浏览器编辑技术的底层关系QEditor 并没有自建排版引擎而是直接调用浏览器提供的基础设施。理解这一点是掌握 QEditor 各种配置行为的前提Design Mode通过document.designMode让整个文档可编辑contentEditableHTML 全局属性让指定元素区域可编辑execCommand()执行bold、italic、formatBlock等编辑命令的底层 API。在 QEditor.js 的 setup 阶段组件会调用一次document.execCommand(defaultParagraphSeparator, false, props.paragraphTag)将回车产生的段落标签固定为div或p由paragraph-tag属性控制默认div这也是为什么 QEditor 默认使用div而非p作为段落容器的原因。快速上手与 v-model 数据流QEditor 通过v-model双向绑定内容值始终是纯 HTML 字符串见 QEditor.json 中update:model-value事件的参数说明。最简用法对应 Basic.vueq-editor v-modeleditor min-height5rem /import { ref } from vue const editor ref(What you see is bwhat/b you get.)组件的输入输出路径在源码中清晰可见用户在编辑区输入时onInput读取contentRef的innerHTML查看源码模式时为innerText与当前modelValue不一致时触发update:modelValue见 QEditor.js外部修改modelValue时watch回调通过setContent回写编辑区并尝试恢复光标位置见 QEditor.js。渲染用户内容时的 XSS 警告官方文档在第一个示例中特别给出 [!WARNING] 警示编辑区下方的两个卡片第一个用双大括号{{ editor }}显示未解析的 HTML第二个用v-htmleditor渲染。如果内容是用户产生的使用v-html会让你的用户暴露在跨站脚本XSS攻击之下。务必在渲染侧或服务端最好两者都做对内容做消毒sanitize处理。定义工具栏toolbar 属性的三层结构toolbar是 QEditor 定制能力的核心。它是一个组group的数组每组之间渲染间距每个组又是一个令牌token的数组。一个令牌可以是三种东西之一见 editor.md内置命令名字符串如bold来自下方内置命令表带options的下拉对象把一组命令放进下拉菜单任意其他字符串会变成同名工具栏插槽slot例如token对应template #token。完整结构示例toolbar: [ // 数组的数组组每组之间留间隔 // 内置命令按名字引用 [bold, italic, strike, underline], [ // 下拉菜单一个包含 options 命令列表的对象 { label: Formatting, // 通常用 $q.lang.editor.formatting便于跟随语言包翻译 icon: format_size, // 通常用 $q.iconSet.editor.formatting跟随图标集 // 可选no-icons 仅按标签列出选项only-icons 仅按图标缺省则两者都显示 list: no-icons, // 可选保持下拉自身标签与图标而不替换为所选选项的 fixedLabel: true, fixedIcon: true, // 可选当某个选项生效时给下拉按钮上色toolbar-toggle-color highlight: true, options: [p, h1, h2, h3, code] } ], // 通过 definitions 属性新增的命令 [save], // 任意其他字符串该名称的工具栏插槽template #token [token] ]当toolbar未传入时组件使用默认工具栏[[left, center, right, justify], [bold, italic, underline, strike], [undo, redo]]见 QEditor.js。toolbar 的校验器要求每个组至少含一个元素。源码的令牌解析逻辑位于 QEditor.js带options的对象解析为dropdown类型能在命令定义表中命中的字符串按toggle或no-state渲染其余字符串一律当作插槽名渲染undefined空洞令牌会被过滤对应 issue #16940。内置命令一览每个内置命令都预配置了图标与国际化i18n提示文本。官方文档给出的命令分组如下分组命令文本 Textbold、italic、strike、underline、subscript、superscript、removeFormat块 Blockp段落、h1~h6、code代码段、quote对齐 Alignmentleft、center、right、justify列表与缩进 Lists and indentationunordered、ordered、outdent、indent插入 Insertionlink、hr字体 Fontsize-1~size-7、default_font以及你fonts属性中定义的字体名历史与视图 History and viewundo、redo、fullscreen、viewsource、print从源码buttonDef见 QEditor.js可以看到每个命令到底映射了哪个execCommand文本类bold、italic、strikeThrough、underline、subscript、superscript、removeFormat块类quote与h1~h6走formatBlock参数分别是BLOCKQUOTE、H1~H6p的formatBlock参数直接取paragraphTag属性值code走formatBlockPRE对齐类justifyLeft、justifyCenter、justifyRight、justifyFull列表/缩进insertUnorderedList、insertOrderedList、outdent、indent插入link走组件内建链接编辑流程、insertHorizontalRule字体fontSize参数1~7历史/视图undo、redo、printtype: no-state以及组件内建的fullscreen、viewsource特殊命令。这些命令还预定义了 CTRL 快捷键的 keycode见 QEditor.jsbold66B、italic73I、strike83S、underline85U、link76L、fullscreen70F、quote81Q、print80P、undo90Z、redo89Y。快捷键机制在keys计算属性中汇总onKeydown里当e.ctrlKey且命中了映射表中的按键时执行对应命令并preventDefault见 QEditor.js。覆盖与扩展命令definitions 与 fonts 属性definitions覆盖内置或新增命令要覆盖某个内置命令的设置或添加自己的命令使用definitions属性——一个以命令名为键的对象。命令名随后与普通令牌一样放进toolbar。官方文档给出的覆盖示例:definitions{ bold: { label: Bold, icon: null, tip: My bold tooltip } }完整可复制版本对应 NewBold.vueq-editor v-modeleditor :definitions{ bold: { label: Bold, icon: null, tip: My bold tooltip } } /新增命令的示例对应 NewCommands.vue——注意定义后必须把命令名放进toolbar才会显示q-editor v-modeleditor :definitions{ save: { tip: Save your work, icon: save, label: Save, handler: saveWork }, upload: { tip: Upload to cloud, icon: cloud_upload, label: Upload, handler: uploadIt } } :toolbar[ [bold, italic, strike, underline], [upload, save] ] /import { useQuasar } from quasar import { ref } from vue const $q useQuasar() const editor ref(After you define a new button, you have to make sure to put it in the toolbar too!) function saveWork() { $q.notify({ message: Saved your text to local storage, color: green-4, icon: cloud_done }) } function uploadIt() { $q.notify({ message: Server unavailable. Check connectivity., color: red-5, icon: warning }) }根据 QEditor.json 中definitions的定义每个命令对象支持的键为键类型说明labelString按钮标签tipString悬停提示文本htmlTipString悬停提示中的 HTML 富文本内容iconString按钮图标keyNumber与ctrl组合使用的键码作为该命令的快捷键handlerFunction点击/触摸时执行的函数与cmd二选一必填cmdString必须是 designMode API 中合法的 execCommand 方法名与handler二选一必填paramString仅在使用cmd时设置多为要注入的文本或 HTMLdisableBoolean / Function是否禁用按钮函数可基于状态动态判断typeString / null传no-state使按钮不呈现“激活”状态fixedLabelBoolean锁定按钮标签不随所选子项变化fixedIconBoolean锁定按钮图标不随所选子项变化highlightBoolean当某个子选项生效时高亮工具栏按钮一个值得注意的细节自定义命令若指定了cmd且该cmd恰好与某个内置no-state命令同名按钮也会以no-state渲染见 QEditor.js 的合并逻辑从而避免“激活态”误报。fonts注册字体家族fonts属性是一个“字体名 → 显示标签”的对象注册后字体名会自动成为工具栏可用的令牌default_font表示默认字体fonts: { arial: Arial, arial_black: Arial Black, comic_sans: Comic Sans MS, courier_new: Courier New, impact: Impact, lucida_grande: Lucida Grande, times_new_roman: Times New Roman, verdana: Verdana }源码中getFonts会把defaultFont页面 body 计算出的字体族与props.fonts合并进命令定义表见 QEditor.js因此default_font令牌可以放在下拉的options里充当“恢复默认字体”选项。下拉菜单Dropdowns的三种形态下拉菜单就是把一组命令包进一个按钮。官方文档给出了三种展示形态的完整示例见 editor.md 的“Types of dropdowns”q-editor v-modelmodel :toolbar[ [ { label: Icons Label, icon: filter_1, fixedLabel: true, fixedIcon: true, options: [bold, italic, strike, underline] } ], [ { label: Only label, icon: filter_2, fixedLabel: true, fixedIcon: true, list: no-icons, options: [bold, italic, strike, underline] } ], [ { label: Only icons, icon: filter_3, fixedLabel: true, fixedIcon: true, list: only-icons, options: [bold, italic, strike, underline] } ] ] /第一组图标与标签同时显示第二组list: no-icons仅按标签列出选项第三组list: only-icons仅按图标列出选项。互斥选项exclusive options下拉用户每次只能从这类下拉中选取一个选项。通过fixedLabel与fixedIcon的组合可以控制按钮是否跟随当前所选子项变化对应 editor.md 中的“Dropdowns with exclusive options”示例q-editor v-modelmodel :toolbar[ [ { label: Dynamic label, icon: help_outline, options: [left, center, right, justify] } ], [ { label: Static label, fixedLabel: true, options: [left, center, right, justify] } ], [ { label: Some label, icon: account_balance, fixedIcon: true, options: [left, center, right, justify] } ] ] /第一个标签与图标都随当前选区动态变化第二个fixedLabel: true标签固定、图标动态第三个fixedIcon: true图标固定、标签动态。悬停打开下拉v2.30dropdown-hover属性让工具栏下拉在指针悬停按钮时打开指针离开按钮与菜单后关闭点击、触摸与键盘交互仍照常切换触摸设备因没有 hover 概念而回退到点击行为。配套两个延迟控制属性dropdown-hover-delay默认 0指针停留多久后打开下拉避免指针只是划过按钮时误开dropdown-hover-hide-delay默认 150指针离开按钮或菜单后的宽限期毫秒在此期间指针重新进入按钮或菜单则不会关闭防止稍微偏出目标的操作误关下拉。这两个属性在 QEditor.json 中标注为需要dropdown-hover属性配合实际实现复用了use-hover组合式函数并重命名而来。参考示例 DropdownHover.vueq-editor v-modeleditor dropdown-hover :dropdown-hover-delay200 :toolbar[ [ { label: Formatting, icon: format_size, fixedLabel: true, list: no-icons, options: [p, h4, h5, h6, code] }, { label: Align, icon: format_align_left, fixedLabel: true, list: only-icons, options: [left, center, right, justify] } ], [bold, italic, underline] ] /添加链接link 命令的完整行为link命令会把工具栏切换为一个 URL 输入框指向当前选区无选区时指向光标下的单词。输入框初始内容若所选文本本身已是一个 URL 则显示该文本否则显示https://。内容只在提交 URL 时才被修改提交方式有三种按Enter、点击 Update 按钮、或点击输入框外部。保留未动的https://不会提交任何内容因此放弃输入框永远不会留下坏链接。按Escape直接取消Remove 按钮则把选区上的链接剥离。无论把link放在工具栏何处包括放进上文提到的下拉菜单中行为一致。链接编辑器的显示/隐藏会触发linkShow/linkHide事件见 QEditor.js 与 QEditor.json 的事件表。只读与禁用readonly 与 disable 的差异两个属性都让内容不可编辑但粒度不同对应 editor.md 的 Read-only and disabled 一节readonly仅禁止修改内容所有会改变内容的工具栏命令被禁用但只读类命令仍可用——fullscreen、print、viewsource保持可用因此一个只读的 QEditor 可以充当“查看器”disable把整个组件移出可用状态——内容不可编辑、整个工具栏禁用、编辑器整体变暗。源码层面editable computed(() !props.readonly !props.disable)按钮的disable属性直接绑定!editable见 QEditor.js同时编辑区分别以aria-readonly与aria-disabled暴露给辅助技术见 QEditor.js。无障碍支持v2.25键盘导航工具栏遵循 WAI-ARIA toolbar pattern工具栏整体是单个 Tab 停靠点——第一次按Tab把焦点移入工具栏落到上次使用的按钮或第一个可用按钮第二次Tab移入编辑区而不是逐个走过每个按钮。工具栏内部Arrow Left/Arrow Right在按钮间移动焦点两端循环RTL 环境下方向镜像Home/End跳到第一个 / 最后一个按钮通过自定义插槽渲染的内容保留自己的 Tab 停靠点。源码中onToolbarKeydown实现了这套逻辑只处理 35~39 号键码Home/End/方向键跳过插槽内容从启用的按钮集合中计算下一个焦点并focus({ preventScroll: true })RTL 方向通过$q.lang.rtl取反见 QEditor.js。当前工具栏绑定的命令在编辑时也响应各自的CTRL组合键快捷键会显示在按钮提示中例如CTRL B加粗。你可以通过阻止 QEditor 的keydown事件来接管某个快捷键——例如keydown.ctrl.b.prevent让CTRL B交给你自己的处理器而不是切换加粗onKeydown中会检查e.defaultPrevented后跳过命令执行见 QEditor.js。标签Labeling编辑区对外暴露为多行textbox角色工具栏携带来自 Quasar 语言包的本地化aria-label。编辑区还会把自身属性镜像到 ARIA 角色上placeholder变成aria-placeholderreadonly与disable分别呈现为aria-readonly与aria-disabled。切换类命令加粗、斜体、对齐按钮等通过aria-pressed报告状态而不只靠颜色区分。传递给 QEditor 自身的属性如aria-label、aria-labelledby、aria-describedby会应用到编辑区因此你应该——也应当——为它提供一个可访问名称q-editor v-modelmodel aria-labelPost body /源码中contentAttributes计算属性先填充role: textbox、aria-multiline: true等默认值再通过splitAttrs让消费方提供的属性“获胜”覆盖见 QEditor.js。帮助对话框Help dialogQEditor没有内置列出键盘命令的帮助对话框但一个自定义工具栏插槽即可实现。参考 HelpDialog.vue在工具栏加一个help令牌并用#help插槽放一个按钮点击后弹出q-dialog用q-markup-table列出快捷键TAB、方向键、Home/End、CTRLB/I/U/Z/Y 等q-editor v-modeleditor aria-labelHelp dialog demonstration min-height5rem :toolbar[[bold, italic, underline], [undo, redo], [help]] template #help q-btn dense flat sizesm iconhelp_outline aria-labelEditor help clickshowHelp true / /template /q-editor工具栏插槽与运行期命令调用任意未命中的字符串令牌都会渲染为同名插槽。源码中这类令牌被解析为{ type: slot, slot: token }见 QEditor.js。ToolbarSlot.vue 展示了完整玩法用#token插槽放一个q-btn-dropdown点击菜单项时通过模板引用调用组件实例的runCmd(insertHTML, ...)注入带样式的不可编辑“令牌”元素q-editor v-modeleditor refeditorRef toolbar-text-colorwhite toolbar-toggle-coloryellow-8 toolbar-bgprimary :toolbar[ [token], [bold, italic, underline], [ { label: $q.lang.editor.formatting, icon: $q.iconSet.editor.formatting, list: no-icons, options: [p, h3, h4, h5, h6, code] } ] ] template #token q-btn-dropdown dense no-caps unelevated colorwhite text-colorprimary labelToken sizesm reftokenRef q-list dense q-item taglabel clickable clickadd(email) q-item-section sideq-icon namemail //q-item-section q-item-sectionEmail/q-item-section /q-item q-item taglabel clickable clickadd(title) q-item-section sideq-icon nametitle //q-item-section q-item-sectionTitle/q-item-section /q-item /q-list /q-btn-dropdown /template /q-editorimport { ref, useTemplateRef } from vue const editorRef useTemplateRef(editorRef) const tokenRef useTemplateRef(tokenRef) const editor ref(Customize it.) function add(name) { const edit editorRef.value tokenRef.value.hide() edit.caret.restore() // 恢复光标 edit.runCmd(insertHTML, nbsp;div classeditor_token ... contenteditablefalsenbsp;span${name}/spannbsp;... /divnbsp;) edit.focus() }这个示例同时展示了组件暴露的关键方法与计算属性详见 QEditor.json 的 methods / computedProps成员类型说明runCmd(cmd, param, update true)方法在光标位置与选区执行 contentEditable 命令update控制是否刷新工具栏refreshToolbar()方法隐藏链接编辑器如可见并强制实例重新渲染focus()方法在保存的光标位置聚焦 contentEditablegetContentEl()方法返回编辑区 DOM 元素纯 HTML 内容caret计算属性当前光标状态对象类型QEditorCaret实现在 editor-caret.jsrunCmd的内部流程是先focus()恢复保存的光标应用命令再保存新光标并刷新工具栏见 QEditor.js。组件还支持[command]插槽——即每个命令名都可以用同名插槽整体替换默认按钮。外观与尺寸定制QEditor 的视觉风格主要通过一组 toolbar 属性与内容区属性控制。结合 Custom.vue 与 QEditor.json 的说明属性默认值说明toolbar-color—工具栏按钮与文字的颜色Quasar 调色板toolbar-text-color—工具栏命令文字颜色Quasar 调色板toolbar-bggrey-3运行期默认工具栏背景色toolbar-toggle-colorprimary命令按钮处于激活态时的颜色例如secondary、blue-3toolbar-outlinefalse按钮以 outlined 样式渲染toolbar-pushfalse按钮以 push-button 类型渲染toolbar-roundedfalse按钮圆角渲染min-height10rem编辑区最小高度 CSS 值max-height—编辑区最大高度超出后内部滚动height—编辑区固定高度content-style—编辑区容器的 CSS 对象content-class—编辑区 CSS 类字符串/数组/对象placeholder—占位文本square/flat/densefalse分别去掉圆角、去掉边框、单行紧凑工具栏dark—暗色主题综合示例q-editor v-modeleditor flat content-classbg-amber-3 toolbar-text-colorwhite toolbar-toggle-coloryellow-8 toolbar-bgprimary :toolbar[ [bold, italic, underline], [ { label: $q.lang.editor.formatting, icon: $q.iconSet.editor.formatting, list: no-icons, options: [p, h3, h4, h5, h6, code] } ] ] /全功能示例 KitchenSink.vue 展示了近乎完整的工具栏编排两种对齐下拉only-icons与默认、文本样式、token/hr/link/custom_btn、print/fullscreen、格式化下拉、字号下拉size-1~size-7、字体族下拉配合fonts、removeFormat、引用/列表/缩进、undo/redo、viewsource并在窄屏下用:dense$q.screen.lt.md自动切换紧凑模式。值得注意工具栏使用 Quasar 调色板类名源码中toolbarBackgroundClass通过模板字符串拼接bg-${props.toolbarBg}生成见 QEditor.js因此颜色值必须来自 Quasar 调色板。viewsource命令切换“查看源码”模式此时onInput读取的是innerText而非innerHTML见 QEditor.js。事件一览除update:modelValue外组件还暴露见 QEditor.json events 表事件触发时机update:model-value内容变化参数为内容的纯 HTMLdropdown-show/dropdown-hide工具栏下拉显示 / 隐藏之后dropdown-before-show/dropdown-before-hide工具栏下拉显示 / 隐藏之前link-show/link-hide链接编辑工具栏显示 / 隐藏keydown/click/focus/blur透传的编辑区交互事件内部标记实战注意事项Caveats关闭自动纠错与拼写检查很多现代浏览器内置自动更正、自动补全、首字母大写与拼写检查功能若想关闭把q-editor包进一个form并设置对应属性即可form autocorrectoff autocapitalizeoff autocompleteoff spellcheckfalse q-editor v-modeleditor / /form图片粘贴与拖放从剪贴板粘贴图片、或把图片拖入编辑器在各浏览器间行为差异很大而且高度取决于图片最初是如何进入剪贴板的。事实上直到不久之前Firefox 中还能在 ContentEditable 里直接缩放图片。官方建议若需要支持图片粘贴/拖放请自行编写处理逻辑并监听paste与dropq-editor v-modeleditor pasteevt pasteCapture(evt) dropevt dropCapture(evt) /纯文本粘贴若粘贴事件的 content type 为文本取决于文本来源contentEditable 可能已经自动解析出大量标记。若只想粘贴“干净、无标记”的文本参考 Pasting.vue 的做法在paste中preventDefault()后从clipboardData.getData(text/plain)取纯文本再用runCmd(insertText, text)插入function onPaste(evt) { // 让输入框自行处理避免破坏链接粘贴 if (evt.target.nodeName INPUT) return evt.preventDefault() evt.stopPropagation() if (evt.originalEvent evt.originalEvent.clipboardData.getData) { const text evt.originalEvent.clipboardData.getData(text/plain) editorRef.value.runCmd(insertText, text) } else if (evt.clipboardData evt.clipboardData.getData) { const text evt.clipboardData.getData(text/plain) editorRef.value.runCmd(insertText, text) } else if (window.clipboardData window.clipboardData.getData) { // 旧 IE 兼容分支 editorRef.value.runCmd(ms-pasteTextOnly, text) } }该示例同样把form的自动纠错/拼写检查关闭与纯文本粘贴结合使用。打印如果没有设置字体或用户未选择打印对话框会默认使用系统字体结果随浏览器和操作系统而异设计打印样式时需考虑这一点。国际化QEditor 的提示文本由 Quasar 语言包翻译只需切换语言即可同步界面语言。若你的语言包缺失或存在错误官方欢迎以 PR 形式提供更新。语言包与图标集在源码中的角色可见于buttonDef提示取$q.lang.editor.*图标取$q.iconSet.editor.*见 QEditor.js这也是官方示例中推荐用$q.lang.editor.formatting、$q.iconSet.editor.formatting之类引用下拉标签/图标的原因。源码级要点回顾默认工具栏[[left, center, right, justify], [bold, italic, underline, strike], [undo, redo]]QEditor.js段落标签paragraphTag仅接受div或p默认div启动时写入defaultParagraphSeparatorQEditor.js光标管理独立的Caret类负责选区保存/恢复与命令执行editor-caret.js失焦时保存、聚焦/命令执行时恢复下拉解析options中未知的命令会被静默忽略与未知的普通令牌行为一致QEditor.js无障碍实现data-tbi标记工具栏控件、RTL 方向镜像、Home/End/方向键循环导航QEditor.js测试覆盖仓库提供 QEditor.test.js 与 QEditor.hydration.test.js含 QEditor.hydration.fixtures.js样式定义见 QEditor.sass。综上所述QEditor 的价值在于“用配置表达能力”绝大多数富文本场景无需写一行编辑器逻辑只要把toolbar、definitions、fonts组合好即可而遇到粘贴净化、图片处理、自定义令牌等进阶需求时runCmd、caret、事件与插槽又提供了足够的逃生舱。使用时请始终牢记 XSS 风险对用户产生的内容在渲染与服务端两侧做好消毒。【免费下载链接】quasarQuasar Framework - Build high-performance VueJS user interfaces in record time项目地址: https://gitcode.com/gh_mirrors/qu/quasar创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表