From 517afa30a0d4ad77171d68cf15ec823f3b30c46e Mon Sep 17 00:00:00 2001 From: qiang Date: Mon, 2 Dec 2019 21:17:05 +0800 Subject: [PATCH] feat: add editor fix: editor format fix: editor clipboard fix: editor convert fix: editor html2delta fix: editor imgToolbar --- lib/apis.js | 15 +- lib/modules.json | 7 +- packages/uni-cli-shared/lib/tags.js | 5 +- src/core/helpers/tags.js | 1 + src/core/service/api/context/editor.js | 59 +++ src/core/view/components/editor/editor.css | 395 ++++++++++++++++++ .../view/components/editor/formats/align.js | 11 + .../components/editor/formats/background.js | 10 + .../view/components/editor/formats/box.js | 16 + .../components/editor/formats/direction.js | 11 + .../view/components/editor/formats/divider.js | 9 + .../view/components/editor/formats/font.js | 15 + .../view/components/editor/formats/index.js | 26 ++ .../view/components/editor/formats/ins.js | 9 + .../view/components/editor/formats/list.js | 95 +++++ .../view/components/editor/formats/text.js | 26 ++ src/core/view/components/editor/index.vue | 312 ++++++++++++++ src/shared/callback.js | 38 ++ src/shared/index.js | 1 + src/shared/util.js | 8 +- 20 files changed, 1055 insertions(+), 14 deletions(-) create mode 100644 src/core/service/api/context/editor.js create mode 100644 src/core/view/components/editor/editor.css create mode 100644 src/core/view/components/editor/formats/align.js create mode 100644 src/core/view/components/editor/formats/background.js create mode 100644 src/core/view/components/editor/formats/box.js create mode 100644 src/core/view/components/editor/formats/direction.js create mode 100644 src/core/view/components/editor/formats/divider.js create mode 100644 src/core/view/components/editor/formats/font.js create mode 100644 src/core/view/components/editor/formats/index.js create mode 100644 src/core/view/components/editor/formats/ins.js create mode 100644 src/core/view/components/editor/formats/list.js create mode 100644 src/core/view/components/editor/formats/text.js create mode 100644 src/core/view/components/editor/index.vue create mode 100644 src/shared/callback.js diff --git a/lib/apis.js b/lib/apis.js index fb84e01d..1986540b 100644 --- a/lib/apis.js +++ b/lib/apis.js @@ -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' ] diff --git a/lib/modules.json b/lib/modules.json index 2ec88deb..b73f8042 100644 --- a/lib/modules.json +++ b/lib/modules.json @@ -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 } -}] +}] diff --git a/packages/uni-cli-shared/lib/tags.js b/packages/uni-cli-shared/lib/tags.js index 6b5c51f3..e0f54554 100644 --- a/packages/uni-cli-shared/lib/tags.js +++ b/packages/uni-cli-shared/lib/tags.js @@ -1,4 +1,4 @@ -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'] -} +} diff --git a/src/core/helpers/tags.js b/src/core/helpers/tags.js index 42b16a8c..3dda9810 100644 --- a/src/core/helpers/tags.js +++ b/src/core/helpers/tags.js @@ -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', diff --git a/src/core/service/api/context/editor.js b/src/core/service/api/context/editor.js new file mode 100644 index 00000000..430bde5f --- /dev/null +++ b/src/core/service/api/context/editor.js @@ -0,0 +1,59 @@ +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') + } +} diff --git a/src/core/view/components/editor/editor.css b/src/core/view/components/editor/editor.css new file mode 100644 index 00000000..0145223b --- /dev/null +++ b/src/core/view/components/editor/editor.css @@ -0,0 +1,395 @@ +.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/src/core/view/components/editor/formats/align.js b/src/core/view/components/editor/formats/align.js new file mode 100644 index 00000000..602a3ada --- /dev/null +++ b/src/core/view/components/editor/formats/align.js @@ -0,0 +1,11 @@ +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 + } +} diff --git a/src/core/view/components/editor/formats/background.js b/src/core/view/components/editor/formats/background.js new file mode 100644 index 00000000..687ffdf8 --- /dev/null +++ b/src/core/view/components/editor/formats/background.js @@ -0,0 +1,10 @@ +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 + } +} diff --git a/src/core/view/components/editor/formats/box.js b/src/core/view/components/editor/formats/box.js new file mode 100644 index 00000000..deab3c84 --- /dev/null +++ b/src/core/view/components/editor/formats/box.js @@ -0,0 +1,16 @@ +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 +} diff --git a/src/core/view/components/editor/formats/direction.js b/src/core/view/components/editor/formats/direction.js new file mode 100644 index 00000000..3cb4afd2 --- /dev/null +++ b/src/core/view/components/editor/formats/direction.js @@ -0,0 +1,11 @@ +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 + } +} diff --git a/src/core/view/components/editor/formats/divider.js b/src/core/view/components/editor/formats/divider.js new file mode 100644 index 00000000..c7753aad --- /dev/null +++ b/src/core/view/components/editor/formats/divider.js @@ -0,0 +1,9 @@ +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 + } +} diff --git a/src/core/view/components/editor/formats/font.js b/src/core/view/components/editor/formats/font.js new file mode 100644 index 00000000..91635b8f --- /dev/null +++ b/src/core/view/components/editor/formats/font.js @@ -0,0 +1,15 @@ +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 +} diff --git a/src/core/view/components/editor/formats/index.js b/src/core/view/components/editor/formats/index.js new file mode 100644 index 00000000..02225304 --- /dev/null +++ b/src/core/view/components/editor/formats/index.js @@ -0,0 +1,26 @@ +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) +} diff --git a/src/core/view/components/editor/formats/ins.js b/src/core/view/components/editor/formats/ins.js new file mode 100644 index 00000000..5d2ee391 --- /dev/null +++ b/src/core/view/components/editor/formats/ins.js @@ -0,0 +1,9 @@ +export default function (Quill) { + const Inline = Quill.import('blots/inline') + class Ins extends Inline { } + Ins.blotName = 'ins' + Ins.tagName = 'INS' + return { + 'formats/ins': Ins + } +} diff --git a/src/core/view/components/editor/formats/list.js b/src/core/view/components/editor/formats/list.js new file mode 100644 index 00000000..c051d6be --- /dev/null +++ b/src/core/view/components/editor/formats/list.js @@ -0,0 +1,95 @@ +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 + } +} diff --git a/src/core/view/components/editor/formats/text.js b/src/core/view/components/editor/formats/text.js new file mode 100644 index 00000000..d9336c82 --- /dev/null +++ b/src/core/view/components/editor/formats/text.js @@ -0,0 +1,26 @@ +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 +} diff --git a/src/core/view/components/editor/index.vue b/src/core/view/components/editor/index.vue new file mode 100644 index 00000000..b352be04 --- /dev/null +++ b/src/core/view/components/editor/index.vue @@ -0,0 +1,312 @@ + + + + + + diff --git a/src/shared/callback.js b/src/shared/callback.js new file mode 100644 index 00000000..6f141cab --- /dev/null +++ b/src/shared/callback.js @@ -0,0 +1,38 @@ +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 +} diff --git a/src/shared/index.js b/src/shared/index.js index 46ca7320..08626b82 100644 --- a/src/shared/index.js +++ b/src/shared/index.js @@ -4,3 +4,4 @@ export * from './color' export * from './query' export * from './scroll' export * from './platform' +export * from './callback' diff --git a/src/shared/util.js b/src/shared/util.js index 9ba02462..e820cda1 100644 --- a/src/shared/util.js +++ b/src/shared/util.js @@ -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()) +} -- GitLab