提交 517afa30 编写于 作者: Q qiang

feat: add editor

fix: editor format

fix: editor clipboard

fix: editor convert

fix: editor html2delta

fix: editor imgToolbar
上级 fd41241e
......@@ -59,7 +59,8 @@ const media = [
'saveVideoToPhotosAlbum',
'createVideoContext',
'createCameraContext',
'createLivePlayerContext'
'createLivePlayerContext',
'createEditorContext'
]
const device = [
......@@ -111,9 +112,9 @@ const device = [
'onBeaconUpdate',
'getBeacons',
'startBeaconDiscovery',
'stopBeaconDiscovery',
'checkIsSupportSoterAuthentication',
'checkIsSoterEnrolledInDevice',
'stopBeaconDiscovery',
'checkIsSupportSoterAuthentication',
'checkIsSoterEnrolledInDevice',
'startSoterAuthentication'
]
......@@ -194,9 +195,9 @@ const third = [
'onPush',
'offPush',
'requireNativePlugin',
'upx2px',
'restoreGlobal',
'getSubNVueById',
'upx2px',
'restoreGlobal',
'getSubNVueById',
'getCurrentSubNVue'
]
......
......@@ -66,7 +66,8 @@
"uni.saveVideoToPhotosAlbum": true,
"uni.createVideoContext": true,
"uni.createCameraContext": true,
"uni.createLivePlayerContext": true
"uni.createLivePlayerContext": true,
"uni.createEditorContext": true
}
}, {
"name": "device",
......@@ -152,7 +153,7 @@
"uni.stopPullDownRefresh": true,
"uni.createSelectorQuery": true,
"uni.createIntersectionObserver": true,
"uni.hideKeyboard": true,
"uni.hideKeyboard": true,
"uni.onKeyboardHeightChange": true
}
}, {
......@@ -206,4 +207,4 @@
"uni.base64ToArrayBuffer": true,
"uni.arrayBufferToBase64": true
}
}]
}]
module.exports = {
module.exports = {
'resize-sensor': ['h5'],
'ad': ['mp-weixin'],
'audio': ['app-plus', 'mp-weixin', 'h5'],
......@@ -9,6 +9,7 @@ module.exports = {
'checkbox-group': ['app-plus', 'mp-weixin', 'h5'],
'cover-image': ['app-plus', 'mp-weixin'],
'cover-view': ['app-plus', 'mp-weixin'],
'editor': ['app-plus', 'mp-weixin', 'h5'],
'form': ['app-plus', 'mp-weixin', 'h5'],
'functional-page-navigator': ['mp-weixin'],
'icon': ['app-plus', 'mp-weixin'],
......@@ -40,4 +41,4 @@ module.exports = {
'video': ['app-plus', 'mp-weixin', 'h5'],
'view': ['app-plus', 'mp-weixin', 'h5'],
'web-view': ['app-plus', 'mp-weixin']
}
}
......@@ -20,6 +20,7 @@ module.exports = [
'uni-checkbox-group',
'uni-cover-image',
'uni-cover-view',
'uni-editor',
'uni-form',
'uni-functional-page-navigator',
'uni-icon',
......
import {
getCurrentPageId
} from '../../platform'
import {
callback
} from 'uni-shared'
function operateEditor (componentId, pageId, type, data) {
UniServiceJSBridge.publishHandler(pageId + '-editor-' + componentId, {
componentId,
type,
data
}, pageId)
}
UniServiceJSBridge.subscribe('onEditorMethodCallback', ({
callbackId,
data
}) => {
callback.invoke(callbackId, data)
})
const methods = ['insertDivider', 'insertImage', 'insertText', 'setContents', 'getContents', 'clear', 'removeFormat', 'undo', 'redo']
class EditorContext {
constructor (id, pageId) {
this.id = id
this.pageId = pageId
}
format (name, value) {
operateEditor(this.id, this.pageId, 'format', {
options: {
name,
value
}
})
}
}
methods.forEach(function (method) {
EditorContext.prototype[method] = callback.warp(function (options, callbackId) {
operateEditor(this.id, this.pageId, method, {
options,
callbackId
})
})
})
export function createEditorContext (id, context) {
if (context) {
return new EditorContext(id, context.$page.id)
}
const pageId = getCurrentPageId()
if (pageId) {
return new EditorContext(id, pageId)
} else {
UniServiceJSBridge.emit('onError', 'createEditorContext:fail')
}
}
.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%;
}
export default function (Quill) {
const { Scope, Attributor } = Quill.import('parchment')
const config = {
scope: Scope.BLOCK,
whitelist: ['left', 'right', 'center', 'justify']
}
const AlignStyle = new Attributor.Style('align', 'text-align', config)
return {
'formats/align': AlignStyle
}
}
export default function (Quill) {
const { Scope } = Quill.import('parchment')
const BackgroundStyle = Quill.import('formats/background')
const BackgroundColorStyle = new BackgroundStyle.constructor('backgroundColor', 'background-color', {
scope: Scope.INLINE
})
return {
'formats/backgroundColor': BackgroundColorStyle
}
}
import { kebabCase } from 'uni-shared'
export default function (Quill) {
const { Scope, Attributor } = Quill.import('parchment')
const config = {
scope: Scope.BLOCK
}
const margin = ['margin', 'marginTop', 'marginBottom', 'marginLeft', 'marginRight']
const padding = ['padding', 'paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight']
const result = {}
margin.concat(padding).forEach(name => {
result[`formats/${name}`] = new Attributor.Style(name, kebabCase(name), config)
})
return result
}
export default function (Quill) {
const { Scope, Attributor } = Quill.import('parchment')
const config = {
scope: Scope.BLOCK,
whitelist: ['rtl']
}
const DirectionStyle = new Attributor.Style('direction', 'direction', config)
return {
'formats/direction': DirectionStyle
}
}
export default function (Quill) {
const BlockEmbed = Quill.import('blots/block/embed')
class Divider extends BlockEmbed { }
Divider.blotName = 'divider'
Divider.tagName = 'HR'
return {
'formats/divider': Divider
}
}
import { kebabCase } from 'uni-shared'
export default function (Quill) {
const { Scope, Attributor } = Quill.import('parchment')
const config = {
scope: Scope.INLINE
}
const font = ['font', 'fontSize', 'fontStyle', 'fontVariant', 'fontWeight', 'fontFamily']
const result = {}
font.forEach(name => {
result[`formats/${name}`] = new Attributor.Style(name, kebabCase(name), config)
})
return result
}
import divider from './divider'
import ins from './ins'
import align from './align'
import direction from './direction'
import list from './list'
import background from './background'
import box from './box'
import font from './font'
import text from './text'
export function register (Quill) {
const formats = {
divider,
ins,
align,
direction,
list,
background,
box,
font,
text
}
const options = {}
Object.values(formats).forEach(value => Object.assign(options, value(Quill)))
Quill.register(options, true)
}
export default function (Quill) {
const Inline = Quill.import('blots/inline')
class Ins extends Inline { }
Ins.blotName = 'ins'
Ins.tagName = 'INS'
return {
'formats/ins': Ins
}
}
export default function (Quill) {
const Parchment = Quill.import('parchment')
const Container = Quill.import('blots/container')
const ListItem = Quill.import('formats/list/item')
class List extends Container {
static create (value) {
let tagName = value === 'ordered' ? 'OL' : 'UL'
let node = super.create(tagName)
if (value === 'checked' || value === 'unchecked') {
node.setAttribute('data-checked', value === 'checked')
}
return node
}
static formats (domNode) {
if (domNode.tagName === 'OL') return 'ordered'
if (domNode.tagName === 'UL') {
if (domNode.hasAttribute('data-checked')) {
return domNode.getAttribute('data-checked') === 'true' ? 'checked' : 'unchecked'
} else {
return 'bullet'
}
}
return undefined
}
constructor (domNode) {
super(domNode)
const listEventHandler = (e) => {
if (e.target.parentNode !== domNode) return
let format = this.statics.formats(domNode)
let blot = Parchment.find(e.target)
if (format === 'checked') {
blot.format('list', 'unchecked')
} else if (format === 'unchecked') {
blot.format('list', 'checked')
}
}
domNode.addEventListener('click', listEventHandler)
}
format (name, value) {
if (this.children.length > 0) {
this.children.tail.format(name, value)
}
}
formats () {
// We don't inherit from FormatBlot
return { [this.statics.blotName]: this.statics.formats(this.domNode) }
}
insertBefore (blot, ref) {
if (blot instanceof ListItem) {
super.insertBefore(blot, ref)
} else {
let index = ref == null ? this.length() : ref.offset(this)
let after = this.split(index)
after.parent.insertBefore(blot, after)
}
}
optimize (context) {
super.optimize(context)
let next = this.next
if (next != null && next.prev === this &&
next.statics.blotName === this.statics.blotName &&
next.domNode.tagName === this.domNode.tagName &&
next.domNode.getAttribute('data-checked') === this.domNode.getAttribute('data-checked')) {
next.moveChildren(this)
next.remove()
}
}
replace (target) {
if (target.statics.blotName !== this.statics.blotName) {
let item = Parchment.create(this.statics.defaultChild)
target.moveChildren(item)
this.appendChild(item)
}
super.replace(target)
}
}
List.blotName = 'list'
List.scope = Parchment.Scope.BLOCK_BLOT
List.tagName = ['OL', 'UL']
List.defaultChild = 'list-item'
List.allowedChildren = [ListItem]
return {
'formats/list': List
}
}
import { kebabCase } from 'uni-shared'
export default function (Quill) {
const { Scope, Attributor } = Quill.import('parchment')
const text = [{
name: 'lineHeight',
scope: Scope.BLOCK
}, {
name: 'letterSpacing',
scope: Scope.INLINE
}, {
name: 'textDecoration',
scope: Scope.INLINE
}, {
name: 'textIndent',
scope: Scope.BLOCK
}]
const result = {}
text.forEach(({ name, scope }) => {
result[`formats/${name}`] = new Attributor.Style(name, kebabCase(name), {
scope
})
})
return result
}
<template>
<uni-editor
:id="id"
class="ql-container" />
</template>
<script>
import {
subscriber,
emitter,
keyboard
} from 'uni-mixins'
import HTMLParser from 'uni-helpers/html-parser'
import * as formats from './formats'
let Quill
let ImageResize
export default {
name: 'Editor',
mixins: [subscriber, emitter, keyboard],
props: {
id: {
type: String,
default: ''
},
readOnly: {
type: [Boolean, String],
default: false
},
placeholder: {
type: String,
default: ''
},
showImgSize: {
type: [Boolean, String],
default: false
},
showImgToolbar: {
type: [Boolean, String],
default: false
},
showImgResize: {
type: [Boolean, String],
default: false
}
},
data () {
return {
quillReady: false
}
},
computed: {
},
watch: {
readOnly (value) {
if (this.quillReady) {
const quill = this.quill
quill.enable(!value)
if (!value) {
quill.blur()
}
}
},
placeholder (value) {
if (this.quillReady) {
this.quill.root.setAttribute('data-placeholder', value)
}
}
},
mounted () {
const imageResizeModules = []
if (this.showImgSize) {
imageResizeModules.push('DisplaySize')
}
if (this.showImgToolbar) {
imageResizeModules.push('Toolbar')
}
if (this.showImgResize) {
imageResizeModules.push('Resize')
}
this.loadQuill(() => {
Quill = window.Quill
if (imageResizeModules.length) {
this.loadImageResizeModule(() => {
ImageResize = window.ImageResize
Quill.register('modules/ImageResize', ImageResize.default)
this.initQuill(imageResizeModules)
})
} else {
this.initQuill(imageResizeModules)
}
})
},
methods: {
_handleSubscribe ({
type,
data
}) {
const { options, callbackId } = data
const quill = this.quill
let res
let range
let errMsg
if (this.quillReady) {
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', Quill.sources.USER)
} else if (!value && align === 'right') {
quill.format('align', false, Quill.sources.USER)
}
} else if (name === 'indent') {
const rtl = quill.getFormat(range).direction === 'rtl'
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, Quill.sources.USER)
break
case 'insertDivider':
range = quill.getSelection(true)
quill.insertText(range.index, '\n', Quill.sources.USER)
quill.insertEmbed(range.index + 1, 'divider', true, Quill.sources.USER)
quill.setSelection(range.index + 2, Quill.sources.SILENT)
break
case 'insertImage':
range = quill.getSelection(true)
const { src = '', alt = '', data = {} } = options
quill.insertEmbed(range.index, 'image', src, Quill.sources.USER)
quill.formatText(range.index, 1, 'alt', alt)
quill.formatText(range.index, 1, 'data-custom', Object.keys(data).map(key => `${key}=${data[key]}`).join('&'))
quill.setSelection(range.index + 1, Quill.sources.SILENT)
break
case 'insertText':
range = quill.getSelection(true)
const { text = '' } = options
quill.insertText(range.index, text, Quill.sources.USER)
quill.setSelection(range.index + text.length, 0, Quill.sources.SILENT)
break
case 'setContents':
const { delta, html } = options
if (typeof delta === 'object') {
quill.setContents(delta, Quill.sources.SILENT)
} else if (typeof html === 'string') {
quill.setContents(this.html2delta(html), Quill.sources.SILENT)
} else {
errMsg = 'contents is missing'
}
break
case 'getContents':
res = this.getContents()
break
case 'clear':
quill.setContents([])
break
case 'removeFormat':
range = quill.getSelection(true)
var parchment = Quill.import('parchment')
if (range.length) {
quill.removeFormat(range, Quill.sources.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
default:
break
}
} else {
errMsg = 'not ready'
}
if (callbackId) {
UniViewJSBridge.publishHandler('onEditorMethodCallback', {
callbackId,
data: Object.assign({}, res, {
errMsg: `${type}:${errMsg ? 'fail ' + errMsg : 'ok'}`
})
}, this.$page.id)
}
},
loadQuill (callback) {
if (typeof Quill === 'function') {
if (typeof callback === 'function') {
callback()
}
return
}
let script = document.createElement('script')
script.src = 'https://unpkg.com/quill@1.3.6/dist/quill.min.js'
document.body.appendChild(script)
script.onload = callback
},
loadImageResizeModule (callback) {
if (typeof ImageResize === 'function') {
if (typeof callback === 'function') {
callback()
}
return
}
let script = document.createElement('script')
script.src = 'https://unpkg.com/quill-image-resize-mp@3.0.0/image-resize.min.js'
document.body.appendChild(script)
script.onload = callback
},
initQuill (imageResizeModules) {
const quill = this.quill = new Quill(this.$el, {
toolbar: false,
readOnly: this.readOnly,
placeholder: this.placeholder,
modules: {
ImageResize: {
modules: imageResizeModules
}
}
})
formats.register(Quill)
const $el = quill.root
const events = ['focus', 'blur']
events.forEach(name => {
$el.addEventListener(name, ($event) => {
this.$trigger(name, $event, this.getContents())
})
})
quill.on(Quill.events.TEXT_CHANGE, () => {
this.$trigger('input', {}, this.getContents())
})
quill.clipboard.addMatcher(Node.ELEMENT_NODE, (node, delta) => {
if (this.skipMatcher) {
return delta
}
return {
ops: delta.ops.filter(({ insert }) => typeof insert === 'string').map(({ insert }) => ({ insert }))
}
})
this.initKeyboard($el)
this.quillReady = true
this.$trigger('ready', event, {})
},
getContents () {
const quill = this.quill
const html = quill.root.innerHTML
const text = quill.getText()
const delta = quill.getContents()
return {
html,
text,
delta
}
},
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']
let content = ''
let disable
HTMLParser(html, {
start: function (tag, attrs, unary) {
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) {
if (!disable) {
content += `</${tag}>`
}
},
chars: function (text) {
if (!disable) {
content += text
}
}
})
this.skipMatcher = true
const delta = this.quill.clipboard.convert(content)
this.skipMatcher = false
return delta
}
}
}
</script>
<style src="./editor.css"></style>
<style>
</style>
let id = 0
const callbacks = {}
function warp (fn) {
return function (options = {}) {
const callbackId = String(id++)
callbacks[callbackId] = {
success: options.success,
fail: options.fail,
complete: options.complete
}
const data = Object.assign({}, options)
delete data.success
delete data.fail
delete data.complete
const res = fn.bind(this)(data, callbackId)
if (res) {
invoke(callbackId, res)
}
}
}
function invoke (callbackId, res) {
const callback = callbacks[callbackId] || {}
delete callbacks[callbackId]
const errMsg = res.errMsg || ''
if (new RegExp('\\:\\s*fail').test(errMsg)) {
callback.fail && callback.fail(res)
} else {
callback.success && callback.success(res)
}
callback.complete && callback.complete(res)
}
export const callback = {
warp,
invoke
}
......@@ -4,3 +4,4 @@ export * from './color'
export * from './query'
export * from './scroll'
export * from './platform'
export * from './callback'
......@@ -21,7 +21,7 @@ export function hasOwn (obj, key) {
return hasOwnProperty.call(obj, key)
}
export function noop () {}
export function noop () { }
export function toRawType (val) {
return _toString.call(val).slice(8, -1)
......@@ -87,4 +87,8 @@ export function debounce (fn, delay) {
const timerFn = () => fn.apply(this, arguments)
timeout = setTimeout(timerFn, delay)
}
}
}
export function kebabCase (string) {
return string.replace(/[A-Z]/g, str => '-' + str.toLowerCase())
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册