提交 910188e0 编写于 作者: Q qiang

style: editor

上级 9dfb037c
.ql-container {
display: block;
position: relative;
box-sizing: border-box;
-webkit-user-select: text;
user-select: text;
outline: none;
overflow: hidden;
width: 100%;
height: 200px;
min-height: 200px;
}
.ql-container[hidden] {
display: none;
}
.ql-container .ql-editor {
position: relative;
font-size: inherit;
line-height: inherit;
font-family: inherit;
min-height: inherit;
width: 100%;
height: 100%;
padding: 0;
overflow-x: hidden;
overflow-y: auto;
-webkit-tap-highlight-color: transparent;
-webkit-touch-callout: none;
-webkit-overflow-scrolling: touch;
}
.ql-container .ql-editor::-webkit-scrollbar {
width: 0 !important;
}
.ql-container .ql-editor.scroll-disabled {
overflow: hidden;
}
.ql-container .ql-image-overlay {
display: flex;
position: absolute;
box-sizing: border-box;
border: 1px dashed #ccc;
justify-content: center;
align-items: center;
-webkit-user-select: none;
user-select: none;
}
.ql-container .ql-image-overlay .ql-image-size {
position: absolute;
padding: 4px 8px;
text-align: center;
background-color: #fff;
color: #888;
border: 1px solid #ccc;
box-sizing: border-box;
opacity: 0.8;
right: 4px;
top: 4px;
font-size: 12px;
display: inline-block;
width: auto;
}
.ql-container .ql-image-overlay .ql-image-toolbar {
position: relative;
text-align: center;
box-sizing: border-box;
background: #000;
border-radius: 5px;
color: #fff;
font-size: 0;
min-height: 24px;
z-index: 100;
}
.ql-container .ql-image-overlay .ql-image-toolbar span {
display: inline-block;
cursor: pointer;
padding: 5px;
font-size: 12px;
border-right: 1px solid #fff;
}
.ql-container .ql-image-overlay .ql-image-toolbar span:last-child {
border-right: 0;
}
.ql-container .ql-image-overlay .ql-image-toolbar span.triangle-up {
padding: 0;
position: absolute;
top: -12px;
left: 50%;
transform: translatex(-50%);
width: 0;
height: 0;
border-width: 6px;
border-style: solid;
border-color: transparent transparent black transparent;
}
.ql-container .ql-image-overlay .ql-image-handle {
position: absolute;
height: 12px;
width: 12px;
border-radius: 50%;
border: 1px solid #ccc;
box-sizing: border-box;
background: #fff;
}
.ql-container img {
display: inline-block;
max-width: 100%;
}
.ql-clipboard p {
margin: 0;
padding: 0;
}
.ql-editor {
box-sizing: border-box;
height: 100%;
outline: none;
overflow-y: auto;
tab-size: 4;
-moz-tab-size: 4;
text-align: left;
white-space: pre-wrap;
word-wrap: break-word;
}
.ql-editor > * {
cursor: text;
}
.ql-editor p,
.ql-editor ol,
.ql-editor ul,
.ql-editor pre,
.ql-editor blockquote,
.ql-editor h1,
.ql-editor h2,
.ql-editor h3,
.ql-editor h4,
.ql-editor h5,
.ql-editor h6 {
margin: 0;
padding: 0;
counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9;
}
.ql-editor ol > li,
.ql-editor ul > li {
list-style-type: none;
}
.ql-editor ul > li::before {
content: '\2022';
}
.ql-editor ul[data-checked=true],
.ql-editor ul[data-checked=false] {
pointer-events: none;
}
.ql-editor ul[data-checked=true] > li *,
.ql-editor ul[data-checked=false] > li * {
pointer-events: all;
}
.ql-editor ul[data-checked=true] > li::before,
.ql-editor ul[data-checked=false] > li::before {
color: #777;
cursor: pointer;
pointer-events: all;
}
.ql-editor ul[data-checked=true] > li::before {
content: '\2611';
}
.ql-editor ul[data-checked=false] > li::before {
content: '\2610';
}
.ql-editor li::before {
display: inline-block;
white-space: nowrap;
width: 2em;
}
.ql-editor ol li {
counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9;
counter-increment: list-0;
}
.ql-editor ol li:before {
content: counter(list-0, decimal) '. ';
}
.ql-editor ol li.ql-indent-1 {
counter-increment: list-1;
}
.ql-editor ol li.ql-indent-1:before {
content: counter(list-1, lower-alpha) '. ';
}
.ql-editor ol li.ql-indent-1 {
counter-reset: list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9;
}
.ql-editor ol li.ql-indent-2 {
counter-increment: list-2;
}
.ql-editor ol li.ql-indent-2:before {
content: counter(list-2, lower-roman) '. ';
}
.ql-editor ol li.ql-indent-2 {
counter-reset: list-3 list-4 list-5 list-6 list-7 list-8 list-9;
}
.ql-editor ol li.ql-indent-3 {
counter-increment: list-3;
}
.ql-editor ol li.ql-indent-3:before {
content: counter(list-3, decimal) '. ';
}
.ql-editor ol li.ql-indent-3 {
counter-reset: list-4 list-5 list-6 list-7 list-8 list-9;
}
.ql-editor ol li.ql-indent-4 {
counter-increment: list-4;
}
.ql-editor ol li.ql-indent-4:before {
content: counter(list-4, lower-alpha) '. ';
}
.ql-editor ol li.ql-indent-4 {
counter-reset: list-5 list-6 list-7 list-8 list-9;
}
.ql-editor ol li.ql-indent-5 {
counter-increment: list-5;
}
.ql-editor ol li.ql-indent-5:before {
content: counter(list-5, lower-roman) '. ';
}
.ql-editor ol li.ql-indent-5 {
counter-reset: list-6 list-7 list-8 list-9;
}
.ql-editor ol li.ql-indent-6 {
counter-increment: list-6;
}
.ql-editor ol li.ql-indent-6:before {
content: counter(list-6, decimal) '. ';
}
.ql-editor ol li.ql-indent-6 {
counter-reset: list-7 list-8 list-9;
}
.ql-editor ol li.ql-indent-7 {
counter-increment: list-7;
}
.ql-editor ol li.ql-indent-7:before {
content: counter(list-7, lower-alpha) '. ';
}
.ql-editor ol li.ql-indent-7 {
counter-reset: list-8 list-9;
}
.ql-editor ol li.ql-indent-8 {
counter-increment: list-8;
}
.ql-editor ol li.ql-indent-8:before {
content: counter(list-8, lower-roman) '. ';
}
.ql-editor ol li.ql-indent-8 {
counter-reset: list-9;
}
.ql-editor ol li.ql-indent-9 {
counter-increment: list-9;
}
.ql-editor ol li.ql-indent-9:before {
content: counter(list-9, decimal) '. ';
}
.ql-editor .ql-indent-1:not(.ql-direction-rtl) {
padding-left: 2em;
}
.ql-editor li.ql-indent-1:not(.ql-direction-rtl) {
padding-left: 2em;
}
.ql-editor .ql-indent-1.ql-direction-rtl.ql-align-right {
padding-right: 2em;
}
.ql-editor li.ql-indent-1.ql-direction-rtl.ql-align-right {
padding-right: 2em;
}
.ql-editor .ql-indent-2:not(.ql-direction-rtl) {
padding-left: 4em;
}
.ql-editor li.ql-indent-2:not(.ql-direction-rtl) {
padding-left: 4em;
}
.ql-editor .ql-indent-2.ql-direction-rtl.ql-align-right {
padding-right: 4em;
}
.ql-editor li.ql-indent-2.ql-direction-rtl.ql-align-right {
padding-right: 4em;
}
.ql-editor .ql-indent-3:not(.ql-direction-rtl) {
padding-left: 6em;
}
.ql-editor li.ql-indent-3:not(.ql-direction-rtl) {
padding-left: 6em;
}
.ql-editor .ql-indent-3.ql-direction-rtl.ql-align-right {
padding-right: 6em;
}
.ql-editor li.ql-indent-3.ql-direction-rtl.ql-align-right {
padding-right: 6em;
}
.ql-editor .ql-indent-4:not(.ql-direction-rtl) {
padding-left: 8em;
}
.ql-editor li.ql-indent-4:not(.ql-direction-rtl) {
padding-left: 8em;
}
.ql-editor .ql-indent-4.ql-direction-rtl.ql-align-right {
padding-right: 8em;
}
.ql-editor li.ql-indent-4.ql-direction-rtl.ql-align-right {
padding-right: 8em;
}
.ql-editor .ql-indent-5:not(.ql-direction-rtl) {
padding-left: 10em;
}
.ql-editor li.ql-indent-5:not(.ql-direction-rtl) {
padding-left: 10em;
}
.ql-editor .ql-indent-5.ql-direction-rtl.ql-align-right {
padding-right: 10em;
}
.ql-editor li.ql-indent-5.ql-direction-rtl.ql-align-right {
padding-right: 10em;
}
.ql-editor .ql-indent-6:not(.ql-direction-rtl) {
padding-left: 12em;
}
.ql-editor li.ql-indent-6:not(.ql-direction-rtl) {
padding-left: 12em;
}
.ql-editor .ql-indent-6.ql-direction-rtl.ql-align-right {
padding-right: 12em;
}
.ql-editor li.ql-indent-6.ql-direction-rtl.ql-align-right {
padding-right: 12em;
}
.ql-editor .ql-indent-7:not(.ql-direction-rtl) {
padding-left: 14em;
}
.ql-editor li.ql-indent-7:not(.ql-direction-rtl) {
padding-left: 14em;
}
.ql-editor .ql-indent-7.ql-direction-rtl.ql-align-right {
padding-right: 14em;
}
.ql-editor li.ql-indent-7.ql-direction-rtl.ql-align-right {
padding-right: 14em;
}
.ql-editor .ql-indent-8:not(.ql-direction-rtl) {
padding-left: 16em;
}
.ql-editor li.ql-indent-8:not(.ql-direction-rtl) {
padding-left: 16em;
}
.ql-editor .ql-indent-8.ql-direction-rtl.ql-align-right {
padding-right: 16em;
}
.ql-editor li.ql-indent-8.ql-direction-rtl.ql-align-right {
padding-right: 16em;
}
.ql-editor .ql-indent-9:not(.ql-direction-rtl) {
padding-left: 18em;
}
.ql-editor li.ql-indent-9:not(.ql-direction-rtl) {
padding-left: 18em;
}
.ql-editor .ql-indent-9.ql-direction-rtl.ql-align-right {
padding-right: 18em;
}
.ql-editor li.ql-indent-9.ql-direction-rtl.ql-align-right {
padding-right: 18em;
}
.ql-editor .ql-direction-rtl {
direction: rtl;
text-align: inherit;
}
.ql-editor .ql-align-center {
text-align: center;
}
.ql-editor .ql-align-justify {
text-align: justify;
}
.ql-editor .ql-align-right {
text-align: right;
}
.ql-editor.ql-blank::before {
color: rgba(0, 0, 0, 0.6);
content: attr(data-placeholder);
font-style: italic;
pointer-events: none;
position: absolute;
}
.ql-container.ql-disabled .ql-editor ul[data-checked] > li::before {
pointer-events: none;
}
.ql-clipboard {
left: -100000px;
height: 1px;
overflow-y: hidden;
position: absolute;
top: 50%;
}
import { onMounted, Ref, ref, watch } from 'vue'
import QuillClass, {
QuillOptionsStatic,
EventEmitter,
RangeStatic,
StringMap,
} from 'quill'
import { useContextInfo, useSubscribe } from '@dcloudio/uni-components'
import { getRealPath } from '@dcloudio/uni-platform'
import { Ref, ref } from 'vue'
import { defineBuiltInComponent } from '../../helpers/component'
import { CustomEventTrigger, useCustomEvent } from '../../helpers/useEvent'
import { useCustomEvent } from '../../helpers/useEvent'
import {
props as keyboardProps,
emit as keyboardEmit,
useKeyboard,
} from '../../helpers/useKeyboard'
import HTMLParser from '../../helpers/html-parser'
import * as formats from './formats'
import loadScript from './load-script'
type EDITOR_CHANGE = 'editor-change'
type SCROLL_BEFORE_UPDATE = 'scroll-before-update'
type SCROLL_OPTIMIZE = 'scroll-optimize'
type SCROLL_UPDATE = 'scroll-update'
type SELECTION_CHANGE = 'selection-change'
type TEXT_CHANGE = 'text-change'
interface QuillSelection {
getRange: () => RangeStatic[]
savedRange: RangeStatic
}
interface QuillHistory {
undo: () => void
redo: () => void
}
interface QuillExt extends QuillClass {
selection: QuillSelection
on: (
eventName:
| EDITOR_CHANGE
| SCROLL_BEFORE_UPDATE
| SCROLL_OPTIMIZE
| SCROLL_UPDATE
| SELECTION_CHANGE
| TEXT_CHANGE,
handler: Function
) => EventEmitter
scrollIntoView: () => void
history: QuillHistory
}
interface QuillOptionsStaticExt extends QuillOptionsStatic {
toolbar?: boolean
}
interface WindowExt extends Window {
Quill?: typeof QuillClass
ImageResize?: any
}
function useQuill(
props: {
readOnly?: any
placeholder?: any
showImgSize?: any
showImgToolbar?: any
showImgResize?: any
},
rootRef: Ref<HTMLElement | null>,
trigger: CustomEventTrigger
) {
type ResizeModuleName = 'DisplaySize' | 'Toolbar' | 'Resize'
let quillReady: boolean
let skipMatcher: boolean
let quill: QuillExt
watch(
() => props.readOnly,
(value) => {
if (quillReady) {
quill.enable(!value)
if (!value) {
quill.blur()
}
}
}
)
watch(
() => props.placeholder,
(value) => {
if (quillReady) {
quill.root.setAttribute('data-placeholder', value)
}
}
)
function html2delta(html: string) {
const tags = [
'span',
'strong',
'b',
'ins',
'em',
'i',
'u',
'a',
'del',
's',
'sub',
'sup',
'img',
'div',
'p',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'hr',
'ol',
'ul',
'li',
'br',
]
let content = ''
let disable: boolean
HTMLParser(html, {
start: function (tag: string, attrs: any[], unary: boolean) {
if (!tags.includes(tag)) {
disable = !unary
return
}
disable = false
const arrts = attrs
.map(({ name, value }) => `${name}="${value}"`)
.join(' ')
const start = `<${tag} ${arrts} ${unary ? '/' : ''}>`
content += start
},
end: function (tag: string) {
if (!disable) {
content += `</${tag}>`
}
},
chars: function (text: string) {
if (!disable) {
content += text
}
},
})
skipMatcher = true
const delta = quill.clipboard.convert(content)
skipMatcher = false
return delta
}
function getContents() {
const html = quill.root.innerHTML
const text = quill.getText()
const delta = quill.getContents()
return {
html,
text,
delta,
}
}
let oldStatus: StringMap = {}
function updateStatus(range?: RangeStatic) {
const status = range ? quill.getFormat(range) : {}
const keys = Object.keys(status)
if (
keys.length !== Object.keys(oldStatus).length ||
keys.find((key) => status[key] !== oldStatus[key])
) {
oldStatus = status
trigger('statuschange', {} as Event, status)
}
}
function initQuill(imageResizeModules: ResizeModuleName[]) {
const Quill = (window as WindowExt).Quill as typeof QuillClass
formats.register(Quill)
const options: QuillOptionsStaticExt = {
toolbar: false,
readOnly: props.readOnly,
placeholder: props.placeholder,
}
if (imageResizeModules.length) {
Quill.register(
'modules/ImageResize',
(window as WindowExt).ImageResize.default
)
options.modules = {
ImageResize: {
modules: imageResizeModules,
},
}
}
const rootEl = rootRef.value as HTMLElement
quill = new Quill(rootEl, options) as QuillExt
const $el = quill.root
const events = ['focus', 'blur', 'input']
events.forEach((name) => {
$el.addEventListener(name, ($event) => {
if (name === 'input') {
$event.stopPropagation()
} else {
trigger(name, $event, getContents())
}
})
})
quill.on('text-change', () => {
trigger('input', {} as Event, getContents())
})
quill.on('selection-change', updateStatus)
quill.on('scroll-optimize', () => {
const range = quill.selection.getRange()[0]
updateStatus(range)
})
quill.clipboard.addMatcher(Node.ELEMENT_NODE, (node, delta) => {
if (skipMatcher) {
return delta
}
if (delta.ops) {
delta.ops = delta.ops
.filter(({ insert }) => typeof insert === 'string')
.map(({ insert }) => ({ insert }))
}
return delta
})
quillReady = true
trigger('ready', {} as Event, {})
}
onMounted(() => {
const imageResizeModules: ResizeModuleName[] = []
if (props.showImgSize) {
imageResizeModules.push('DisplaySize')
}
if (props.showImgToolbar) {
imageResizeModules.push('Toolbar')
}
if (props.showImgResize) {
imageResizeModules.push('Resize')
}
const quillSrc =
__PLATFORM__ === 'app'
? './__uniappquill.js'
: 'https://unpkg.com/quill@1.3.7/dist/quill.min.js'
loadScript((window as WindowExt).Quill, quillSrc, () => {
if (imageResizeModules.length) {
const imageResizeSrc =
__PLATFORM__ === 'app'
? './__uniappquillimageresize.js'
: 'https://unpkg.com/quill-image-resize-mp@3.0.1/image-resize.min.js'
loadScript((window as WindowExt).ImageResize, imageResizeSrc, () => {
initQuill(imageResizeModules)
})
} else {
initQuill(imageResizeModules)
}
})
})
const id = useContextInfo()
useSubscribe(
(type: string, data: any) => {
const { options, callbackId } = data
let res
let range: RangeStatic | undefined
let errMsg
if (quillReady) {
const Quill = (window as WindowExt).Quill as typeof QuillClass
switch (type) {
case 'format':
{
let { name = '', value = false } = options
range = quill.getSelection(true)
let format = quill.getFormat(range)[name] || false
if (
['bold', 'italic', 'underline', 'strike', 'ins'].includes(name)
) {
value = !format
} else if (name === 'direction') {
value = value === 'rtl' && format ? false : value
const align = quill.getFormat(range).align
if (value === 'rtl' && !align) {
quill.format('align', 'right', 'user')
} else if (!value && align === 'right') {
quill.format('align', false, 'user')
}
} else if (name === 'indent') {
const rtl = quill.getFormat(range).direction === 'rtl'
value = value === '+1'
if (rtl) {
value = !value
}
value = value ? '+1' : '-1'
} else {
if (name === 'list') {
value = value === 'check' ? 'unchecked' : value
format = format === 'checked' ? 'unchecked' : format
}
value =
(format && format !== (value || false)) || (!format && value)
? value
: !format
}
quill.format(name, value, 'user')
}
break
case 'insertDivider':
range = quill.getSelection(true)
quill.insertText(range.index, '\n', 'user')
quill.insertEmbed(range.index + 1, 'divider', true, 'user')
quill.setSelection(range.index + 2, 0, 'silent')
break
case 'insertImage':
{
range = quill.getSelection(true)
const {
src = '',
alt = '',
width = '',
height = '',
extClass = '',
data = {},
} = options
const path = getRealPath(src)
quill.insertEmbed(range.index, 'image', path, 'user')
const local = /^(file|blob):/.test(path) ? path : false
quill.formatText(range.index, 1, 'data-local', local)
quill.formatText(range.index, 1, 'alt', alt)
quill.formatText(range.index, 1, 'width', width)
quill.formatText(range.index, 1, 'height', height)
quill.formatText(range.index, 1, 'class', extClass)
quill.formatText(
range.index,
1,
'data-custom',
Object.keys(data)
.map((key) => `${key}=${data[key]}`)
.join('&')
)
quill.setSelection(range.index + 1, 0, 'silent')
}
break
case 'insertText':
{
range = quill.getSelection(true)
const { text = '' } = options
quill.insertText(range.index, text, 'user')
quill.setSelection(range.index + text.length, 0, 'silent')
}
break
case 'setContents':
{
const { delta, html } = options
if (typeof delta === 'object') {
quill.setContents(delta, 'silent')
} else if (typeof html === 'string') {
quill.setContents(html2delta(html), 'silent')
} else {
errMsg = 'contents is missing'
}
}
break
case 'getContents':
res = getContents()
break
case 'clear':
quill.setText('')
break
case 'removeFormat':
{
range = quill.getSelection(true)
const parchment = Quill.import('parchment')
if (range.length) {
quill.removeFormat(range.index, range.length, 'user')
} else {
Object.keys(quill.getFormat(range)).forEach((key) => {
if (parchment.query(key, parchment.Scope.INLINE)) {
quill.format(key, false)
}
})
}
}
break
case 'undo':
quill.history.undo()
break
case 'redo':
quill.history.redo()
break
case 'blur':
quill.blur()
break
case 'getSelectionText':
range = quill.selection.savedRange
res = { text: '' }
if (range && range.length !== 0) {
res.text = quill.getText(range.index, range.length)
}
break
case 'scrollIntoView':
quill.scrollIntoView()
break
default:
break
}
updateStatus(range)
} else {
errMsg = 'not ready'
}
if (callbackId) {
UniViewJSBridge.publishHandler('onEditorMethodCallback', {
callbackId,
data: Object.assign({}, res, {
errMsg: `${type}:${errMsg ? 'fail ' + errMsg : 'ok'}`,
}),
})
}
},
id,
true
)
}
import { useQuill } from './quill'
const props = /*#__PURE__*/ Object.assign({}, keyboardProps, {
id: {
......
import { onMounted, Ref, watch } from 'vue'
import QuillClass, {
QuillOptionsStatic,
EventEmitter,
RangeStatic,
StringMap,
} from 'quill'
import { useContextInfo, useSubscribe } from '@dcloudio/uni-components'
import { getRealPath } from '@dcloudio/uni-platform'
import { CustomEventTrigger } from '../../../helpers/useEvent'
import HTMLParser from '../../../helpers/html-parser'
import loadScript from './loadScript'
import * as formats from './formats'
type EDITOR_CHANGE = 'editor-change'
type SCROLL_BEFORE_UPDATE = 'scroll-before-update'
type SCROLL_OPTIMIZE = 'scroll-optimize'
type SCROLL_UPDATE = 'scroll-update'
type SELECTION_CHANGE = 'selection-change'
type TEXT_CHANGE = 'text-change'
interface QuillSelection {
getRange: () => RangeStatic[]
savedRange: RangeStatic
}
interface QuillHistory {
undo: () => void
redo: () => void
}
interface QuillExt extends QuillClass {
selection: QuillSelection
on: (
eventName:
| EDITOR_CHANGE
| SCROLL_BEFORE_UPDATE
| SCROLL_OPTIMIZE
| SCROLL_UPDATE
| SELECTION_CHANGE
| TEXT_CHANGE,
handler: Function
) => EventEmitter
scrollIntoView: () => void
history: QuillHistory
}
interface QuillOptionsStaticExt extends QuillOptionsStatic {
toolbar?: boolean
}
interface WindowExt extends Window {
Quill?: typeof QuillClass
ImageResize?: any
}
export function useQuill(
props: {
readOnly?: any
placeholder?: any
showImgSize?: any
showImgToolbar?: any
showImgResize?: any
},
rootRef: Ref<HTMLElement | null>,
trigger: CustomEventTrigger
) {
type ResizeModuleName = 'DisplaySize' | 'Toolbar' | 'Resize'
let quillReady: boolean
let skipMatcher: boolean
let quill: QuillExt
watch(
() => props.readOnly,
(value) => {
if (quillReady) {
quill.enable(!value)
if (!value) {
quill.blur()
}
}
}
)
watch(
() => props.placeholder,
(value) => {
if (quillReady) {
quill.root.setAttribute('data-placeholder', value)
}
}
)
function html2delta(html: string) {
const tags = [
'span',
'strong',
'b',
'ins',
'em',
'i',
'u',
'a',
'del',
's',
'sub',
'sup',
'img',
'div',
'p',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'hr',
'ol',
'ul',
'li',
'br',
]
let content = ''
let disable: boolean
HTMLParser(html, {
start: function (tag: string, attrs: any[], unary: boolean) {
if (!tags.includes(tag)) {
disable = !unary
return
}
disable = false
const arrts = attrs
.map(({ name, value }) => `${name}="${value}"`)
.join(' ')
const start = `<${tag} ${arrts} ${unary ? '/' : ''}>`
content += start
},
end: function (tag: string) {
if (!disable) {
content += `</${tag}>`
}
},
chars: function (text: string) {
if (!disable) {
content += text
}
},
})
skipMatcher = true
const delta = quill.clipboard.convert(content)
skipMatcher = false
return delta
}
function getContents() {
const html = quill.root.innerHTML
const text = quill.getText()
const delta = quill.getContents()
return {
html,
text,
delta,
}
}
let oldStatus: StringMap = {}
function updateStatus(range?: RangeStatic) {
const status = range ? quill.getFormat(range) : {}
const keys = Object.keys(status)
if (
keys.length !== Object.keys(oldStatus).length ||
keys.find((key) => status[key] !== oldStatus[key])
) {
oldStatus = status
trigger('statuschange', {} as Event, status)
}
}
function initQuill(imageResizeModules: ResizeModuleName[]) {
const Quill = (window as WindowExt).Quill as typeof QuillClass
formats.register(Quill)
const options: QuillOptionsStaticExt = {
toolbar: false,
readOnly: props.readOnly,
placeholder: props.placeholder,
}
if (imageResizeModules.length) {
Quill.register(
'modules/ImageResize',
(window as WindowExt).ImageResize.default
)
options.modules = {
ImageResize: {
modules: imageResizeModules,
},
}
}
const rootEl = rootRef.value as HTMLElement
quill = new Quill(rootEl, options) as QuillExt
const $el = quill.root
const events = ['focus', 'blur', 'input']
events.forEach((name) => {
$el.addEventListener(name, ($event) => {
if (name === 'input') {
$event.stopPropagation()
} else {
trigger(name, $event, getContents())
}
})
})
quill.on('text-change', () => {
trigger('input', {} as Event, getContents())
})
quill.on('selection-change', updateStatus)
quill.on('scroll-optimize', () => {
const range = quill.selection.getRange()[0]
updateStatus(range)
})
quill.clipboard.addMatcher(Node.ELEMENT_NODE, (node, delta) => {
if (skipMatcher) {
return delta
}
if (delta.ops) {
delta.ops = delta.ops
.filter(({ insert }) => typeof insert === 'string')
.map(({ insert }) => ({ insert }))
}
return delta
})
quillReady = true
trigger('ready', {} as Event, {})
}
onMounted(() => {
const imageResizeModules: ResizeModuleName[] = []
if (props.showImgSize) {
imageResizeModules.push('DisplaySize')
}
if (props.showImgToolbar) {
imageResizeModules.push('Toolbar')
}
if (props.showImgResize) {
imageResizeModules.push('Resize')
}
const quillSrc =
__PLATFORM__ === 'app'
? './__uniappquill.js'
: 'https://unpkg.com/quill@1.3.7/dist/quill.min.js'
loadScript((window as WindowExt).Quill, quillSrc, () => {
if (imageResizeModules.length) {
const imageResizeSrc =
__PLATFORM__ === 'app'
? './__uniappquillimageresize.js'
: 'https://unpkg.com/quill-image-resize-mp@3.0.1/image-resize.min.js'
loadScript((window as WindowExt).ImageResize, imageResizeSrc, () => {
initQuill(imageResizeModules)
})
} else {
initQuill(imageResizeModules)
}
})
})
const id = useContextInfo()
useSubscribe(
(type: string, data: any) => {
const { options, callbackId } = data
let res
let range: RangeStatic | undefined
let errMsg
if (quillReady) {
const Quill = (window as WindowExt).Quill as typeof QuillClass
switch (type) {
case 'format':
{
let { name = '', value = false } = options
range = quill.getSelection(true)
let format = quill.getFormat(range)[name] || false
if (
['bold', 'italic', 'underline', 'strike', 'ins'].includes(name)
) {
value = !format
} else if (name === 'direction') {
value = value === 'rtl' && format ? false : value
const align = quill.getFormat(range).align
if (value === 'rtl' && !align) {
quill.format('align', 'right', 'user')
} else if (!value && align === 'right') {
quill.format('align', false, 'user')
}
} else if (name === 'indent') {
const rtl = quill.getFormat(range).direction === 'rtl'
value = value === '+1'
if (rtl) {
value = !value
}
value = value ? '+1' : '-1'
} else {
if (name === 'list') {
value = value === 'check' ? 'unchecked' : value
format = format === 'checked' ? 'unchecked' : format
}
value =
(format && format !== (value || false)) || (!format && value)
? value
: !format
}
quill.format(name, value, 'user')
}
break
case 'insertDivider':
range = quill.getSelection(true)
quill.insertText(range.index, '\n', 'user')
quill.insertEmbed(range.index + 1, 'divider', true, 'user')
quill.setSelection(range.index + 2, 0, 'silent')
break
case 'insertImage':
{
range = quill.getSelection(true)
const {
src = '',
alt = '',
width = '',
height = '',
extClass = '',
data = {},
} = options
const path = getRealPath(src)
quill.insertEmbed(range.index, 'image', path, 'user')
const local = /^(file|blob):/.test(path) ? path : false
quill.formatText(range.index, 1, 'data-local', local)
quill.formatText(range.index, 1, 'alt', alt)
quill.formatText(range.index, 1, 'width', width)
quill.formatText(range.index, 1, 'height', height)
quill.formatText(range.index, 1, 'class', extClass)
quill.formatText(
range.index,
1,
'data-custom',
Object.keys(data)
.map((key) => `${key}=${data[key]}`)
.join('&')
)
quill.setSelection(range.index + 1, 0, 'silent')
}
break
case 'insertText':
{
range = quill.getSelection(true)
const { text = '' } = options
quill.insertText(range.index, text, 'user')
quill.setSelection(range.index + text.length, 0, 'silent')
}
break
case 'setContents':
{
const { delta, html } = options
if (typeof delta === 'object') {
quill.setContents(delta, 'silent')
} else if (typeof html === 'string') {
quill.setContents(html2delta(html), 'silent')
} else {
errMsg = 'contents is missing'
}
}
break
case 'getContents':
res = getContents()
break
case 'clear':
quill.setText('')
break
case 'removeFormat':
{
range = quill.getSelection(true)
const parchment = Quill.import('parchment')
if (range.length) {
quill.removeFormat(range.index, range.length, 'user')
} else {
Object.keys(quill.getFormat(range)).forEach((key) => {
if (parchment.query(key, parchment.Scope.INLINE)) {
quill.format(key, false)
}
})
}
}
break
case 'undo':
quill.history.undo()
break
case 'redo':
quill.history.redo()
break
case 'blur':
quill.blur()
break
case 'getSelectionText':
range = quill.selection.savedRange
res = { text: '' }
if (range && range.length !== 0) {
res.text = quill.getText(range.index, range.length)
}
break
case 'scrollIntoView':
quill.scrollIntoView()
break
default:
break
}
updateStatus(range)
} else {
errMsg = 'not ready'
}
if (callbackId) {
UniViewJSBridge.publishHandler('onEditorMethodCallback', {
callbackId,
data: Object.assign({}, res, {
errMsg: `${type}:${errMsg ? 'fail ' + errMsg : 'ok'}`,
}),
})
}
},
id,
true
)
}
......@@ -2236,10 +2236,7 @@ function useQuill(props2, rootRef, trigger) {
});
const id = useContextInfo();
useSubscribe((type, data) => {
const {
options,
callbackId
} = data;
const {options, callbackId} = data;
let res;
let errMsg;
{
......
......@@ -3465,6 +3465,26 @@ function makeMap(str) {
}
return obj;
}
const scripts = {};
function loadScript(globalName, src, callback) {
const globalObject = typeof globalName === "string" ? window[globalName] : globalName;
if (globalObject) {
callback();
return;
}
let callbacks2 = scripts[src];
if (!callbacks2) {
callbacks2 = scripts[src] = [];
const script = document.createElement("script");
script.src = src;
document.body.appendChild(script);
script.onload = function() {
callbacks2.forEach((callback2) => callback2());
delete scripts[src];
};
}
callbacks2.push(callback);
}
function divider(Quill) {
const BlockEmbed = Quill.import("blots/block/embed");
class Divider extends BlockEmbed {
......@@ -3721,26 +3741,6 @@ function register(Quill) {
Object.values(formats).forEach((value) => Object.assign(options, value(Quill)));
Quill.register(options, true);
}
const scripts = {};
function loadScript(globalName, src, callback) {
const globalObject = typeof globalName === "string" ? window[globalName] : globalName;
if (globalObject) {
callback();
return;
}
let callbacks2 = scripts[src];
if (!callbacks2) {
callbacks2 = scripts[src] = [];
const script = document.createElement("script");
script.src = src;
document.body.appendChild(script);
script.onload = function() {
callbacks2.forEach((callback2) => callback2());
delete scripts[src];
};
}
callbacks2.push(callback);
}
function useQuill(props2, rootRef, trigger) {
let quillReady;
let skipMatcher;
......@@ -3759,7 +3759,34 @@ function useQuill(props2, rootRef, trigger) {
}
});
function html2delta(html) {
const tags = ["span", "strong", "b", "ins", "em", "i", "u", "a", "del", "s", "sub", "sup", "img", "div", "p", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "ol", "ul", "li", "br"];
const tags = [
"span",
"strong",
"b",
"ins",
"em",
"i",
"u",
"a",
"del",
"s",
"sub",
"sup",
"img",
"div",
"p",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"hr",
"ol",
"ul",
"li",
"br"
];
let content = "";
let disable;
HTMLParser(html, {
......@@ -3769,10 +3796,7 @@ function useQuill(props2, rootRef, trigger) {
return;
}
disable = false;
const arrts = attrs2.map(({
name,
value
}) => `${name}="${value}"`).join(" ");
const arrts = attrs2.map(({name, value}) => `${name}="${value}"`).join(" ");
const start = `<${tag} ${arrts} ${unary ? "/" : ""}>`;
content += start;
},
......@@ -3853,13 +3877,7 @@ function useQuill(props2, rootRef, trigger) {
return delta;
}
if (delta.ops) {
delta.ops = delta.ops.filter(({
insert
}) => typeof insert === "string").map(({
insert
}) => ({
insert
}));
delta.ops = delta.ops.filter(({insert}) => typeof insert === "string").map(({insert}) => ({insert}));
}
return delta;
});
......@@ -3891,10 +3909,7 @@ function useQuill(props2, rootRef, trigger) {
});
const id2 = useContextInfo();
useSubscribe((type, data) => {
const {
options,
callbackId
} = data;
const {options, callbackId} = data;
let res;
let range;
let errMsg;
......@@ -3903,10 +3918,7 @@ function useQuill(props2, rootRef, trigger) {
switch (type) {
case "format":
{
let {
name = "",
value = false
} = options;
let {name = "", value = false} = options;
range = quill.getSelection(true);
let format = quill.getFormat(range)[name] || false;
if (["bold", "italic", "underline", "strike", "ins"].includes(name)) {
......@@ -3968,19 +3980,14 @@ function useQuill(props2, rootRef, trigger) {
case "insertText":
{
range = quill.getSelection(true);
const {
text: text2 = ""
} = options;
const {text: text2 = ""} = options;
quill.insertText(range.index, text2, "user");
quill.setSelection(range.index + text2.length, 0, "silent");
}
break;
case "setContents":
{
const {
delta,
html
} = options;
const {delta, html} = options;
if (typeof delta === "object") {
quill.setContents(delta, "silent");
} else if (typeof html === "string") {
......@@ -4022,9 +4029,7 @@ function useQuill(props2, rootRef, trigger) {
break;
case "getSelectionText":
range = quill.selection.savedRange;
res = {
text: ""
};
res = {text: ""};
if (range && range.length !== 0) {
res.text = quill.getText(range.index, range.length);
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册