From 910188e0e41c2df4b6dfc7cbe3bd656918322eab Mon Sep 17 00:00:00 2001 From: qiang Date: Wed, 19 May 2021 20:41:39 +0800 Subject: [PATCH] style: editor --- .../src/components/editor/editor.css | 395 ----------------- .../src/components/editor/index.tsx | 418 +----------------- .../editor/{ => quill}/formats/align.ts | 0 .../editor/{ => quill}/formats/background.ts | 0 .../editor/{ => quill}/formats/box.ts | 0 .../editor/{ => quill}/formats/direction.ts | 0 .../editor/{ => quill}/formats/divider.ts | 0 .../editor/{ => quill}/formats/font.ts | 0 .../editor/{ => quill}/formats/image.ts | 0 .../editor/{ => quill}/formats/index.ts | 0 .../editor/{ => quill}/formats/ins.ts | 0 .../editor/{ => quill}/formats/list.ts | 0 .../editor/{ => quill}/formats/text.ts | 0 .../src/components/editor/quill/index.ts | 416 +++++++++++++++++ .../{load-script.ts => quill/loadScript.ts} | 0 packages/uni-h5/dist/uni-h5.cjs.js | 5 +- packages/uni-h5/dist/uni-h5.es.js | 105 ++--- 17 files changed, 475 insertions(+), 864 deletions(-) delete mode 100644 packages/uni-components/src/components/editor/editor.css rename packages/uni-components/src/components/editor/{ => quill}/formats/align.ts (100%) rename packages/uni-components/src/components/editor/{ => quill}/formats/background.ts (100%) rename packages/uni-components/src/components/editor/{ => quill}/formats/box.ts (100%) rename packages/uni-components/src/components/editor/{ => quill}/formats/direction.ts (100%) rename packages/uni-components/src/components/editor/{ => quill}/formats/divider.ts (100%) rename packages/uni-components/src/components/editor/{ => quill}/formats/font.ts (100%) rename packages/uni-components/src/components/editor/{ => quill}/formats/image.ts (100%) rename packages/uni-components/src/components/editor/{ => quill}/formats/index.ts (100%) rename packages/uni-components/src/components/editor/{ => quill}/formats/ins.ts (100%) rename packages/uni-components/src/components/editor/{ => quill}/formats/list.ts (100%) rename packages/uni-components/src/components/editor/{ => quill}/formats/text.ts (100%) create mode 100644 packages/uni-components/src/components/editor/quill/index.ts rename packages/uni-components/src/components/editor/{load-script.ts => quill/loadScript.ts} (100%) diff --git a/packages/uni-components/src/components/editor/editor.css b/packages/uni-components/src/components/editor/editor.css deleted file mode 100644 index 0145223bc..000000000 --- a/packages/uni-components/src/components/editor/editor.css +++ /dev/null @@ -1,395 +0,0 @@ -.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%; -} diff --git a/packages/uni-components/src/components/editor/index.tsx b/packages/uni-components/src/components/editor/index.tsx index dc6e905a7..ae90c7b47 100644 --- a/packages/uni-components/src/components/editor/index.tsx +++ b/packages/uni-components/src/components/editor/index.tsx @@ -1,424 +1,12 @@ -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, - 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 += `` - } - }, - 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: { diff --git a/packages/uni-components/src/components/editor/formats/align.ts b/packages/uni-components/src/components/editor/quill/formats/align.ts similarity index 100% rename from packages/uni-components/src/components/editor/formats/align.ts rename to packages/uni-components/src/components/editor/quill/formats/align.ts diff --git a/packages/uni-components/src/components/editor/formats/background.ts b/packages/uni-components/src/components/editor/quill/formats/background.ts similarity index 100% rename from packages/uni-components/src/components/editor/formats/background.ts rename to packages/uni-components/src/components/editor/quill/formats/background.ts diff --git a/packages/uni-components/src/components/editor/formats/box.ts b/packages/uni-components/src/components/editor/quill/formats/box.ts similarity index 100% rename from packages/uni-components/src/components/editor/formats/box.ts rename to packages/uni-components/src/components/editor/quill/formats/box.ts diff --git a/packages/uni-components/src/components/editor/formats/direction.ts b/packages/uni-components/src/components/editor/quill/formats/direction.ts similarity index 100% rename from packages/uni-components/src/components/editor/formats/direction.ts rename to packages/uni-components/src/components/editor/quill/formats/direction.ts diff --git a/packages/uni-components/src/components/editor/formats/divider.ts b/packages/uni-components/src/components/editor/quill/formats/divider.ts similarity index 100% rename from packages/uni-components/src/components/editor/formats/divider.ts rename to packages/uni-components/src/components/editor/quill/formats/divider.ts diff --git a/packages/uni-components/src/components/editor/formats/font.ts b/packages/uni-components/src/components/editor/quill/formats/font.ts similarity index 100% rename from packages/uni-components/src/components/editor/formats/font.ts rename to packages/uni-components/src/components/editor/quill/formats/font.ts diff --git a/packages/uni-components/src/components/editor/formats/image.ts b/packages/uni-components/src/components/editor/quill/formats/image.ts similarity index 100% rename from packages/uni-components/src/components/editor/formats/image.ts rename to packages/uni-components/src/components/editor/quill/formats/image.ts diff --git a/packages/uni-components/src/components/editor/formats/index.ts b/packages/uni-components/src/components/editor/quill/formats/index.ts similarity index 100% rename from packages/uni-components/src/components/editor/formats/index.ts rename to packages/uni-components/src/components/editor/quill/formats/index.ts diff --git a/packages/uni-components/src/components/editor/formats/ins.ts b/packages/uni-components/src/components/editor/quill/formats/ins.ts similarity index 100% rename from packages/uni-components/src/components/editor/formats/ins.ts rename to packages/uni-components/src/components/editor/quill/formats/ins.ts diff --git a/packages/uni-components/src/components/editor/formats/list.ts b/packages/uni-components/src/components/editor/quill/formats/list.ts similarity index 100% rename from packages/uni-components/src/components/editor/formats/list.ts rename to packages/uni-components/src/components/editor/quill/formats/list.ts diff --git a/packages/uni-components/src/components/editor/formats/text.ts b/packages/uni-components/src/components/editor/quill/formats/text.ts similarity index 100% rename from packages/uni-components/src/components/editor/formats/text.ts rename to packages/uni-components/src/components/editor/quill/formats/text.ts diff --git a/packages/uni-components/src/components/editor/quill/index.ts b/packages/uni-components/src/components/editor/quill/index.ts new file mode 100644 index 000000000..edd46cbf3 --- /dev/null +++ b/packages/uni-components/src/components/editor/quill/index.ts @@ -0,0 +1,416 @@ +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, + 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 += `` + } + }, + 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 + ) +} diff --git a/packages/uni-components/src/components/editor/load-script.ts b/packages/uni-components/src/components/editor/quill/loadScript.ts similarity index 100% rename from packages/uni-components/src/components/editor/load-script.ts rename to packages/uni-components/src/components/editor/quill/loadScript.ts diff --git a/packages/uni-h5/dist/uni-h5.cjs.js b/packages/uni-h5/dist/uni-h5.cjs.js index e096e7998..82f10d1fc 100644 --- a/packages/uni-h5/dist/uni-h5.cjs.js +++ b/packages/uni-h5/dist/uni-h5.cjs.js @@ -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; { diff --git a/packages/uni-h5/dist/uni-h5.es.js b/packages/uni-h5/dist/uni-h5.es.js index 11d466b34..da4354382 100644 --- a/packages/uni-h5/dist/uni-h5.es.js +++ b/packages/uni-h5/dist/uni-h5.es.js @@ -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); } -- GitLab