tiptap menus 菜单

tiptap编辑器非常简洁,你可以完全的定制它的外观, menus菜单也一样。 tiptap提供的API来触发命令和添加活动状态, tiptap提供了一些实用程序和组件使菜单的定位更容易。

固定菜单

tiptap并没有提供类似其他编辑器的顶部菜单组件,但是你可以自己用任何html元素来实现。

气泡菜单

当用户选中编辑器中的文字时,tiptap将会在光标上方弹出菜单,JavaScript中用法如下

import { Editor } from '@tiptap/core'
import BubbleMenu from '@tiptap/extension-bubble-menu'

new Editor({
  extensions: [
    BubbleMenu.configure({
      element: document.querySelector('.menu'),
    }),
  ],
})

tiptap vue3 中气泡菜单用法如下

<template>
  <div>
    <bubble-menu :editor="state.editor" :tippy-options="{ duration: 100 }" v-if="state.editor">
      <button @click="state.editor.chain().focus().toggleBold().run()"
        :class="{ 'is-active': state.editor.isActive('bold') }">
        bold
      </button>
      <button @click="state.editor.chain().focus().toggleItalic().run()"
        :class="{ 'is-active': state.editor.isActive('italic') }">
        italic
      </button>
      <button @click="state.editor.chain().focus().toggleStrike().run()"
        :class="{ 'is-active': state.editor.isActive('strike') }">
        strike
      </button>
    </bubble-menu>
    <editor-content :editor="state.editor" />
  </div>
</template>
<script setup>
/*
 tiptap 中文文档
 https://www.itxst.com/tiptap/tutorial.html
*/
import { onMounted, reactive } from "vue";
import { BubbleMenu, Editor, EditorContent } from "@tiptap/vue-3";
import StarterKit from "@tiptap/starter-kit";

const state = reactive({
  editor: new Editor({
    content: "<p>Tiptap 中文文档</p>",
    extensions: [StarterKit],
    autofocus: true,
    editable: true,
  }) as any,
});

onMounted(() => { });
</script>
<style scoped>
.editor {
  margin: 10px 20px;
  width: 390px;
  border-radius: 6px;
}

.editor:deep(.ProseMirror) {
  color: red;
  border: solid 1px #ddd;
  padding: 0px 6px;
  border-radius: 6px;
}
</style>

在 Vue2 中试一试

react 中气泡菜单用法如下

import './styles.scss'
import { BubbleMenu, EditorContent, useEditor } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
import React, { useEffect } from 'react'

export default () => {
  const editor = useEditor({
    extensions: [
      StarterKit,
    ],
    content: `
      <p>
        Hey, try to select some text here. There will popup a menu for selecting some inline styles. Remember: you have full control about content and styling of this menu.
      </p>
    `,
  })

  const [isEditable, setIsEditable] = React.useState(true)

  useEffect(() => {
    if (editor) {
      editor.setEditable(isEditable)
    }
  }, [isEditable, editor])

  return (
    <>
      <div>
        <input type="checkbox" checked={isEditable} onChange={() => setIsEditable(!isEditable)} />
        Editable
      </div>
      {editor && <BubbleMenu editor={editor} tippyOptions={{ duration: 100 }}>
        <button
          onClick={() => editor.chain().focus().toggleBold().run()}
          className={editor.isActive('bold') ? 'is-active' : ''}
        >
          bold
        </button>
        <button
          onClick={() => editor.chain().focus().toggleItalic().run()}
          className={editor.isActive('italic') ? 'is-active' : ''}
        >
          italic
        </button>
        <button
          onClick={() => editor.chain().focus().toggleStrike().run()}
          className={editor.isActive('strike') ? 'is-active' : ''}
        >
          strike
        </button>
      </BubbleMenu>}
      <EditorContent editor={editor} />
    </>
  )
}

浮动菜单

当点击tiptap编辑器空白行时,将会显示浮动的菜单,下面的代码将会展示在JS、VUE、React中的使用方法。

在js中的使用方法

import { Editor } from '@tiptap/core'
import FloatingMenu from '@tiptap/extension-floating-menu'

new Editor({
  extensions: [
    FloatingMenu.configure({
      element: document.querySelector('.menu'),
    }),
  ],
})

在Vue3中的使用方法

<template>
  <div class="editor">
    <floating-menu :editor="state.editor" :tippy-options="{ duration: 100 }" v-if="state.editor">
      <button @click="state.editor.chain().focus().toggleHeading({ level: 1 }).run()" :class="{ 'is-active': state.editor.isActive('heading', { level: 1 }) }">
        H1
      </button>
      <button @click="state.editor.chain().focus().toggleHeading({ level: 2 }).run()" :class="{ 'is-active': state.editor.isActive('heading', { level: 2 }) }">
        H2
      </button>
      <button @click="state.editor.chain().focus().toggleBulletList().run()" :class="{ 'is-active': state.editor.isActive('bulletList') }">
        Bullet List
      </button>
    </floating-menu>
    <editor-content :editor="state.editor" />
  </div>
</template>
<script setup lang="ts">
/*
 tiptap 中文文档
 https://www.itxst.com/tiptap/tutorial.html
*/
import { onMounted, reactive } from "vue";
import { FloatingMenu , Editor, EditorContent } from "@tiptap/vue-3";
import StarterKit from "@tiptap/starter-kit";

const state = reactive({
  editor: new Editor({
    content: "<p>Tiptap 中文文档,点击编辑器的空白处试试看</p>",
    extensions: [StarterKit],
    autofocus: true,
    editable: true,
  }) as any,
});

onMounted(() => { });
</script>
<style scoped>
.editor {
  margin: 10px 20px;
  width: 390px;
  border-radius: 6px;
}

.editor:deep(.ProseMirror) {
  color: red;
  border: solid 1px #ddd;
  padding: 0px 6px;
  border-radius: 6px;
  min-height: 160px;
}
</style>

在 Vue2 中试一试

在React中的使用方法

import './styles.scss'
import { EditorContent, FloatingMenu, useEditor } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
import React, { useEffect } from 'react'

export default () => {
  const editor = useEditor({
    extensions: [
      StarterKit,
    ],
    content: `
      <p>
        This is an example of a Medium-like editor. Enter a new line and some buttons will appear.
      </p>
      <p></p>
    `,
  })


  const [isEditable, setIsEditable] = React.useState(true)


  useEffect(() => {
    if (editor) {
      editor.setEditable(isEditable)
    }
  }, [isEditable, editor])


  return (
    <>
      <div>
        <input type="checkbox" checked={isEditable} onChange={() => setIsEditable(!isEditable)} />
        Editable
      </div>
      {editor && <FloatingMenu editor={editor} tippyOptions={{ duration: 100 }}>
        <button
          onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
          className={editor.isActive('heading', { level: 1 }) ? 'is-active' : ''}
        >
          h1
        </button>
        <button
          onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
          className={editor.isActive('heading', { level: 2 }) ? 'is-active' : ''}
        >
          h2
        </button>
        <button
          onClick={() => editor.chain().focus().toggleBulletList().run()}
          className={editor.isActive('bulletList') ? 'is-active' : ''}
        >
          bullet list
        </button>
      </FloatingMenu>}
      <EditorContent editor={editor} />
    </>
  )
}
//样式文件
import './styles.scss'
.ProseMirror {  > * + * {    margin-top: 0.75em;  }  ul,  ol {    padding: 0 1rem;  }}

菜单命令

tiptap编辑器的加粗和颜色菜单是如何让选中的对象/文字改变颜色大小的呢?当鼠标点击对象/文字菜单又是如何知道是加粗菜单还是设置颜色菜单的呢?以下代码表示将选中文字加粗

editor.chain().focus().toggleBold().run()
  • editor编辑器对象

  • chain告诉编辑器需要执行命令

  • focus执行完后让编辑器获得焦点

  • toggleBold加粗选中的文字

  • run执行命令

激活状态,当选中文字时可以获取编辑器的激活状态是加粗还是颜色等,下面的代码表示当选中的文字被加粗则给按钮加上“is-active”样式

<button :class="{ 'is-active': editor.isActive('bold') }" @click="editor.chain().focus().toggleBold().run()">  Bold</button>

isActive方法适应nodes 和marks还支持特定的属性,如下代码当设置颜色为#000000,选中这个颜色才能激活颜色菜单

//选择#000000颜色才返回是否激活状态
editor.isActive('textStyle', { color: '#000000' })
//也可以是正则表达式
editor.isActive('textStyle', { color: /.*/ })

文字颜色

<template>
  <div class="editor">
    <span :class="{'is-active-color': state.editor.isActive('textStyle', {color: 'rgb(0, 0, 0)',})}">
      {{ state.editor.getAttributes("textStyle").color }}
      <input type="color" @input="onColor" :value="state.editor.getAttributes('textStyle').color" />
    </span>
    <editor-content :editor="state.editor" />
  </div>
</template>
<script setup lang="ts">
/*
 tiptap 中文文档
 https://www.itxst.com/tiptap/tutorial.html
*/
import { onMounted, reactive } from "vue";
import { Editor, EditorContent } from "@tiptap/vue-3";
import StarterKit from "@tiptap/starter-kit";
import Color from "@tiptap/extension-color";
import TextStyle from "@tiptap/extension-text-style";

const state = reactive({
  editor: new Editor({
    content:
      'Tiptap 中文文档,<span style="color: #000000">点击黑色文字</span>试试看效果',
    extensions: [
      StarterKit,
      TextStyle,
      Color.configure({
        types: ["textStyle"],
      }),
    ],
    autofocus: true,
    editable: true,
  }) as any,
});

onMounted(() => { });

//设置颜色
const onColor = ($event: any) => {
  // debugger;
  state.editor.chain().focus().setColor($event.target.value).run();
};
</script>
<style scoped>
.editor {
  margin: 10px 20px;
  width: 390px;
  border-radius: 6px;
}

.editor:deep(.ProseMirror) {
  color: red;
  border: solid 1px #ddd;
  padding: 0px 6px;
  border-radius: 6px;
  min-height: 160px;
}

.is-active-color {
  background-color: black;
  padding: 6px 6px;
  color: #fff;
}
</style>

下载教程 Demo

本教程Demo的源码点击这里下载,Tiptap Demo,下载完成后运行npm i初始化依赖包。