forked from opentiny/tiny-vue
feat(rich-text-editor): Add api design (#475)
* feat(rich-text-editor): Add api design * feat(rich-text-editor): change code * feat(rich-text-editor): delete log --------- Co-authored-by: 常浩-BJS21325 <changhao01@youdao>
This commit is contained in:
parent
7d6120ed1d
commit
51c46cbd42
|
@ -23,11 +23,140 @@ export default {
|
||||||
'en-US': 'default rich text content'
|
'en-US': 'default rich text content'
|
||||||
},
|
},
|
||||||
demoId: 'basic-usage'
|
demoId: 'basic-usage'
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
'name': 'collaboration',
|
||||||
|
'type': 'Boolean',
|
||||||
|
'defaultValue': 'false',
|
||||||
|
desc: {
|
||||||
|
'zh-CN': '是否开启协同编辑,默认不开启',
|
||||||
|
'en-US': 'Whether to enable collaborative editing. It is disabled by default'
|
||||||
|
},
|
||||||
|
demoId: 'basic-usage'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'customToolBar',
|
||||||
|
'type': 'Array',
|
||||||
|
'defaultValue': '[]',
|
||||||
|
desc: {
|
||||||
|
'zh-CN': '传入需要展示的工具栏按钮配置,自定义使用',
|
||||||
|
'en-US': 'Pass in the toolbar button configuration that needs to be displayed, and customize the use'
|
||||||
|
},
|
||||||
|
demoId: 'basic-usage'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'placeholder',
|
||||||
|
'type': 'Stirng',
|
||||||
|
'defaultValue': 'Write soming ...',
|
||||||
|
desc: {
|
||||||
|
'zh-CN': '占位符,在v-model为空时展示',
|
||||||
|
'en-US': 'Placeholder, displayed when v-model is empty'
|
||||||
|
},
|
||||||
|
demoId: 'basic-usage'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'options',
|
||||||
|
'type': 'Object',
|
||||||
|
'defaultValue': '{}',
|
||||||
|
desc: {
|
||||||
|
'zh-CN': '参见tiptap扩展说明,会覆盖useEditor配置项',
|
||||||
|
'en-US': 'See tiptap extension notes to overwrite the useEditor configuration item'
|
||||||
|
},
|
||||||
|
demoId: 'basic-usage'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'events': [
|
||||||
|
{
|
||||||
|
'name': 'update',
|
||||||
|
'type': '',
|
||||||
|
'defaultValue': '',
|
||||||
|
'desc': {
|
||||||
|
'zh-CN': '当编辑器状态改变完成后,将会触发该事件,
|
||||||
|
'en-US': 'When the content is updated.'
|
||||||
|
},
|
||||||
|
'demoId': 'base'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'beforeCreate',
|
||||||
|
'type': '',
|
||||||
|
'defaultValue': '',
|
||||||
|
'desc': {
|
||||||
|
'zh-CN': '当编辑器视图创造之前,将会触发该事件',
|
||||||
|
'en-US': 'Before view creation.'
|
||||||
|
},
|
||||||
|
'demoId': 'base'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'create',
|
||||||
|
'type': '',
|
||||||
|
'defaultValue': '',
|
||||||
|
'desc': {
|
||||||
|
'zh-CN': '当编辑器已经挂载好,将会触发该事件',
|
||||||
|
'en-US': 'The editor is mounted.'
|
||||||
|
},
|
||||||
|
'demoId': 'base'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'focus',
|
||||||
|
'type': '',
|
||||||
|
'defaultValue': '',
|
||||||
|
'desc': {
|
||||||
|
'zh-CN': '当编辑器获得焦点,将会触发该事件',
|
||||||
|
'en-US': 'The editor gets focus.'
|
||||||
|
},
|
||||||
|
'demoId': 'base'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'blur',
|
||||||
|
'type': '',
|
||||||
|
'defaultValue': '',
|
||||||
|
'desc': {
|
||||||
|
'zh-CN': '当编辑器失去焦点,将会触发该事件',
|
||||||
|
'en-US': 'The editor loses focus.'
|
||||||
|
},
|
||||||
|
'demoId': 'base'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'selectionUpdate',
|
||||||
|
'type': '',
|
||||||
|
'defaultValue': '',
|
||||||
|
'desc': {
|
||||||
|
'zh-CN': '当编辑器选区改变,将会触发该事件',
|
||||||
|
'en-US': 'The selection has changed.'
|
||||||
|
},
|
||||||
|
'demoId': 'base'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'transaction',
|
||||||
|
'type': '',
|
||||||
|
'defaultValue': '',
|
||||||
|
'desc': {
|
||||||
|
'zh-CN': '当编辑器状态改变,将会触发该事件。',
|
||||||
|
'en-US': 'The editor state has changed.'
|
||||||
|
},
|
||||||
|
'demoId': 'base'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'destroy',
|
||||||
|
'type': '',
|
||||||
|
'defaultValue': '',
|
||||||
|
'desc': {
|
||||||
|
'zh-CN': '当编辑器编辑器销毁了,将会触发该事件',
|
||||||
|
'en-US': 'The editor is being destroyed.'
|
||||||
|
},
|
||||||
|
'demoId': 'base'
|
||||||
|
},
|
||||||
],
|
],
|
||||||
'events': [],
|
|
||||||
'methods': [],
|
'methods': [],
|
||||||
'slots': []
|
'slots': [
|
||||||
|
{
|
||||||
|
'name': 'toolBar',
|
||||||
|
'type': '',
|
||||||
|
'defaultValue': '',
|
||||||
|
'desc': { 'zh-CN': 'toolBar添加按钮,会传出editor实例,详情见tiptap', 'en-US': 'toolBar Add Button' },
|
||||||
|
'demoId': 'custom-search-types'
|
||||||
|
},
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,57 +1,50 @@
|
||||||
export const handleChange = (editor) => {
|
export const handleChange = (editor) => {
|
||||||
return (event) => {
|
return (event) => {
|
||||||
const file = event.target.files[0]
|
const file = event.target.files[0]
|
||||||
if (!file.type.match("image.*")) {
|
if (!file.type.match('image.*')) {
|
||||||
console.log("请选择图片文件!")
|
console.log('请选择图片文件!')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const reader = new FileReader()
|
const reader = new FileReader()
|
||||||
reader.onload = function (e) {
|
reader.onload = function (e) {
|
||||||
editor.value.chain().focus().setImage({ src: e.target?.result }).run()
|
editor.value
|
||||||
|
.chain()
|
||||||
|
.focus()
|
||||||
|
.setImage({ src: e.target?.result })
|
||||||
|
.run()
|
||||||
}
|
}
|
||||||
reader.readAsDataURL(file)
|
reader.readAsDataURL(file)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export const setLink = (editor) => {
|
export const setLink = (editor) => {
|
||||||
return () => {
|
return () => {
|
||||||
const previousUrl = editor.value.getAttributes('link').href
|
const previousUrl = editor.getAttributes('link').href
|
||||||
const url = window.prompt('URL', previousUrl)
|
const url = window.prompt('URL', previousUrl)
|
||||||
if (url === null) {
|
if (url === null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (url === '') {
|
if (url === '') {
|
||||||
editor.value
|
editor.chain().focus().extendMarkRange('link').unsetLink().run()
|
||||||
.chain()
|
|
||||||
.focus()
|
|
||||||
.extendMarkRange('link')
|
|
||||||
.unsetLink()
|
|
||||||
.run()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
editor.value
|
editor.chain().focus().extendMarkRange('link').setLink({ href: url }).run()
|
||||||
.chain()
|
|
||||||
.focus()
|
|
||||||
.extendMarkRange('link')
|
|
||||||
.setLink({ href: url })
|
|
||||||
.run()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// table 处理逻辑
|
// table 处理逻辑
|
||||||
export const handleMove = (state, box) => {
|
export const handleMove = (state, box) => {
|
||||||
return (e) => {
|
return (e) => {
|
||||||
let { x, y } = box.value.getBoundingClientRect()
|
let { x, y } = box.value[0].getBoundingClientRect()
|
||||||
state.flagX = Math.ceil((e.x - x) / 30) // 后期改变30就可以
|
state.flagX = Math.ceil((e.x - x) / 30) // 后期改变30就可以
|
||||||
state.flagY = Math.ceil((e.y - y) / 30)
|
state.flagY = Math.ceil((e.y - y) / 30)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export const handleClickOutside = (state, box) => {
|
export const handleClickOutside = (state, box) => {
|
||||||
return (e) => {
|
return (e) => {
|
||||||
if (!box.value?.contains(e.target)) {
|
if (!box.value[0]?.contains(e.target)) {
|
||||||
state.isShow = false
|
state.isShow = false
|
||||||
removeClickOutside(state, box)()
|
removeClickOutside(state, box)()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
export const removeClickOutside = (state, box) => {
|
export const removeClickOutside = (state, box) => {
|
||||||
return () => {
|
return () => {
|
||||||
|
@ -60,7 +53,7 @@ export const removeClickOutside = (state, box) => {
|
||||||
}
|
}
|
||||||
export const handleClick = (state, box) => {
|
export const handleClick = (state, box) => {
|
||||||
return (e) => {
|
return (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation()
|
||||||
if (state.isShow) {
|
if (state.isShow) {
|
||||||
if (state.flagX && state.flagY) {
|
if (state.flagX && state.flagY) {
|
||||||
state.editor.chain().focus().insertTable({ rows: state.flagX, cols: state.flagY, withHeaderRow: true }).run()
|
state.editor.chain().focus().insertTable({ rows: state.flagX, cols: state.flagY, withHeaderRow: true }).run()
|
||||||
|
@ -77,13 +70,165 @@ export const handleClick = (state, box) => {
|
||||||
// bubble菜单
|
// bubble菜单
|
||||||
export const shouldShow = ({ editor, view, state, oldState, from, to }) => {
|
export const shouldShow = ({ editor, view, state, oldState, from, to }) => {
|
||||||
// 仅在无序列表选中的时候才显示 气泡菜单
|
// 仅在无序列表选中的时候才显示 气泡菜单
|
||||||
return editor.isActive("table");
|
return editor.isActive('table')
|
||||||
};
|
}
|
||||||
// font-size 设置
|
// font-size 设置
|
||||||
export const handleFontSize = (fontSize) => {
|
export const handleFontSize = (fontSize) => {
|
||||||
return (value) => {
|
return (value) => {
|
||||||
console.log('123', value);
|
fontSize.value = value + 'px'
|
||||||
|
|
||||||
fontSize.value = value + "px";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理参数实现自定义展示
|
||||||
|
const eventMap = new Map()
|
||||||
|
eventMap.set('bold', (editor) => {
|
||||||
|
editor.chain().focus().toggleBold().run()
|
||||||
|
})
|
||||||
|
eventMap.set('italic', (editor) => {
|
||||||
|
editor.chain().focus().toggleItalic().run()
|
||||||
|
})
|
||||||
|
eventMap.set('link', (editor) => {
|
||||||
|
setLink(editor)()
|
||||||
|
})
|
||||||
|
eventMap.set('unlink', (editor) => {
|
||||||
|
editor.chain().focus().unsetLink().run()
|
||||||
|
})
|
||||||
|
eventMap.set('highlight', (editor) => {
|
||||||
|
editor.chain().focus().toggleHighlight().run()
|
||||||
|
})
|
||||||
|
eventMap.set('underline', (editor) => {
|
||||||
|
editor.chain().focus().toggleUnderline().run()
|
||||||
|
})
|
||||||
|
eventMap.set('strike', (editor) => {
|
||||||
|
editor.chain().focus().toggleStrike().run()
|
||||||
|
})
|
||||||
|
eventMap.set('subscript', (editor) => {
|
||||||
|
editor.chain().focus().toggleSubscript().run()
|
||||||
|
})
|
||||||
|
eventMap.set('superscript', (editor) => {
|
||||||
|
editor.chain().focus().toggleSuperscript().run()
|
||||||
|
})
|
||||||
|
eventMap.set('code', (editor) => {
|
||||||
|
editor.chain().focus().toggleCode().run()
|
||||||
|
})
|
||||||
|
eventMap.set('unorderedlist', (editor) => {
|
||||||
|
editor.chain().focus().toggleBulletList().run()
|
||||||
|
})
|
||||||
|
eventMap.set('orderedlist', (editor) => {
|
||||||
|
editor.chain().focus().toggleOrderedList().run()
|
||||||
|
})
|
||||||
|
eventMap.set('taskList', (editor) => {
|
||||||
|
editor.chain().focus().toggleTaskList().run()
|
||||||
|
})
|
||||||
|
eventMap.set('quote', (editor) => {
|
||||||
|
editor.chain().focus().toggleBlockquote().run()
|
||||||
|
})
|
||||||
|
eventMap.set('code-block', (editor) => {
|
||||||
|
editor.chain().focus().toggleCodeBlock().run()
|
||||||
|
})
|
||||||
|
eventMap.set('format-clear', (editor) => {
|
||||||
|
editor.chain().focus().unsetAllMarks().run()
|
||||||
|
})
|
||||||
|
eventMap.set('node-delete', (editor) => {
|
||||||
|
editor.chain().focus().clearNodes().run()
|
||||||
|
})
|
||||||
|
eventMap.set('undo', (editor) => {
|
||||||
|
editor.chain().focus().undo().run()
|
||||||
|
})
|
||||||
|
eventMap.set('redo', (editor) => {
|
||||||
|
editor.chain().focus().redo().run()
|
||||||
|
})
|
||||||
|
eventMap.set('left', (editor) => {
|
||||||
|
editor.chain().focus().setTextAlign('left').run()
|
||||||
|
})
|
||||||
|
eventMap.set('center', (editor) => {
|
||||||
|
editor.chain().focus().setTextAlign('center').run()
|
||||||
|
})
|
||||||
|
eventMap.set('right', (editor) => {
|
||||||
|
editor.chain().focus().setTextAlign('right').run()
|
||||||
|
})
|
||||||
|
|
||||||
|
export const eventClick = (editor, item) => {
|
||||||
|
if (typeof item === 'string') {
|
||||||
|
eventMap.get(item)(editor)
|
||||||
|
} else {
|
||||||
|
eventMap.get(item.name)(editor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const imgMap = new Map()
|
||||||
|
imgMap.set('bold', 'TinyIconRichTextBold')
|
||||||
|
imgMap.set('italic', 'TinyIconRichTextItalic')
|
||||||
|
imgMap.set('link', 'TinyIconRichTextLink')
|
||||||
|
imgMap.set('unlink', 'TinyIconRichTextLinkUnlink')
|
||||||
|
imgMap.set('highlight', 'TinyIconRichTextHighLight')
|
||||||
|
imgMap.set('underline', 'TinyIconRichTextUnderline')
|
||||||
|
imgMap.set('strike', 'TinyIconRichTextStrikeThrough')
|
||||||
|
imgMap.set('subscript', 'TinyIconRichTextSubscript')
|
||||||
|
imgMap.set('superscript', 'TinyIconRichTextSuperscript')
|
||||||
|
imgMap.set('code', 'TinyIconRichTextCodeView')
|
||||||
|
imgMap.set('unorderedlist', 'TinyIconRichTextListUnordered')
|
||||||
|
imgMap.set('orderedlist', 'TinyIconRichTextListOrdered')
|
||||||
|
imgMap.set('taskList', 'TinyIconRichTextTaskList')
|
||||||
|
imgMap.set('quote', 'TinyIconRichTextQuoteText')
|
||||||
|
imgMap.set('code-block', 'TinyIconRichTextCodeBlock')
|
||||||
|
imgMap.set('format-clear', 'TinyIconRichTextFormatClear')
|
||||||
|
imgMap.set('node-delete', 'TinyIconRichTextNodeDelete')
|
||||||
|
imgMap.set('undo', 'TinyIconRichTextUndo')
|
||||||
|
imgMap.set('redo', 'TinyIconRichTextRedo')
|
||||||
|
imgMap.set('left', 'TinyIconRichTextAlignLeft')
|
||||||
|
imgMap.set('center', 'TinyIconRichTextAlignCenter')
|
||||||
|
imgMap.set('right', 'TinyIconRichTextAlignRight')
|
||||||
|
export const eventImg = (item) => {
|
||||||
|
// 判断是否有图片
|
||||||
|
// 有: 直接返回
|
||||||
|
if (typeof item === 'string') {
|
||||||
|
return imgMap.get(item)
|
||||||
|
} else if (item.img) {
|
||||||
|
return item.img
|
||||||
|
} else {
|
||||||
|
return imgMap.get(item.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Active = (item) => {
|
||||||
|
let result = ''
|
||||||
|
if (item.name) {
|
||||||
|
switch (item.name) {
|
||||||
|
case 'unlink':
|
||||||
|
result = 'link'
|
||||||
|
break
|
||||||
|
case 'left':
|
||||||
|
result = { textAlign: 'left' }
|
||||||
|
break
|
||||||
|
case 'center':
|
||||||
|
result = { textAlign: 'center' }
|
||||||
|
break
|
||||||
|
case 'right':
|
||||||
|
result = { textAlign: 'right' }
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
result = item.name
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (item) {
|
||||||
|
case 'unlink':
|
||||||
|
result = 'link'
|
||||||
|
break
|
||||||
|
case 'left':
|
||||||
|
result = { textAlign: 'left' }
|
||||||
|
break
|
||||||
|
case 'center':
|
||||||
|
result = { textAlign: 'center' }
|
||||||
|
break
|
||||||
|
case 'right':
|
||||||
|
result = { textAlign: 'right' }
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
result = item
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
|
@ -1,13 +1,107 @@
|
||||||
import { handleChange, setLink, handleMove, handleClickOutside, removeClickOutside, handleClick, shouldShow, handleFontSize } from './index'
|
import {
|
||||||
|
handleChange,
|
||||||
|
setLink,
|
||||||
|
handleMove,
|
||||||
|
handleClickOutside,
|
||||||
|
removeClickOutside,
|
||||||
|
handleClick,
|
||||||
|
shouldShow,
|
||||||
|
handleFontSize,
|
||||||
|
eventImg,
|
||||||
|
eventClick,
|
||||||
|
Active
|
||||||
|
} from './index'
|
||||||
import Codehighlight from './code-highlight'
|
import Codehighlight from './code-highlight'
|
||||||
export const api = ['state', 'setLink', 'handleChange', 'box', 'handleMove', 'handleClickOutside', 'removeClickOutside', 'handleClick', 'shouldShow', 'handleFontSize', 'fontSize']
|
export const api = [
|
||||||
|
'toolBar',
|
||||||
|
'state',
|
||||||
|
'setLink',
|
||||||
|
'handleChange',
|
||||||
|
'box',
|
||||||
|
'handleMove',
|
||||||
|
'handleClickOutside',
|
||||||
|
'removeClickOutside',
|
||||||
|
'handleClick',
|
||||||
|
'shouldShow',
|
||||||
|
'handleFontSize',
|
||||||
|
'fontSize',
|
||||||
|
'eventImg',
|
||||||
|
'eventClick',
|
||||||
|
'Active'
|
||||||
|
]
|
||||||
export const renderless = (
|
export const renderless = (
|
||||||
props,
|
props,
|
||||||
{ computed, onMounted, onBeforeUnmount, reactive, ref },
|
{ computed, onMounted, onBeforeUnmount, reactive, ref },
|
||||||
{ vm, emit, parent },
|
{ vm, emit, parent },
|
||||||
{ useEditor, Collaboration, Y, WebrtcProvider, StarterKit, Table, TableCell, TableHeader, TableRow, Color, TextStyle, Image, Highlight, Link, Underline, Subscript, Superscript, TaskItem, TaskList, TextAlign, Paragraph, mergeAttributes, CodeBlockLowlight, lowlight, VueNodeViewRenderer, NodeViewContent, nodeViewProps, NodeViewWrapper }
|
{
|
||||||
|
useEditor,
|
||||||
|
Collaboration,
|
||||||
|
Y,
|
||||||
|
WebrtcProvider,
|
||||||
|
StarterKit,
|
||||||
|
Table,
|
||||||
|
TableCell,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
Color,
|
||||||
|
TextStyle,
|
||||||
|
Image,
|
||||||
|
Highlight,
|
||||||
|
Link,
|
||||||
|
Underline,
|
||||||
|
Subscript,
|
||||||
|
Superscript,
|
||||||
|
TaskItem,
|
||||||
|
TaskList,
|
||||||
|
TextAlign,
|
||||||
|
Paragraph,
|
||||||
|
mergeAttributes,
|
||||||
|
CodeBlockLowlight,
|
||||||
|
lowlight,
|
||||||
|
VueNodeViewRenderer,
|
||||||
|
NodeViewContent,
|
||||||
|
nodeViewProps,
|
||||||
|
NodeViewWrapper,
|
||||||
|
Placeholder
|
||||||
|
}
|
||||||
) => {
|
) => {
|
||||||
const ydoc = new Y.Doc()
|
let toolBar = [
|
||||||
|
'bold',
|
||||||
|
'italic',
|
||||||
|
'link',
|
||||||
|
'unlink',
|
||||||
|
'highlight',
|
||||||
|
'underline',
|
||||||
|
'strike',
|
||||||
|
'subscript',
|
||||||
|
'superscript',
|
||||||
|
'code',
|
||||||
|
'unorderedlist',
|
||||||
|
'orderedlist',
|
||||||
|
'taskList',
|
||||||
|
'quote',
|
||||||
|
'code-block',
|
||||||
|
'format-clear',
|
||||||
|
'node-delete',
|
||||||
|
'undo',
|
||||||
|
'redo',
|
||||||
|
'left',
|
||||||
|
'center',
|
||||||
|
'right',
|
||||||
|
'font-size',
|
||||||
|
'line-height',
|
||||||
|
'h-box',
|
||||||
|
'img',
|
||||||
|
'color',
|
||||||
|
'table'
|
||||||
|
]
|
||||||
|
if (props.customToolBar) {
|
||||||
|
toolBar = props.customToolBar
|
||||||
|
}
|
||||||
|
if (!window._yDoc) {
|
||||||
|
window._yDoc = new Y.Doc()
|
||||||
|
}
|
||||||
|
const ydoc = window._yDoc
|
||||||
const provider = new WebrtcProvider('tiny-examsple-document', ydoc)
|
const provider = new WebrtcProvider('tiny-examsple-document', ydoc)
|
||||||
// 自定义图片
|
// 自定义图片
|
||||||
const CustomImage = Image.extend({
|
const CustomImage = Image.extend({
|
||||||
|
@ -18,46 +112,48 @@ export const renderless = (
|
||||||
const CustomParagraph = Paragraph.extend({
|
const CustomParagraph = Paragraph.extend({
|
||||||
addOptions() {
|
addOptions() {
|
||||||
return {
|
return {
|
||||||
levels: [1, 1.5, 2, 2.5, 3],
|
levels: [1, 1.5, 2, 2.5, 3]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addAttributes() {
|
addAttributes() {
|
||||||
return {
|
return {
|
||||||
level: {
|
level: {
|
||||||
default: 1,
|
default: 1
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
renderHTML({ node, HTMLAttributes }) {
|
renderHTML({ node, HTMLAttributes }) {
|
||||||
const hasLevel = this.options.levels.includes(node.attrs.level)
|
const hasLevel = this.options.levels.includes(node.attrs.level)
|
||||||
const level = hasLevel
|
const level = hasLevel ? node.attrs.level : this.options.levels[0]
|
||||||
? node.attrs.level
|
|
||||||
: this.options.levels[0]
|
|
||||||
console.log('2', node, HTMLAttributes, this.options);
|
|
||||||
return ['p', mergeAttributes({ style: `line-height: ${level}` }, HTMLAttributes), 0]
|
return ['p', mergeAttributes({ style: `line-height: ${level}` }, HTMLAttributes), 0]
|
||||||
},
|
},
|
||||||
addCommands() {
|
addCommands() {
|
||||||
return {
|
return {
|
||||||
setP: attributes => ({ commands }) => {
|
setP:
|
||||||
return commands.setNode(this.name, attributes)
|
(attributes) =>
|
||||||
},
|
({ commands }) => {
|
||||||
|
return commands.setNode(this.name, attributes)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
const editor = useEditor({
|
const editor = useEditor({
|
||||||
extensions: [
|
extensions: [
|
||||||
StarterKit?.configure({
|
StarterKit?.configure({
|
||||||
// 开启多人协作功能要关闭默认的history模式
|
// 开启多人协作功能要关闭默认的history模式
|
||||||
history: false,
|
history: false
|
||||||
}),
|
}),
|
||||||
Collaboration?.configure({
|
Collaboration?.configure({
|
||||||
document: ydoc,
|
document: ydoc
|
||||||
}),
|
}),
|
||||||
Table.configure({
|
Table.configure({
|
||||||
resizable: true,
|
resizable: true
|
||||||
}),
|
}),
|
||||||
TableCell, TableHeader, TableRow,
|
TableCell,
|
||||||
Color, TextStyle,
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
Color,
|
||||||
|
TextStyle,
|
||||||
CustomImage,
|
CustomImage,
|
||||||
Highlight,
|
Highlight,
|
||||||
Link,
|
Link,
|
||||||
|
@ -66,24 +162,65 @@ export const renderless = (
|
||||||
Superscript,
|
Superscript,
|
||||||
TaskList,
|
TaskList,
|
||||||
TaskItem.configure({
|
TaskItem.configure({
|
||||||
nested: true,
|
nested: true
|
||||||
}),
|
}),
|
||||||
TextAlign.configure({
|
TextAlign.configure({
|
||||||
types: ['heading', 'paragraph'],
|
types: ['heading', 'paragraph']
|
||||||
}),
|
}),
|
||||||
CustomParagraph,
|
CustomParagraph,
|
||||||
CodeBlockLowlight.extend({
|
CodeBlockLowlight.extend({
|
||||||
addNodeView() {
|
addNodeView() {
|
||||||
return VueNodeViewRenderer(Codehighlight(NodeViewContent, nodeViewProps, NodeViewWrapper))
|
return VueNodeViewRenderer(Codehighlight(NodeViewContent, nodeViewProps, NodeViewWrapper))
|
||||||
},
|
}
|
||||||
|
}).configure({ lowlight }),
|
||||||
|
Placeholder.configure({
|
||||||
|
placeholder: props.placeholder ?? 'Write something …'
|
||||||
})
|
})
|
||||||
.configure({ lowlight }),
|
|
||||||
],
|
],
|
||||||
content: 'Example Tesxt',
|
content: '',
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
editable: true,
|
editable: true,
|
||||||
injectCSS: false,
|
injectCSS: false,
|
||||||
|
// 事件
|
||||||
|
onBeforeCreate({ editor }) {
|
||||||
|
emit('beforeCreate', { editor })
|
||||||
|
},
|
||||||
|
onCreate({ editor }) {
|
||||||
|
emit('create', { editor })
|
||||||
|
},
|
||||||
|
onUpdate({ editor }) {
|
||||||
|
const json = editor.getJSON()
|
||||||
|
const html = editor.getHTML()
|
||||||
|
const text = editor.getText()
|
||||||
|
// 可传入参数 blockSeparator 控制节点之间的连接
|
||||||
|
const lineText = editor.getText({ blockSeparator: '--' })
|
||||||
|
// console.log(json)
|
||||||
|
// console.log(html)
|
||||||
|
// console.log(text)
|
||||||
|
// console.log(lineText) // 文本一行内展示,可设置连接符,只能获得文本
|
||||||
|
emit('update', { editor })
|
||||||
|
emit('update:modelValue', html)
|
||||||
|
},
|
||||||
|
|
||||||
|
onFocus({ editor, event }) {
|
||||||
|
emit('focus', { editor, event })
|
||||||
|
},
|
||||||
|
onBlur({ editor, event }) {
|
||||||
|
emit('blur', { editor, event })
|
||||||
|
},
|
||||||
|
onSelectionUpdate({ editor }) {
|
||||||
|
// The selection has changed.
|
||||||
|
emit('selectionUpdate', { editor })
|
||||||
|
},
|
||||||
|
onTransaction({ editor, transaction }) {
|
||||||
|
// The editor state has changed.
|
||||||
|
emit('transaction', { editor, transaction })
|
||||||
|
},
|
||||||
|
onDestroy() {
|
||||||
|
// The editor is being destroyed.
|
||||||
|
emit('destroy')
|
||||||
|
},
|
||||||
|
...props.options
|
||||||
})
|
})
|
||||||
|
|
||||||
const box = ref(null)
|
const box = ref(null)
|
||||||
|
@ -93,10 +230,11 @@ export const renderless = (
|
||||||
// table 变量
|
// table 变量
|
||||||
isShow: false,
|
isShow: false,
|
||||||
flagX: 0,
|
flagX: 0,
|
||||||
flagY: 0,
|
flagY: 0
|
||||||
})
|
})
|
||||||
state.editor = editor
|
state.editor = editor
|
||||||
const api = {
|
const api = {
|
||||||
|
toolBar,
|
||||||
state,
|
state,
|
||||||
setLink: setLink(editor),
|
setLink: setLink(editor),
|
||||||
handleChange: handleChange(editor),
|
handleChange: handleChange(editor),
|
||||||
|
@ -111,6 +249,9 @@ export const renderless = (
|
||||||
//
|
//
|
||||||
fontSize,
|
fontSize,
|
||||||
handleFontSize: handleFontSize(fontSize),
|
handleFontSize: handleFontSize(fontSize),
|
||||||
|
eventImg,
|
||||||
|
eventClick,
|
||||||
|
Active
|
||||||
}
|
}
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
state.editor.destroy()
|
state.editor.destroy()
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
|
|
||||||
&:not(:disabled):hover,
|
&:not(:disabled):hover,
|
||||||
&.is-active {
|
&.is-active {
|
||||||
|
background-color: #d2e4ff;
|
||||||
|
|
||||||
svg,
|
svg,
|
||||||
path {
|
path {
|
||||||
|
@ -103,7 +104,7 @@
|
||||||
background: white;
|
background: white;
|
||||||
display: flex;
|
display: flex;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 1.00rem;
|
top: 1.25rem;
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
width: 90px;
|
width: 90px;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
@ -511,4 +512,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ProseMirror p.is-editor-empty:first-child::before {
|
||||||
|
content: attr(data-placeholder);
|
||||||
|
float: left;
|
||||||
|
color: #9a9a9a;
|
||||||
|
pointer-events: none;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -36,6 +36,7 @@
|
||||||
"@tiptap/extension-code-block-lowlight": "^2.0.4",
|
"@tiptap/extension-code-block-lowlight": "^2.0.4",
|
||||||
"highlight.js": "^11.8.0",
|
"highlight.js": "^11.8.0",
|
||||||
"lowlight": "^2.9.0",
|
"lowlight": "^2.9.0",
|
||||||
|
"@tiptap/extension-placeholder": "^2.1.7",
|
||||||
"@opentiny/vue-common": "workspace:~",
|
"@opentiny/vue-common": "workspace:~",
|
||||||
"@opentiny/vue-renderless": "workspace:~",
|
"@opentiny/vue-renderless": "workspace:~",
|
||||||
"@opentiny/vue-theme": "workspace:~"
|
"@opentiny/vue-theme": "workspace:~"
|
||||||
|
|
|
@ -2,170 +2,102 @@
|
||||||
<div class="tiny-rich-text-editor">
|
<div class="tiny-rich-text-editor">
|
||||||
<div class="tiny-rich-text-editor__toolbar">
|
<div class="tiny-rich-text-editor__toolbar">
|
||||||
<!-- starter-kit功能区 -->
|
<!-- starter-kit功能区 -->
|
||||||
<button title="bold" @click="state.editor.chain().focus().toggleBold().run()"
|
<template v-for="item in toolBar">
|
||||||
:class="{ 'is-active': state.editor?.isActive('bold') }">
|
<button v-if="(item.name ?? item) === 'font-size'" class="font-size-box">
|
||||||
<TinyIconRichTextBold></TinyIconRichTextBold>
|
<TinyIconRichTextFontSize></TinyIconRichTextFontSize>
|
||||||
</button>
|
<div class="font-size-options">
|
||||||
<button class="font-size-box">
|
<button @click="handleFontSize(12)">12px</button>
|
||||||
<TinyIconRichTextFontSize></TinyIconRichTextFontSize>
|
<button @click="handleFontSize(14)">14px</button>
|
||||||
<div class="font-size-options">
|
<button @click="handleFontSize(16)">16px</button>
|
||||||
<button @click="handleFontSize(12)">12px</button>
|
<button @click="handleFontSize(18)">18px</button>
|
||||||
<button @click="handleFontSize(14)">14px</button>
|
<button @click="handleFontSize(20)">20px</button>
|
||||||
<button @click="handleFontSize(16)">16px</button>
|
<button @click="handleFontSize(24)">24px</button>
|
||||||
<button @click="handleFontSize(18)">18px</button>
|
<button @click="handleFontSize(30)">30px</button>
|
||||||
<button @click="handleFontSize(20)">20px</button>
|
|
||||||
<button @click="handleFontSize(24)">24px</button>
|
|
||||||
<button @click="handleFontSize(30)">30px</button>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
<button title="link" @click="setLink" :class="{ 'is-active': state.editor?.isActive('link') }">
|
|
||||||
<TinyIconRichTextLink></TinyIconRichTextLink>
|
|
||||||
</button>
|
|
||||||
<button title="unlink" @click="state.editor.chain().focus().unsetLink().run()"
|
|
||||||
:disabled="!state.editor?.isActive('link')">
|
|
||||||
<TinyIconRichTextLinkUnlink></TinyIconRichTextLinkUnlink>
|
|
||||||
</button>
|
|
||||||
<button title="high light" @click="state.editor.chain().focus().toggleHighlight().run()"
|
|
||||||
:class="{ 'is-active': state.editor?.isActive('highlight') }">
|
|
||||||
<TinyIconRichTextHighLight></TinyIconRichTextHighLight>
|
|
||||||
</button>
|
|
||||||
<button class="line-height-button" title="line height" @click="state.editor.chain().focus().toggleHeight().run()">
|
|
||||||
<div class="line-height-icon">
|
|
||||||
<TinyIconRichTextLineHeight></TinyIconRichTextLineHeight>
|
|
||||||
</div>
|
|
||||||
<div class="line-height-options">
|
|
||||||
<button class="line-1.0" @click.stop="state.editor.chain().focus().setP({ level: 1 }).run()">1.0</button>
|
|
||||||
<button class="line-1.5" @click.stop="state.editor.chain().focus().setP({ level: 1.5 }).run()">1.5</button>
|
|
||||||
<button class="line-2.0" @click.stop="state.editor.chain().focus().setP({ level: 2 }).run()">2.0</button>
|
|
||||||
<button class="line-2.5" @click.stop="state.editor.chain().focus().setP({ level: 2.5 }).run()">2.5</button>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
<button @click="state.editor.chain().focus().toggleUnderline().run()"
|
|
||||||
:class="{ 'is-active': state.editor?.isActive('underline') }">
|
|
||||||
<TinyIconRichTextUnderline></TinyIconRichTextUnderline>
|
|
||||||
</button>
|
|
||||||
<button title="strike through" @click="state.editor.chain().focus().toggleStrike().run()"
|
|
||||||
:class="{ 'is-active': state.editor?.isActive('strike') }">
|
|
||||||
<TinyIconRichTextStrikeThrough></TinyIconRichTextStrikeThrough>
|
|
||||||
</button>
|
|
||||||
<button title="italic" @click="state.editor.chain().focus().toggleItalic().run()">
|
|
||||||
<TinyIconRichTextItalic></TinyIconRichTextItalic>
|
|
||||||
</button>
|
|
||||||
<button class="h-box">
|
|
||||||
<div class="h-ico">
|
|
||||||
<TinyIconRichTextHeading></TinyIconRichTextHeading>
|
|
||||||
</div>
|
|
||||||
<div class="h-options">
|
|
||||||
<button title="paragraph" @click="state.editor.chain().focus().setParagraph().run()">
|
|
||||||
<TinyIconRichTextParagraph></TinyIconRichTextParagraph>
|
|
||||||
</button>
|
|
||||||
<button title="h1" @click="state.editor.chain().focus().toggleHeading({ level: 1 }).run()">
|
|
||||||
<TinyIconRichTextH1></TinyIconRichTextH1>
|
|
||||||
</button>
|
|
||||||
<button title="h2" @click="state.editor.chain().focus().toggleHeading({ level: 2 }).run()">
|
|
||||||
<TinyIconRichTextH2></TinyIconRichTextH2>
|
|
||||||
</button>
|
|
||||||
<button title="h3" @click="state.editor.chain().focus().toggleHeading({ level: 3 }).run()">
|
|
||||||
<TinyIconRichTextH3></TinyIconRichTextH3>
|
|
||||||
</button>
|
|
||||||
<button title="h4" @click="state.editor.chain().focus().toggleHeading({ level: 4 }).run()">
|
|
||||||
<TinyIconRichTextH4></TinyIconRichTextH4>
|
|
||||||
</button>
|
|
||||||
<button title="h5" @click="state.editor.chain().focus().toggleHeading({ level: 5 }).run()">
|
|
||||||
<TinyIconRichTextH5></TinyIconRichTextH5>
|
|
||||||
</button>
|
|
||||||
<button title="h6" @click="state.editor.chain().focus().toggleHeading({ level: 6 }).run()">
|
|
||||||
<TinyIconRichTextH6></TinyIconRichTextH6>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
<button title="subscript" @click="state.editor.chain().focus().toggleSubscript().run()"
|
|
||||||
:class="{ 'is-active': state.editor?.isActive('subscript') }">
|
|
||||||
<TinyIconRichTextSubscript></TinyIconRichTextSubscript>
|
|
||||||
</button>
|
|
||||||
<button title="superscript" @click="state.editor.chain().focus().toggleSuperscript().run()"
|
|
||||||
:class="{ 'is-active': state.editor?.isActive('superscript') }">
|
|
||||||
<TinyIconRichTextSuperscript></TinyIconRichTextSuperscript>
|
|
||||||
</button>
|
|
||||||
<!-- 无序列表 -->
|
|
||||||
<button title="code" @click="state.editor.chain().focus().toggleCode().run()">
|
|
||||||
<TinyIconRichTextCodeView></TinyIconRichTextCodeView>
|
|
||||||
</button>
|
|
||||||
<button title="unordered list" @click.stop="state.editor.chain().focus().toggleBulletList().run()">
|
|
||||||
<TinyIconRichTextListUnordered></TinyIconRichTextListUnordered>
|
|
||||||
</button>
|
|
||||||
<button title="ordered list" @click="state.editor.chain().focus().toggleOrderedList().run()">
|
|
||||||
<TinyIconRichTextListOrdered></TinyIconRichTextListOrdered>
|
|
||||||
</button>
|
|
||||||
<button @click="state.editor.chain().focus().toggleTaskList().run()"
|
|
||||||
:class="{ 'is-active': state.editor?.isActive('taskList') }">
|
|
||||||
<TinyIconRichTextTaskList></TinyIconRichTextTaskList>
|
|
||||||
</button>
|
|
||||||
<button title="quote" @click="state.editor.chain().focus().toggleBlockquote().run()">
|
|
||||||
<TinyIconRichTextQuoteText></TinyIconRichTextQuoteText>
|
|
||||||
</button>
|
|
||||||
<button title="code block" @click="state.editor.chain().focus().toggleCodeBlock().run()">
|
|
||||||
<TinyIconRichTextCodeBlock></TinyIconRichTextCodeBlock>
|
|
||||||
</button>
|
|
||||||
<button title="format clear" @click="state.editor.chain().focus().unsetAllMarks().run()">
|
|
||||||
<TinyIconRichTextFormatClear></TinyIconRichTextFormatClear>
|
|
||||||
</button>
|
|
||||||
<button title="node delete" @click="state.editor.chain().focus().clearNodes().run()">
|
|
||||||
<TinyIconRichTextNodeDelete></TinyIconRichTextNodeDelete>
|
|
||||||
</button>
|
|
||||||
<button title="undo" @click="state.editor.chain().focus().undo().run()">
|
|
||||||
<TinyIconRichTextUndo></TinyIconRichTextUndo>
|
|
||||||
</button>
|
|
||||||
<button title="redo" @click="state.editor.chain().focus().redo().run()">
|
|
||||||
<TinyIconRichTextRedo></TinyIconRichTextRedo>
|
|
||||||
</button>
|
|
||||||
<!-- 图片 -->
|
|
||||||
<button title="img" class="image-button">
|
|
||||||
<input @change="handleChange" id="img-btn" :placeholder="'啊飒飒'" type="file" accept="image/*" />
|
|
||||||
<label for="img-btn">
|
|
||||||
<TinyIconRichTextImage></TinyIconRichTextImage>
|
|
||||||
</label>
|
|
||||||
</button>
|
|
||||||
<!-- 文本对齐 -->
|
|
||||||
<button @click="state.editor.chain().focus().setTextAlign('left').run()"
|
|
||||||
:class="{ 'is-active': state.editor?.isActive({ textAlign: 'left' }) }">
|
|
||||||
<TinyIconRichTextAlignLeft></TinyIconRichTextAlignLeft>
|
|
||||||
</button>
|
|
||||||
<button @click="state.editor.chain().focus().setTextAlign('center').run()"
|
|
||||||
:class="{ 'is-active': state.editor?.isActive({ textAlign: 'center' }) }">
|
|
||||||
<TinyIconRichTextAlignCenter></TinyIconRichTextAlignCenter>
|
|
||||||
</button>
|
|
||||||
<button @click="state.editor.chain().focus().setTextAlign('right').run()"
|
|
||||||
:class="{ 'is-active': state.editor?.isActive({ textAlign: 'right' }) }">
|
|
||||||
<TinyIconRichTextAlignRight></TinyIconRichTextAlignRight>
|
|
||||||
</button>
|
|
||||||
<!-- 颜色 -->
|
|
||||||
<button title="color" class="color-button">
|
|
||||||
<label for="tiny-color">
|
|
||||||
<TinyIconRichTextColor></TinyIconRichTextColor>
|
|
||||||
</label>
|
|
||||||
<input id="tiny-color" type="color" @input="state.editor.chain().focus().setColor($event.target.value).run()"
|
|
||||||
:value="state.editor?.getAttributes('textStyle').color" />
|
|
||||||
</button>
|
|
||||||
<!-- 表格功能按钮 -->
|
|
||||||
<button title="table" class="table-button">
|
|
||||||
<div class="table-box" @click="handleClick">
|
|
||||||
<div class="table-icon">
|
|
||||||
<TinyIconRichTextTable></TinyIconRichTextTable>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="table-option" ref="box" v-if="state.isShow" @mousemove="handleMove">
|
</button>
|
||||||
<div class="item" :class="{ isActive: 1 <= state.flagX && 1 <= state.flagY }"></div>
|
<button v-else-if="(item.name ?? item) === 'line-height'" class="line-height-button" title="line height">
|
||||||
<div class="item" :class="{ isActive: 2 <= state.flagX && 1 <= state.flagY }"></div>
|
<div class="line-height-icon">
|
||||||
<div class="item" :class="{ isActive: 3 <= state.flagX && 1 <= state.flagY }"></div>
|
<TinyIconRichTextLineHeight></TinyIconRichTextLineHeight>
|
||||||
<div class="item" :class="{ isActive: 1 <= state.flagX && 2 <= state.flagY }"></div>
|
|
||||||
<div class="item" :class="{ isActive: 2 <= state.flagX && 2 <= state.flagY }"></div>
|
|
||||||
<div class="item" :class="{ isActive: 3 <= state.flagX && 2 <= state.flagY }"></div>
|
|
||||||
<div class="item" :class="{ isActive: 1 <= state.flagX && 3 <= state.flagY }"></div>
|
|
||||||
<div class="item" :class="{ isActive: 2 <= state.flagX && 3 <= state.flagY }"></div>
|
|
||||||
<div class="item" :class="{ isActive: 3 <= state.flagX && 3 <= state.flagY }"></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="line-height-options">
|
||||||
</button>
|
<button class="line-1.0" @click.stop="state.editor.chain().focus().setP({ level: 1 }).run()">1.0</button>
|
||||||
|
<button class="line-1.5" @click.stop="state.editor.chain().focus().setP({ level: 1.5 }).run()">1.5</button>
|
||||||
|
<button class="line-2.0" @click.stop="state.editor.chain().focus().setP({ level: 2 }).run()">2.0</button>
|
||||||
|
<button class="line-2.5" @click.stop="state.editor.chain().focus().setP({ level: 2.5 }).run()">2.5</button>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<button v-else-if="(item.name ?? item) === 'h-box'" class="h-box">
|
||||||
|
<div class="h-ico">
|
||||||
|
<TinyIconRichTextHeading></TinyIconRichTextHeading>
|
||||||
|
</div>
|
||||||
|
<div class="h-options">
|
||||||
|
<button title="paragraph" @click="state.editor.chain().focus().setParagraph().run()">
|
||||||
|
<TinyIconRichTextParagraph></TinyIconRichTextParagraph>
|
||||||
|
</button>
|
||||||
|
<button title="h1" @click="state.editor.chain().focus().toggleHeading({ level: 1 }).run()">
|
||||||
|
<TinyIconRichTextH1></TinyIconRichTextH1>
|
||||||
|
</button>
|
||||||
|
<button title="h2" @click="state.editor.chain().focus().toggleHeading({ level: 2 }).run()">
|
||||||
|
<TinyIconRichTextH2></TinyIconRichTextH2>
|
||||||
|
</button>
|
||||||
|
<button title="h3" @click="state.editor.chain().focus().toggleHeading({ level: 3 }).run()">
|
||||||
|
<TinyIconRichTextH3></TinyIconRichTextH3>
|
||||||
|
</button>
|
||||||
|
<button title="h4" @click="state.editor.chain().focus().toggleHeading({ level: 4 }).run()">
|
||||||
|
<TinyIconRichTextH4></TinyIconRichTextH4>
|
||||||
|
</button>
|
||||||
|
<button title="h5" @click="state.editor.chain().focus().toggleHeading({ level: 5 }).run()">
|
||||||
|
<TinyIconRichTextH5></TinyIconRichTextH5>
|
||||||
|
</button>
|
||||||
|
<button title="h6" @click="state.editor.chain().focus().toggleHeading({ level: 6 }).run()">
|
||||||
|
<TinyIconRichTextH6></TinyIconRichTextH6>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<button v-else-if="(item.name ?? item) === 'img'" title="img" class="image-button">
|
||||||
|
<input @change="handleChange" id="img-btn" type="file" accept="image/*" />
|
||||||
|
<label for="img-btn">
|
||||||
|
<TinyIconRichTextImage></TinyIconRichTextImage>
|
||||||
|
</label>
|
||||||
|
</button>
|
||||||
|
<button v-else-if="(item.name ?? item) === 'color'" title="color" class="color-button">
|
||||||
|
<label for="tiny-color">
|
||||||
|
<TinyIconRichTextColor></TinyIconRichTextColor>
|
||||||
|
</label>
|
||||||
|
<input id="tiny-color" type="color" @input="state.editor.chain().focus().setColor($event.target.value).run()"
|
||||||
|
:value="state.editor?.getAttributes('textStyle').color" />
|
||||||
|
</button>
|
||||||
|
<button v-else-if="(item.name ?? item) === 'table'" title="table" class="table-button">
|
||||||
|
<div class="table-box" @click="handleClick">
|
||||||
|
<div class="table-icon">
|
||||||
|
<TinyIconRichTextTable></TinyIconRichTextTable>
|
||||||
|
</div>
|
||||||
|
<div class="table-option" ref="box" v-if="state.isShow" @mousemove="handleMove">
|
||||||
|
<div class="item" :class="{ isActive: 1 <= state.flagX && 1 <= state.flagY }"></div>
|
||||||
|
<div class="item" :class="{ isActive: 2 <= state.flagX && 1 <= state.flagY }"></div>
|
||||||
|
<div class="item" :class="{ isActive: 3 <= state.flagX && 1 <= state.flagY }"></div>
|
||||||
|
<div class="item" :class="{ isActive: 1 <= state.flagX && 2 <= state.flagY }"></div>
|
||||||
|
<div class="item" :class="{ isActive: 2 <= state.flagX && 2 <= state.flagY }"></div>
|
||||||
|
<div class="item" :class="{ isActive: 3 <= state.flagX && 2 <= state.flagY }"></div>
|
||||||
|
<div class="item" :class="{ isActive: 1 <= state.flagX && 3 <= state.flagY }"></div>
|
||||||
|
<div class="item" :class="{ isActive: 2 <= state.flagX && 3 <= state.flagY }"></div>
|
||||||
|
<div class="item" :class="{ isActive: 3 <= state.flagX && 3 <= state.flagY }"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<button v-else-if="(item.name ?? item) === 'unlink'" :title="item.name" @click="eventClick(state.editor, item)"
|
||||||
|
:disabled="!state.editor?.isActive(Active(item))">
|
||||||
|
<img v-if="item.img" :src="eventImg(item)" alt="" srcset="" />
|
||||||
|
<component v-else :is='eventImg(item)'></component>
|
||||||
|
</button>
|
||||||
|
<button v-else :title="item.name" @click="eventClick(state.editor, item)"
|
||||||
|
:class="{ 'is-active': state.editor?.isActive(Active(item)) }">
|
||||||
|
<img v-if="item.img" :src="eventImg(item)" alt="" srcset="" />
|
||||||
|
<component v-else :is='eventImg(item)'></component>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
<!-- 插槽传出editor实例 -->
|
||||||
|
<slot name="toolBar" :option="state.editor"></slot>
|
||||||
<BubbleMenu :editor="state.editor" :tippy-options="{ duration: 100 }" v-if="state.editor" :should-show="shouldShow"
|
<BubbleMenu :editor="state.editor" :tippy-options="{ duration: 100 }" v-if="state.editor" :should-show="shouldShow"
|
||||||
class="bubble-menu">
|
class="bubble-menu">
|
||||||
<button title="add column before" @click="state.editor.chain().focus().addColumnBefore().run()"
|
<button title="add column before" @click="state.editor.chain().focus().addColumnBefore().run()"
|
||||||
|
@ -310,6 +242,8 @@ lowlight.registerLanguage('html', html)
|
||||||
lowlight.registerLanguage('css', css)
|
lowlight.registerLanguage('css', css)
|
||||||
lowlight.registerLanguage('js', js)
|
lowlight.registerLanguage('js', js)
|
||||||
lowlight.registerLanguage('ts', ts)
|
lowlight.registerLanguage('ts', ts)
|
||||||
|
// Placeholder
|
||||||
|
import Placeholder from '@tiptap/extension-placeholder'
|
||||||
// collaboration 包
|
// collaboration 包
|
||||||
import Collaboration from '@tiptap/extension-collaboration'
|
import Collaboration from '@tiptap/extension-collaboration'
|
||||||
import * as Y from 'yjs'
|
import * as Y from 'yjs'
|
||||||
|
@ -319,8 +253,8 @@ import { props, setup, defineComponent } from '@opentiny/vue-common'
|
||||||
import '@opentiny/vue-theme/rich-text-editor/index.less'
|
import '@opentiny/vue-theme/rich-text-editor/index.less'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
// emits: ['click', 'hook-updated'],
|
emits: ['beforeCreate', 'create', 'update:modelValue', 'focus', 'blur', 'selectionUpdate', 'transaction', 'destroy', 'update'],
|
||||||
// props: [...props, 'type', 'text', 'size', 'icon', 'resetTime', 'nativeType', 'loading', 'disabled', 'plain', 'autofocus', 'round', 'circle', 'tabindex'],
|
props: [...props, 'modelValue', 'collaboration', 'placeholder', 'customToolBar', 'options'],
|
||||||
components: {
|
components: {
|
||||||
EditorContent,
|
EditorContent,
|
||||||
BubbleMenu,
|
BubbleMenu,
|
||||||
|
@ -405,7 +339,8 @@ export default defineComponent({
|
||||||
VueNodeViewRenderer,
|
VueNodeViewRenderer,
|
||||||
NodeViewContent,
|
NodeViewContent,
|
||||||
nodeViewProps,
|
nodeViewProps,
|
||||||
NodeViewWrapper
|
NodeViewWrapper,
|
||||||
|
Placeholder
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue