返回 导航

Vue.js

hangge.com

Vue.js - 富文本编辑器Tiptap使用详解4(自定义扩展1:Highlight)

作者:hangge | 2026-02-14 12:09
    我们可以使用 Tiptap 的扩展机制来自定义或扩展编辑器功能(基于现有扩展定制,或者从头创建 Mark / Node / NodeView)。前文我介绍了如何使用 tiptap 官方提供的@tiptap/extension-highlight 扩展实现高亮功能(点击查看),本文还是以该功能为例,演示如何手动实现一个自定义高亮(Highlight)的扩展。

四、自定义实现高亮(Highlight)扩展

1,基本介绍

(1)首先我们需要熟悉如下一些概念:
  • Extension:是 Tiptap 的扩展单元,底层基于 ProseMirrorExtension 可是 Node(块或内联内容)、Mark(文本内的格式/注释)或通用扩展(插件/命令/键盘等)。创建扩展时会实现 parseHTMLrenderHTMLaddCommands()addAttributes()addNodeView() 等钩子。
  • Mark:用于内联文本样式(加粗、斜体、链接、下划线、highlight 等)。适合“标记”或注释文本。
  • Node:文档树的节点(段落、图片、附件、表格、引用、嵌入组件等)。当需要可交互或包含子内容(content)时用 Node

(2)开发扩展惯用步骤如下:
  • 决定类型:Markinline)或 Nodeblock/inline、可含子内容)。
  • 设计 schema(属性/attrscontentgroupinline/atom/defining 等)。
  • 实现 parseHTML()(如何从 HTML 恢复)与 renderHTML()(如何输出 HTML)。可用 mergeAttributes() 帮助合并 attrs
  • 提供 addCommands() 将常用操作封装成命令,便于在 UI 中调用(editor.chain().focus().yourCommand().run())。
  • 如需交互/复杂 UI,使用 NodeView,并在 Vue 中通过 VueNodeViewRenderer 渲染组件。
  • 测试:editor.getJSON() / editor.getHTML() 验证文档模型及序列化结果。

2,实现扩展

我们创建一个名为 highlight.js 自定义扩展,代码如下:
import { Mark, mergeAttributes } from '@tiptap/core'

export default Mark.create({
  name: 'highlight',

  addOptions() {
    return {
      HTMLAttributes: {},
      multicolor: false, // 默认不开启多色支持
    }
  },

  // 根据 multicolor 决定要不要加入 color 属性
  addAttributes() {
    if (this.options.multicolor) {
      return {
        color: {
          default: null,
          parseHTML: element => {
            // 尝试从 data-color 或 style 提取 color
            return (
              element.getAttribute('data-color') ||
              (element.style && element.style.backgroundColor) ||
              null
            )
          },
          renderHTML: attrs => {
            if (!attrs.color) return {}
            return {
              'data-color': attrs.color,
              style: `background-color: ${attrs.color}`,
            }
          },
        },
        ...this.options.HTMLAttributes,
      }
    }

    // 不支持多色时,不暴露 color 属性
    return {
      ...this.options.HTMLAttributes,
    }
  },

  parseHTML() {
    // 支持 <mark>,以及可能带 data-color 或 style 的 span/mark
    const rules = [{ tag: 'mark' }]
    if (this.options.multicolor) {
      rules.push({ tag: 'span[data-color]' })
      rules.push({ tag: 'span[style*="background-color"]' })
    }
    return rules
  },

  renderHTML({ HTMLAttributes }) {
    // 如果没有 color 或 multicolor 被禁用,则渲染为 <mark>
    if (!this.options.multicolor || !HTMLAttributes.color) {
      return ['mark', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]
    }

    // multicolor 且有 color:渲染带 style 的 mark(或 span)
    return [
      'mark',
      mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
      0,
    ]
  },

  addCommands() {
    return {
      // setHighlight 可以接受 { color } 或 undefined
      setHighlight:
        attrs =>
        ({ commands }) => {
          // 当 multicolor 关闭时忽略 color 字段
          const cleanAttrs = this.options.multicolor && attrs 
            && attrs.color ? { color: attrs.color } : {}
          return commands.setMark(this.name, cleanAttrs)
        },

      unsetHighlight:
        () =>
        ({ commands }) => {
          return commands.unsetMark(this.name)
        },

      toggleHighlight:
        attrs =>
        ({ commands, editor }) => {
          // 如果 multicolor 开启并传入 color,则优先使用 setHighlight
          if (this.options.multicolor && attrs && attrs.color) {
            // 如果当前选区已高亮且颜色相同 -> 取消;否则设置为新颜色
            if (editor.isActive(this.name, { color: attrs.color })) {
              return commands.unsetHighlight()
            } else {
              return commands.setHighlight({ color: attrs.color })
            }
          }
          // 无 color 情况下切换(simple toggle)
          return commands.toggleMark(this.name)
        },
    }
  },

  addKeyboardShortcuts() {
    // 示例:Ctrl/Cmd-Shift-H 作为高亮快捷键(不传颜色,效果为 toggle)
    return {
      'Mod-Shift-h': () => this.editor.commands.toggleHighlight(),
    }
  },
})

3,使用扩展

(1)自定义扩展创建后后我们就可以使用该扩展了,具体用法和官方提供的 Highlight 扩展一致。
<template>
  <div class="editor-container">

    <!-- 工具栏 -->
    <div class="menu-bar" v-if="editor">

      <!-- 高亮按钮(默认切换) -->
      <button
        :class="{ active: editor.isActive('highlight') }"
        @click="toggleHighlight"
      >
        Highlight
      </button>

      <!-- 多颜色选择 -->
      <span class="color-bar">
        <button
          v-for="c in colors"
          :key="c"
          class="color-btn"
          :style="{ backgroundColor: c }"
          @click="setHighlightColor(c)"
        ></button>
      </span>

      <!-- 取消高亮 -->
      <button @click="clearHighlight">Clear</button>
    </div>

    <!-- 编辑器 -->
    <EditorContent :editor="editor" class="editor-content"/>
  </div>
</template>

<script>
import { Editor, EditorContent } from '@tiptap/vue-2'
import StarterKit from '@tiptap/starter-kit'
import Highlight from './highlight.js'

export default {
  components: { EditorContent },

  data() {
    return {
      editor: null,

      // 可自定义更多颜色
      colors: ['#ffeb3b', '#ff8a80', '#80d8ff', '#ccff90', '#ffd180'],
    }
  },

  mounted() {
    this.editor = new Editor({
      extensions: [
        StarterKit,

        // multicolor: true 允许自定义颜色
        Highlight.configure({
          multicolor: true,
        }),
      ],
      content: `
        <p>选中某些文字并使用上方颜色按钮,即可应用高亮颜色。</p>
      `,
    })
  },

  beforeDestroy() {
    this.editor.destroy()
  },

  methods: {
    toggleHighlight() {
      this.editor.chain().focus().toggleHighlight().run()
    },

    setHighlightColor(color) {
      this.editor.chain().focus().setHighlight({ color }).run()
    },

    clearHighlight() {
      this.editor.chain().focus().unsetHighlight().run()
    },
  },
}
</script>

<style>
.editor-container {
  border: 1px solid #ccc;
  border-radius: 4px;
}

/* 工具栏 */
.menu-bar {
  padding: 8px;
  border-bottom: 1px solid #eee;
  display: flex;
  align-items: center;
  gap: 10px;
}

/* 按钮样式 */
.menu-bar button {
  padding: 6px 12px;
  border: 1px solid #bbb;
  background: #fafafa;
  cursor: pointer;
  border-radius: 4px;
}

.menu-bar button.active {
  background: #ffe066;
}

/* 颜色按钮 */
.color-bar {
  display: flex;
  gap: 8px;
  margin-left: 15px;
}

.color-btn {
  width: 22px;
  height: 22px;
  cursor: pointer;
  border-radius: 4px;
  border: 1px solid #aaa;
  padding: 0;
}
</style>

(2)运行后,我们可以选中文字后应用高亮,并且可以选择不同颜色进行文字高亮,或者取消高亮。
评论

全部评论(0)

回到顶部