forked from opentiny/tiny-vue
perf(rich-text-editor): Optimized component code to add v-model echo (#947)
This commit is contained in:
parent
4679b6ba6a
commit
84704ea59b
|
@ -1,7 +1,23 @@
|
|||
<template>
|
||||
<tiny-rich-text-editor></tiny-rich-text-editor>
|
||||
<div>
|
||||
<tiny-rich-text-editor v-model="value"></tiny-rich-text-editor>
|
||||
<div class="result">
|
||||
<hr />
|
||||
<pre>{{ value }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { RichTextEditor as TinyRichTextEditor } from '@opentiny/vue'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const value = ref('你好 Opentiny!')
|
||||
</script>
|
||||
<style scoped>
|
||||
.result {
|
||||
margin-top: 16px;
|
||||
font-size: 14px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
<template>
|
||||
<tiny-rich-text-editor></tiny-rich-text-editor>
|
||||
<div>
|
||||
<tiny-rich-text-editor v-model="value"></tiny-rich-text-editor>
|
||||
<div class="result">
|
||||
<hr />
|
||||
<pre>{{ value }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
@ -8,6 +14,18 @@ import { RichTextEditor } from '@opentiny/vue'
|
|||
export default {
|
||||
components: {
|
||||
TinyRichTextEditor: RichTextEditor
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
value: '你好 Opentiny!'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.result {
|
||||
margin-top: 16px;
|
||||
font-size: 14px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
export default {
|
||||
column: '1',
|
||||
owner: 'Caesar-ch',
|
||||
owner: '',
|
||||
demos: [
|
||||
{
|
||||
'demoId': 'basic-usage',
|
||||
|
@ -8,12 +8,6 @@ export default {
|
|||
'desc': { 'zh-CN': '详细用法参考如下示例', 'en-US': 'For details, see the following example.' },
|
||||
'codeFiles': ['basic-usage.vue']
|
||||
},
|
||||
// {
|
||||
// 'demoId': 'collaboration-usage',
|
||||
// 'name': { 'zh-CN': '协同编辑用法', 'en-US': 'Basic Usage' },
|
||||
// 'desc': { 'zh-CN': '详细用法参考如下示例', 'en-US': 'For details, see the following example.' },
|
||||
// 'codeFiles': ['collaboration-usage.vue']
|
||||
// },
|
||||
{
|
||||
'demoId': 'custom-bar-usage',
|
||||
'name': { 'zh-CN': '自定义工具栏用法', 'en-US': 'Basic Usage' },
|
||||
|
@ -37,7 +31,7 @@ export default {
|
|||
'name': { 'zh-CN': 'placeholder选项用法', 'en-US': 'Basic Usage' },
|
||||
'desc': { 'zh-CN': '详细用法参考如下示例', 'en-US': 'For details, see the following example.' },
|
||||
'codeFiles': ['placeholder-usage.vue']
|
||||
},
|
||||
}
|
||||
],
|
||||
apis: [
|
||||
{
|
||||
|
@ -54,22 +48,13 @@ export default {
|
|||
},
|
||||
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': '传入需要展示的工具栏按钮配置,自定义使用',
|
||||
'zh-CN':
|
||||
"传入需要展示的工具栏按钮配置,设置时,显示全量的工具栏。可配置的项目有:'bold','italic', 'underline', 'strike', 'quote', 'code', 'codeBlock', 'unorderedlist', 'orderedlist', 'taskList', 'subscript', 'superscript', 'undo', 'redo', 'left', 'center', 'right', 'h-box', 'font-size', 'line-height', 'highlight', 'color', 'backgroundColor', 'formatClear', 'link', 'unlink', 'img', 'table'",
|
||||
'en-US': 'Pass in the toolbar button configuration that needs to be displayed, and customize the use'
|
||||
},
|
||||
demoId: 'basic-usage'
|
||||
|
@ -77,7 +62,7 @@ export default {
|
|||
{
|
||||
'name': 'placeholder',
|
||||
'type': 'Stirng',
|
||||
'defaultValue': 'Write soming ...',
|
||||
'defaultValue': '',
|
||||
desc: {
|
||||
'zh-CN': '占位符,在v-model为空时展示',
|
||||
'en-US': 'Placeholder, displayed when v-model is empty'
|
||||
|
|
|
@ -56,43 +56,26 @@ export const setLink = (editor) => {
|
|||
editor.chain().focus().extendMarkRange('link').setLink({ href: url }).run()
|
||||
}
|
||||
}
|
||||
// table 处理逻辑
|
||||
export const handleMove = (state, box) => {
|
||||
return (e) => {
|
||||
let { x, y } = box.value[0].getBoundingClientRect()
|
||||
// 表格块移动
|
||||
export const tableMouseMove = (state, vm) => (e) => {
|
||||
let { x, y } = vm.$refs.tablePanelRef[0].getBoundingClientRect()
|
||||
state.flagX = Math.ceil((e.x - x) / 30) // 后期改变30就可以
|
||||
state.flagY = Math.ceil((e.y - y) / 30)
|
||||
}
|
||||
}
|
||||
export const handleClickOutside = (state, box) => {
|
||||
return (e) => {
|
||||
if (!box.value[0]?.contains(e.target)) {
|
||||
state.isShow = false
|
||||
removeClickOutside(state, box)()
|
||||
}
|
||||
}
|
||||
}
|
||||
export const removeClickOutside = (state, box) => {
|
||||
return () => {
|
||||
window.removeEventListener('click', handleClickOutside(state, box))
|
||||
}
|
||||
}
|
||||
export const handleClick = (state, box) => {
|
||||
return (e) => {
|
||||
e.stopPropagation()
|
||||
if (state.isShow) {
|
||||
// 点击表格块
|
||||
export const tableChoose = (state, vm) => (e) => {
|
||||
if (state.flagX && state.flagY) {
|
||||
state.editor.chain().focus().insertTable({ rows: state.flagY, cols: state.flagX, withHeaderRow: true }).run()
|
||||
}
|
||||
state.flagX = 0
|
||||
state.flagY = 0
|
||||
removeClickOutside(state, box)()
|
||||
} else {
|
||||
window.addEventListener('click', handleClickOutside(state, box))
|
||||
}
|
||||
state.isShow = !state.isShow
|
||||
}
|
||||
state.isShowTable = false
|
||||
}
|
||||
export const toggleTablePanel = (state) => () => {
|
||||
state.isShowTable = !state.isShowTable
|
||||
}
|
||||
export const closeTablePanel = (state) => () => {
|
||||
state.isShowTable && (state.isShowTable = false)
|
||||
}
|
||||
|
||||
// bubble菜单
|
||||
export const shouldShow = ({ editor, view, state, oldState, from, to }) => {
|
||||
// 仅在无序列表选中的时候才显示 气泡菜单
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import {
|
||||
handleChange,
|
||||
setLink,
|
||||
handleMove,
|
||||
handleClickOutside,
|
||||
removeClickOutside,
|
||||
handleClick,
|
||||
tableMouseMove,
|
||||
tableChoose,
|
||||
toggleTablePanel,
|
||||
closeTablePanel,
|
||||
shouldShow,
|
||||
eventImg,
|
||||
eventClick,
|
||||
|
@ -12,24 +12,21 @@ import {
|
|||
} from './index'
|
||||
|
||||
export const api = [
|
||||
'toolBar',
|
||||
'state',
|
||||
'setLink',
|
||||
'handleChange',
|
||||
'box',
|
||||
'handleMove',
|
||||
'handleClickOutside',
|
||||
'removeClickOutside',
|
||||
'handleClick',
|
||||
'tableMouseMove',
|
||||
'tableChoose',
|
||||
'toggleTablePanel',
|
||||
'closeTablePanel',
|
||||
'shouldShow',
|
||||
'fontSize',
|
||||
'eventImg',
|
||||
'eventClick',
|
||||
'Active'
|
||||
]
|
||||
export const renderless = (
|
||||
props,
|
||||
{ computed, onMounted, onBeforeUnmount, reactive, ref },
|
||||
{ computed, onMounted, onBeforeUnmount, reactive, ref, markRaw },
|
||||
{ vm, emit, parent },
|
||||
{
|
||||
Editor,
|
||||
|
@ -54,15 +51,11 @@ export const renderless = (
|
|||
CodeBlockLowlight,
|
||||
lowlight,
|
||||
VueNodeViewRenderer,
|
||||
// CodehighComp,
|
||||
// NodeViewContent,
|
||||
// nodeViewProps,
|
||||
// NodeViewWrapper,
|
||||
Placeholder,
|
||||
codeHighlight
|
||||
}
|
||||
) => {
|
||||
let toolBar = [
|
||||
let defaultToolBar = [
|
||||
'bold',
|
||||
'italic',
|
||||
'underline',
|
||||
|
@ -75,27 +68,24 @@ export const renderless = (
|
|||
'taskList',
|
||||
'subscript',
|
||||
'superscript',
|
||||
// 'nodeDelete',
|
||||
'undo',
|
||||
'redo',
|
||||
'left',
|
||||
'center',
|
||||
'right',
|
||||
'h-box',
|
||||
'font-size',
|
||||
'line-height',
|
||||
'highlight',
|
||||
'color',
|
||||
'backgroundColor',
|
||||
'formatClear',
|
||||
'link',
|
||||
'unlink',
|
||||
'img',
|
||||
'table'
|
||||
'h-box', //
|
||||
'font-size', //
|
||||
'line-height', //
|
||||
'highlight',
|
||||
'color', //
|
||||
'backgroundColor', //
|
||||
'unlink', //
|
||||
'img', //
|
||||
'table' //
|
||||
]
|
||||
if (props.customToolBar) {
|
||||
toolBar = props.customToolBar
|
||||
}
|
||||
|
||||
// 自定义图片
|
||||
const CustomImage = Image.extend({
|
||||
addAttributes() {
|
||||
|
@ -232,10 +222,10 @@ export const renderless = (
|
|||
}
|
||||
}).configure({ lowlight }),
|
||||
Placeholder.configure({
|
||||
placeholder: props.placeholder ?? 'Write something …'
|
||||
placeholder: props.placeholder
|
||||
})
|
||||
],
|
||||
content: '',
|
||||
content: props.modelValue,
|
||||
autofocus: true,
|
||||
editable: true,
|
||||
injectCSS: false,
|
||||
|
@ -278,34 +268,27 @@ export const renderless = (
|
|||
...props.options
|
||||
}
|
||||
|
||||
let options = props.options ? Object.assign(defaultOptions, props.options) : defaultOptions
|
||||
const editor = new Editor(options)
|
||||
let options = Object.assign(defaultOptions, props.options)
|
||||
|
||||
const box = ref(null)
|
||||
const fontSize = ref('16px')
|
||||
const state = reactive({
|
||||
editor: null,
|
||||
editor: markRaw(new Editor(options)),
|
||||
toolbar: computed(() => (props.customToolBar.length ? props.customToolBar : defaultToolBar)),
|
||||
// table 变量
|
||||
isShow: false,
|
||||
isShowTable: false,
|
||||
flagX: 0,
|
||||
flagY: 0
|
||||
})
|
||||
state.editor = editor
|
||||
const api = {
|
||||
toolBar,
|
||||
state,
|
||||
setLink: setLink(editor),
|
||||
handleChange: handleChange(editor),
|
||||
setLink: setLink(state.editor),
|
||||
handleChange: handleChange(state.editor),
|
||||
// table处理函数
|
||||
box,
|
||||
handleMove: handleMove(state, box),
|
||||
handleClickOutside: handleClickOutside(state, box),
|
||||
removeClickOutside: removeClickOutside(state, box),
|
||||
handleClick: handleClick(state, box),
|
||||
tableMouseMove: tableMouseMove(state, vm),
|
||||
toggleTablePanel: toggleTablePanel(state),
|
||||
closeTablePanel: closeTablePanel(state),
|
||||
tableChoose: tableChoose(state, vm),
|
||||
// bubble 菜单
|
||||
shouldShow,
|
||||
//
|
||||
fontSize,
|
||||
shouldShow: shouldShow,
|
||||
eventImg,
|
||||
eventClick,
|
||||
Active
|
||||
|
@ -313,5 +296,6 @@ export const renderless = (
|
|||
onBeforeUnmount(() => {
|
||||
state.editor.destroy()
|
||||
})
|
||||
|
||||
return api
|
||||
}
|
||||
|
|
|
@ -11,7 +11,8 @@
|
|||
font-style: italic;
|
||||
}
|
||||
|
||||
a, a:hover {
|
||||
a,
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
|
@ -63,14 +64,15 @@
|
|||
|
||||
&__toolbar button {
|
||||
height: 24px;
|
||||
padding: .25rem;
|
||||
margin-right: .25rem;
|
||||
padding: 0.25rem;
|
||||
margin-right: 0.25rem;
|
||||
border: none;
|
||||
border-radius: .4rem;
|
||||
border-radius: 0.4rem;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
|
||||
svg, input {
|
||||
svg,
|
||||
input {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
@ -128,8 +130,8 @@
|
|||
|
||||
.h-options {
|
||||
position: absolute;
|
||||
padding: .15rem;
|
||||
background-color: var(--ti-rich-text-editor-options-bg-color);
|
||||
padding: 0.15rem;
|
||||
background-color: var(--ti-rich-text-edito-options-bg-color);
|
||||
left: 0px;
|
||||
display: none;
|
||||
border-radius: var(--ti-rich-text-editor-options-border-radius);
|
||||
|
@ -141,20 +143,21 @@
|
|||
margin: 0;
|
||||
text-align: center;
|
||||
line-height: var(--ti-common-line-height-3);
|
||||
svg, path {
|
||||
svg,
|
||||
path {
|
||||
fill: black;
|
||||
}
|
||||
&:hover {
|
||||
background-color: var(--ti-rich-text-editor-options-item-bg-color);
|
||||
svg, path {
|
||||
fill: var(--ti-rich-text-editor-options-item-hover-color);
|
||||
background-color: var(--ti-rich-text-edito-options-item-bg-color);
|
||||
svg,
|
||||
path {
|
||||
fill: var(--ti-rich-text-edito-options-item-hover-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
|
||||
.h-options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -170,7 +173,6 @@
|
|||
// 表格区域样式
|
||||
.tiny-rich-text-editor {
|
||||
.table-button {
|
||||
|
||||
.table-box {
|
||||
display: flex;
|
||||
position: relative;
|
||||
|
@ -179,7 +181,7 @@
|
|||
display: inline-block;
|
||||
}
|
||||
|
||||
.table-option {
|
||||
.table-panel {
|
||||
position: absolute;
|
||||
background: white;
|
||||
left: 0;
|
||||
|
@ -201,8 +203,8 @@
|
|||
}
|
||||
|
||||
.isActive {
|
||||
background-color: rgba(32, 122, 183, .5);
|
||||
border-color: rgba(32, 122, 183, .5);
|
||||
background-color: rgba(32, 122, 183, 0.5);
|
||||
border-color: rgba(32, 122, 183, 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -215,7 +217,6 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 颜色选择样式
|
||||
|
@ -235,7 +236,6 @@
|
|||
}
|
||||
|
||||
&:hover {
|
||||
|
||||
#tiny-back-color,
|
||||
#tiny-color {
|
||||
background-color: #d2e4ff;
|
||||
|
@ -291,8 +291,8 @@
|
|||
|
||||
.line-height-options {
|
||||
position: absolute;
|
||||
padding: .15rem;
|
||||
background-color: var(--ti-rich-text-editor-options-bg-color);
|
||||
padding: 0.15rem;
|
||||
background-color: var(--ti-rich-text-edito-options-bg-color);
|
||||
left: 0px;
|
||||
display: none;
|
||||
border-radius: var(--ti-rich-text-editor-options-border-radius);
|
||||
|
@ -326,7 +326,6 @@
|
|||
|
||||
.tiny-rich-text-editor {
|
||||
.bubble-menu {
|
||||
|
||||
button {
|
||||
background-color: #f1f3f5;
|
||||
}
|
||||
|
@ -393,7 +392,7 @@
|
|||
}
|
||||
|
||||
.ProseMirror {
|
||||
>*+* {
|
||||
> * + * {
|
||||
margin-top: 0.75em;
|
||||
}
|
||||
|
||||
|
@ -446,8 +445,8 @@
|
|||
}
|
||||
|
||||
pre {
|
||||
background: #0D0D0D;
|
||||
color: #FFF;
|
||||
background: #0d0d0d;
|
||||
color: #fff;
|
||||
font-family: 'JetBrainsMono', monospace;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
|
@ -467,12 +466,12 @@
|
|||
|
||||
blockquote {
|
||||
padding-left: 1rem;
|
||||
border-left: 2px solid rgba(#0D0D0D, 0.1);
|
||||
border-left: 2px solid rgba(#0d0d0d, 0.1);
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
border-top: 2px solid rgba(#0D0D0D, 0.1);
|
||||
border-top: 2px solid rgba(#0d0d0d, 0.1);
|
||||
margin: 2rem 0;
|
||||
}
|
||||
}
|
||||
|
@ -507,7 +506,7 @@
|
|||
box-sizing: border-box;
|
||||
position: relative;
|
||||
|
||||
>* {
|
||||
> * {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
@ -521,7 +520,7 @@
|
|||
.selectedCell:after {
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
content: "";
|
||||
content: '';
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
|
@ -557,8 +556,7 @@
|
|||
}
|
||||
|
||||
.ProseMirror {
|
||||
|
||||
ul[data-type="taskList"] {
|
||||
ul[data-type='taskList'] {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
|
||||
|
@ -566,13 +564,13 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
>label {
|
||||
> label {
|
||||
flex: 0 0 auto;
|
||||
margin-right: 0.5rem;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
>div {
|
||||
> div {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
}
|
||||
|
@ -580,7 +578,7 @@
|
|||
}
|
||||
|
||||
.ProseMirror {
|
||||
>*+* {
|
||||
> * + * {
|
||||
margin-top: 0.75em;
|
||||
}
|
||||
|
||||
|
@ -589,7 +587,7 @@
|
|||
|
||||
select {
|
||||
position: absolute;
|
||||
top: .5rem;
|
||||
top: 0.5rem;
|
||||
right: 0.5rem;
|
||||
border: 0;
|
||||
background: gray;
|
||||
|
@ -600,7 +598,7 @@
|
|||
pre {
|
||||
background: #0d0d0d;
|
||||
color: #fff;
|
||||
font-family: "JetBrainsMono", monospace;
|
||||
font-family: 'JetBrainsMono', monospace;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
|
||||
|
|
|
@ -1,4 +1,14 @@
|
|||
<script lang="jsx">
|
||||
<template>
|
||||
<NodeViewWrapper class="code-block">
|
||||
<select contenteditable="false" v-model="selectedLanguage">
|
||||
<option value="null">auto</option>
|
||||
<option disabled>—</option>
|
||||
<option v-for="(item, index) in languages" :value="item" :key="index">{{ item }}</option>
|
||||
</select>
|
||||
<pre><code><NodeViewContent /></code></pre>
|
||||
</NodeViewWrapper>
|
||||
</template>
|
||||
<script>
|
||||
import { NodeViewContent, nodeViewProps, NodeViewWrapper } from '@tiptap/vue'
|
||||
import { defineComponent } from '@opentiny/vue-common'
|
||||
|
||||
|
@ -23,27 +33,6 @@ export default defineComponent({
|
|||
this.updateAttributes({ language })
|
||||
}
|
||||
}
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<NodeViewWrapper class="code-block">
|
||||
<select contenteditable="false" v-model={this.selectedLanguage}>
|
||||
<option value="null">auto</option>
|
||||
<option disabled>—</option>
|
||||
{this.languages.map((item, index) => (
|
||||
<option value={item} key={index}>
|
||||
{' '}
|
||||
{item}{' '}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<pre>
|
||||
<code>
|
||||
<NodeViewContent />
|
||||
</code>
|
||||
</pre>
|
||||
</NodeViewWrapper>
|
||||
)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -2,12 +2,8 @@
|
|||
<div class="tiny-rich-text-editor">
|
||||
<div class="tiny-rich-text-editor__toolbar">
|
||||
<!-- starter-kit功能区 -->
|
||||
<template v-for="item in toolBar">
|
||||
<button
|
||||
v-if="(item.name ?? item) === 'font-size'"
|
||||
:title="t('ui.richTextEditor.fontSize')"
|
||||
class="font-size-box"
|
||||
>
|
||||
<template v-for="item in state.toolbar">
|
||||
<button v-if="item === 'font-size'" :title="t('ui.richTextEditor.fontSize')" class="font-size-box">
|
||||
<TinyIconRichTextFontSize></TinyIconRichTextFontSize>
|
||||
<div class="font-size-options">
|
||||
<button @click="state.editor.chain().focus().setSize({ size: 12 }).run()">12px</button>
|
||||
|
@ -20,7 +16,7 @@
|
|||
</div>
|
||||
</button>
|
||||
<button
|
||||
v-else-if="(item.name ?? item) === 'line-height'"
|
||||
v-else-if="item === 'line-height'"
|
||||
class="line-height-button"
|
||||
:title="t('ui.richTextEditor.lineHeight')"
|
||||
>
|
||||
|
@ -34,7 +30,7 @@
|
|||
<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'" :title="t('ui.richTextEditor.hBox')" class="h-box">
|
||||
<button v-else-if="item === 'h-box'" :title="t('ui.richTextEditor.hBox')" class="h-box">
|
||||
<div class="h-ico">
|
||||
<TinyIconRichTextHeading></TinyIconRichTextHeading>
|
||||
</div>
|
||||
|
@ -62,7 +58,7 @@
|
|||
</button>
|
||||
</div>
|
||||
</button>
|
||||
<button v-else-if="(item.name ?? item) === 'img'" :title="t('ui.richTextEditor.img')" class="image-button">
|
||||
<button v-else-if="item === 'img'" :title="t('ui.richTextEditor.img')" class="image-button">
|
||||
<TinyIconRichTextImage></TinyIconRichTextImage>
|
||||
<div class="img-option">
|
||||
<div class="img-item">
|
||||
|
@ -74,7 +70,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<button v-else-if="(item.name ?? item) === 'color'" :title="t('ui.richTextEditor.color')" class="color-button">
|
||||
<button v-else-if="item === 'color'" :title="t('ui.richTextEditor.color')" class="color-button">
|
||||
<label for="tiny-color">
|
||||
<TinyIconRichTextColor></TinyIconRichTextColor>
|
||||
</label>
|
||||
|
@ -85,7 +81,7 @@
|
|||
/>
|
||||
</button>
|
||||
<button
|
||||
v-else-if="(item.name ?? item) === 'backgroundColor'"
|
||||
v-else-if="item === 'backgroundColor'"
|
||||
:title="t('ui.richTextEditor.backgroundColor')"
|
||||
class="color-button"
|
||||
>
|
||||
|
@ -98,12 +94,18 @@
|
|||
@input="state.editor.chain().focus().setBackColor({ bgColor: $event.target.value }).run()"
|
||||
/>
|
||||
</button>
|
||||
<button v-else-if="(item.name ?? item) === 'table'" :title="t('ui.richTextEditor.table')" class="table-button">
|
||||
<div class="table-box" @click="handleClick">
|
||||
<button v-else-if="item === 'table'" :title="t('ui.richTextEditor.table')" class="table-button">
|
||||
<div class="table-box" v-clickoutside="closeTablePanel" @click="toggleTablePanel">
|
||||
<div class="table-icon">
|
||||
<TinyIconRichTextTable></TinyIconRichTextTable>
|
||||
</div>
|
||||
<div class="table-option" ref="box" v-if="state.isShow" @mousemove="handleMove">
|
||||
<div
|
||||
class="table-panel"
|
||||
ref="tablePanelRef"
|
||||
v-if="state.isShowTable"
|
||||
@mousemove="tableMouseMove"
|
||||
@click.stop="tableChoose"
|
||||
>
|
||||
<div class="table-row">
|
||||
<div class="item" :class="{ isActive: 1 <= state.flagX && 1 <= state.flagY }"></div>
|
||||
<div class="item" :class="{ isActive: 2 <= state.flagX && 1 <= state.flagY }"></div>
|
||||
|
@ -132,7 +134,7 @@
|
|||
</div>
|
||||
</button>
|
||||
<button
|
||||
v-else-if="(item.name ?? item) === 'unlink'"
|
||||
v-else-if="item === 'unlink'"
|
||||
:title="t('ui.richTextEditor.unlink')"
|
||||
@click="eventClick(state.editor, item)"
|
||||
:disabled="!state.editor?.isActive(Active(item))"
|
||||
|
@ -142,7 +144,7 @@
|
|||
</button>
|
||||
<button
|
||||
v-else
|
||||
:title="t(`ui.richTextEditor.${item.name ?? item}`)"
|
||||
:title="t(`ui.richTextEditor.${item}`)"
|
||||
@click="eventClick(state.editor, item)"
|
||||
:class="{ 'is-active': state.editor?.isActive(Active(item)) }"
|
||||
>
|
||||
|
@ -224,7 +226,7 @@
|
|||
</button>
|
||||
</BubbleMenu>
|
||||
</div>
|
||||
<div class="tiny-rich-text-editor__container" :style="{ fontSize }">
|
||||
<div class="tiny-rich-text-editor__container">
|
||||
<EditorContent :editor="state.editor"></EditorContent>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -282,15 +284,7 @@ import {
|
|||
iconRichTextUnderline,
|
||||
iconRichTextUndo
|
||||
} from '@opentiny/vue-icon'
|
||||
import {
|
||||
Editor,
|
||||
EditorContent,
|
||||
BubbleMenu,
|
||||
VueNodeViewRenderer
|
||||
// NodeViewContent,
|
||||
// nodeViewProps,
|
||||
// NodeViewWrapper
|
||||
} from '@tiptap/vue'
|
||||
import { Editor, EditorContent, BubbleMenu, VueNodeViewRenderer } from '@tiptap/vue'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
|
||||
// 段落包
|
||||
|
@ -332,6 +326,9 @@ import TextAlign from '@tiptap/extension-text-align'
|
|||
|
||||
// code high light
|
||||
import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight'
|
||||
// Placeholder
|
||||
import Placeholder from '@tiptap/extension-placeholder'
|
||||
|
||||
import css from 'highlight.js/lib/languages/css'
|
||||
import js from 'highlight.js/lib/languages/javascript'
|
||||
import ts from 'highlight.js/lib/languages/typescript'
|
||||
|
@ -352,7 +349,30 @@ function initLowLight() {
|
|||
}
|
||||
/* @__PURE__ */
|
||||
initLowLight()
|
||||
// import Codehighlight from './code-highlight.vue'
|
||||
|
||||
import { $props, setup, defineComponent, $prefix, directive } from '@opentiny/vue-common'
|
||||
import '@opentiny/vue-theme/rich-text-editor/index.less'
|
||||
import Clickoutside from '@opentiny/vue-renderless/common/deps/clickoutside'
|
||||
|
||||
export const richTextEditorProps = {
|
||||
...$props,
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
customToolBar: {
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
options: {
|
||||
type: Object,
|
||||
default: {}
|
||||
}
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: $prefix + 'RichTextEditor',
|
||||
|
@ -367,7 +387,8 @@ export default defineComponent({
|
|||
'destroy',
|
||||
'update'
|
||||
],
|
||||
props: [...props, 'modelValue', 'collaboration', 'placeholder', 'customToolBar', 'options'],
|
||||
directives: directive({ Clickoutside }),
|
||||
props: richTextEditorProps,
|
||||
components: {
|
||||
EditorContent,
|
||||
BubbleMenu,
|
||||
|
@ -449,10 +470,6 @@ export default defineComponent({
|
|||
CodeBlockLowlight,
|
||||
lowlight,
|
||||
VueNodeViewRenderer,
|
||||
// CodehighComp: VueNodeViewRenderer(Codehighlight(NodeViewContent, nodeViewProps, NodeViewWrapper)),
|
||||
// NodeViewContent,
|
||||
// nodeViewProps,
|
||||
// NodeViewWrapper,
|
||||
Placeholder,
|
||||
codeHighlight
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue