From a38dfa7809b267aa1c55b31ec8f00d856a67423e Mon Sep 17 00:00:00 2001 From: yanmao <55792257+yanmao-cc@users.noreply.github.com> Date: Sat, 15 Jan 2022 21:04:42 +0800 Subject: [PATCH] update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - video 插件增加 fullEditor 选项,初始化宽度与编辑器宽度对齐 - paste 优化粘贴嵌套的mark按优先级显示 --- examples/react/components/editor/config.tsx | 13 ++++++- packages/engine/src/change/native-event.ts | 2 +- packages/engine/src/change/paste.ts | 41 +++++++++++++++++++- packages/engine/src/inline/index.ts | 24 +++++++----- packages/engine/src/node/entry.ts | 18 ++++----- packages/engine/src/node/index.ts | 7 ++-- packages/engine/src/parser/index.ts | 43 ++++++++++++++++++--- packages/engine/src/schema.ts | 7 ++-- packages/engine/src/types/inline.ts | 5 ++- packages/engine/src/utils/string.ts | 15 ++++--- plugins/backcolor/src/index.ts | 2 + plugins/video/src/component/index.ts | 13 +++++++ plugins/video/src/types.ts | 7 ++++ 13 files changed, 153 insertions(+), 44 deletions(-) diff --git a/examples/react/components/editor/config.tsx b/examples/react/components/editor/config.tsx index b813b031..0b68b6ca 100644 --- a/examples/react/components/editor/config.tsx +++ b/examples/react/components/editor/config.tsx @@ -1,4 +1,4 @@ -import { $ } from '@aomao/engine'; +import { $, removeUnit } from '@aomao/engine'; import type { PluginEntry, CardEntry, @@ -260,6 +260,17 @@ export const mentionOptions: MentionOptions = { export const fontsizeOptions: FontsizeOptions = { //配置粘贴后需要过滤的字体大小 filter: (fontSize: string) => { + const size = removeUnit(fontSize); + if (size > 48) { + return '48px'; + } else if (size < 12) return '12px'; + else if (size < 19 && size > 16) return '16px'; + else if (size < 22 && size > 19) return '19px'; + else if (size < 24 && size > 22) return '22px'; + else if (size < 29 && size > 24) return '24px'; + else if (size < 32 && size > 29) return '29px'; + else if (size < 40 && size > 32) return '32px'; + else if (size < 48 && size > 40) return '40px'; return ( [ '12px', diff --git a/packages/engine/src/change/native-event.ts b/packages/engine/src/change/native-event.ts index dc94b92d..43530de1 100644 --- a/packages/engine/src/change/native-event.ts +++ b/packages/engine/src/change/native-event.ts @@ -219,7 +219,7 @@ class NativeEvent { textNode.remove(); if (node.isEmpty(parent)) parent.remove(); mark.unwrap(markTops.map((mark) => mark.clone())); - node.insertText(event.data); + node.insertText(event.data === '' ? '\xa0' : event.data); mark.merge(); range = change.range.get().cloneRange().shrinkToTextNode(); startNode = range.startNode; diff --git a/packages/engine/src/change/paste.ts b/packages/engine/src/change/paste.ts index b386c18e..db6b2bae 100644 --- a/packages/engine/src/change/paste.ts +++ b/packages/engine/src/change/paste.ts @@ -42,6 +42,7 @@ export default class Paste { const defautlStyleKeys = Object.keys(defaultStyle); const { inline } = this.engine; const nodeApi = this.engine.node; + const markApi = this.engine.mark; $(fragment).traverse((node) => { let parent = node.parent(); @@ -415,7 +416,45 @@ export default class Paste { nodeApi.isMark(nodeParent, this.schema) && nodeApi.isMark(node, this.schema) ) { - if (this.engine.mark.compare(nodeParent.clone(), node, true)) { + const pMarkPlugin = markApi.findPlugin(nodeParent); + const cMarkPlugin = markApi.findPlugin(node); + if ( + pMarkPlugin && + cMarkPlugin && + cMarkPlugin.mergeLeval > pMarkPlugin.mergeLeval + ) { + const cloneParent = nodeParent.clone(false); + const childrenNodes = nodeParent.children().toArray(); + const startP = cloneParent.clone(); + const endP = cloneParent.clone(); + let isStart = true; + let index = -1; + childrenNodes.forEach((children, i) => { + if (children.equal(node)) { + const nChildren = node.children(); + cloneParent.append(nChildren); + node.append(cloneParent); + isStart = false; + index = i; + } else if (isStart) { + startP.append(children); + } else { + endP.append(children); + } + }); + if (index > 0) { + nodeParent.before(startP); + } + if (index < childrenNodes.length - 1) { + nodeParent.after(endP); + } + if (index > -1) { + nodeParent.before(node); + nodeParent.remove(); + return node; + } + } + if (markApi.compare(nodeParent.clone(), node, true)) { nodeApi.unwrap(node); break; } else { diff --git a/packages/engine/src/inline/index.ts b/packages/engine/src/inline/index.ts index 8350b980..9cc604c6 100644 --- a/packages/engine/src/inline/index.ts +++ b/packages/engine/src/inline/index.ts @@ -1087,7 +1087,7 @@ class Inline implements InlineModelInterface { let parentMark: NodeInterface | undefined = markApi.closest(node); - const element = node as NodeInterface; + let element = node as NodeInterface; while ( parentMark && !parentMark.equal(node) && @@ -1096,32 +1096,36 @@ class Inline implements InlineModelInterface { const cloneMark = parentMark.clone(); const cloneInline = node.clone(); const children = parentMark.children(); - children.each((markChild, index) => { + children.each((child) => { // 零宽字符的文本跳过 if ( - markChild.nodeType === 3 && - /^\u200b$/.test(markChild.textContent || '') + child.nodeType === 3 && + /^\u200b$/.test(child.textContent || '') ) { return; } + + const childNode = $(child); if ( - element.equal(markChild) || - children.eq(index)?.contains(element) + element.equal(childNode) || + childNode.contains(element) ) { - node = nodeApi.wrap( + element = nodeApi.wrap( nodeApi.replace(element, cloneMark), cloneInline, ); - this.repairBoth(node); + this.repairBoth(element); } else { - nodeApi.wrap(markChild, cloneMark); + nodeApi.wrap(childNode, cloneMark); } }); nodeApi.unwrap(parentMark); - parentMark = markApi.closest(node); + parentMark = markApi.closest(element); } + return element; } } + return; } } diff --git a/packages/engine/src/node/entry.ts b/packages/engine/src/node/entry.ts index 0ddd1d92..2b0c8542 100644 --- a/packages/engine/src/node/entry.ts +++ b/packages/engine/src/node/entry.ts @@ -58,12 +58,13 @@ class NodeEntry implements NodeInterface { }); this.length = nodes.length; - - if (this[0]) { + const baseNode = this[0]; + if (baseNode) { this.document = getDocument(context); this.context = context; - this.name = this[0].nodeName.toLowerCase(); - this.type = this[0].nodeType; + const { nodeName, nodeType } = baseNode; + this.name = nodeName.toLowerCase(); + this.type = nodeType; this.window = this.document.defaultView || window; } } @@ -542,14 +543,13 @@ class NodeEntry implements NodeInterface { const element = this.get(); if (!element) return {}; const attrs = {}; - const elementAttributes = element.attributes; - if (!elementAttributes || elementAttributes.length === 0) - return attrs; + const elementAttributes = element.attributes || []; + let i = 0, item = null; while ((item = elementAttributes[i])) { - const { name, value } = item; - attrs[name] = value; + // const { name, value } = item; + attrs[item.name] = item.value; i++; } return attrs; diff --git a/packages/engine/src/node/index.ts b/packages/engine/src/node/index.ts index 195df7ce..ecfaf110 100644 --- a/packages/engine/src/node/index.ts +++ b/packages/engine/src/node/index.ts @@ -39,9 +39,10 @@ class NodeModel implements NodeModelInterface { let name = typeof node === 'string' ? node : ''; if (isNode(node)) name = node.nodeName.toLowerCase(); else if (isNodeEntry(node)) name = node.name; - return schema - .find((rule) => rule.name === name) - .some((rule) => rule.isVoid); + return ( + schema.find((rule) => rule.name === name && rule.isVoid === true) + .length > 0 + ); } isMark( diff --git a/packages/engine/src/parser/index.ts b/packages/engine/src/parser/index.ts index f1c28aec..f886a2fb 100644 --- a/packages/engine/src/parser/index.ts +++ b/packages/engine/src/parser/index.ts @@ -181,6 +181,8 @@ class Parser implements ParserInterface { Object.keys(style).length === 0 ) return; + const attrCount = Object.keys(attributes).length; + const styleCount = Object.keys(style).length; //过滤不符合当前节点规则的属性样式 schema.filter(node, attributes, style); //复制一个节点 @@ -206,8 +208,8 @@ class Parser implements ParserInterface { }); // 如果这个节点过滤掉所有属性样式后还是一个有效的节点就替换掉当前节点 if ( - filterAttrCount === attrKeys.length && - filterStyleCount === styleKeys.length && + filterAttrCount === attrCount && + filterStyleCount === styleCount && schema.getType(newNode) === type ) { node.before(newNode); @@ -261,16 +263,34 @@ class Parser implements ParserInterface { rule.type === 'mark' && oldRules.indexOf(rule) < 0, ); + if (!type) { + if (conversion) { + this.convert(conversion, newNode, schema); + const newChildren = newNode.children(); + if (newChildren.length > 0) { + const children = node.children(); + newChildren.append( + children.length > 0 + ? children + : $('\u200b', null), + ); + node.append(newChildren); + return; + } + } + } //如果是mark节点,使用新节点包裹旧节点子节点 + let tempNode = node; while (type === 'mark') { - const children = node.children(); + const children = tempNode.children(); newNode.append( children.length > 0 ? children : $('\u200b', null), ); - node.append(newNode); + tempNode.append(newNode); + tempNode = newNode; newNode = filter(newNode); if (!newNode) break; //获取这个新的节点所属类型,并且不能是之前节点一样的规则 @@ -281,7 +301,18 @@ class Parser implements ParserInterface { rule.type === 'mark' && oldRules.indexOf(rule) < 0, ); - if (!type) break; + if (!type) { + if (conversion) { + this.convert(conversion, newNode, schema); + const newChildren = newNode.children(); + if (newChildren.length > 0) { + newNode = newChildren; + type = 'mark'; + continue; + } + } + break; + } rule = schema.getRule(newNode); if (!rule) break; oldRules.push(rule); @@ -289,7 +320,7 @@ class Parser implements ParserInterface { } } else if (nodeApi.isInline(node)) { //当前节点是 inline 节点,inline 节点不允许嵌套、不允许放入mark节点 - inlineApi.flat(node, schema); + return inlineApi.flat(node, schema); } } }); diff --git a/packages/engine/src/schema.ts b/packages/engine/src/schema.ts index f95aa423..92d4203b 100644 --- a/packages/engine/src/schema.ts +++ b/packages/engine/src/schema.ts @@ -1,4 +1,4 @@ -import { cloneDeep, isEqual, merge, omit } from 'lodash'; +import { assign, cloneDeep, isEqual, merge, omit } from 'lodash'; import { NodeInterface, SchemaAttributes, @@ -370,14 +370,13 @@ class Schema implements SchemaInterface { if (!rule) return; const { globals } = this.data; const globalRule = globals[rule.type] ? rule.type : undefined; - const allRule = { - ...omit(rule, 'attributes'), + const allRule = Object.assign({}, rule, { attributes: merge( {}, rule.attributes, globalRule ? globals[globalRule] : {}, ), - }; + }); this.filterAttributes(attributes, allRule); this.filterStyles(styles, allRule); } diff --git a/packages/engine/src/types/inline.ts b/packages/engine/src/types/inline.ts index 0b5638e5..e47583fb 100644 --- a/packages/engine/src/types/inline.ts +++ b/packages/engine/src/types/inline.ts @@ -61,7 +61,10 @@ export interface InlineModelInterface { * 标准化inline节点,不能嵌套在mark标签内,不能嵌套inline标签 * @param node */ - flat(node: NodeInterface | RangeInterface, schema?: SchemaInterface): void; + flat( + node: NodeInterface | RangeInterface, + schema?: SchemaInterface, + ): void | NodeInterface; } export interface InlineInterface diff --git a/packages/engine/src/utils/string.ts b/packages/engine/src/utils/string.ts index f7713548..3dca9a22 100644 --- a/packages/engine/src/utils/string.ts +++ b/packages/engine/src/utils/string.ts @@ -7,7 +7,6 @@ import { } from '../constants/card'; import { DATA_ELEMENT } from '../constants/root'; import { isMacos } from './user-agent'; -import md5 from 'blueimp-md5'; /** * 随机字符串 @@ -96,17 +95,17 @@ export const getAttrMap = (value: string): { [k: string]: string } => { return map; }; -const stylesCaches: Record> = {}; +const stylesCaches: Map> = new Map(); /** * 将 style 样式转换为 map 数据类型 * @param {string} style */ export const getStyleMap = (style: string): Record => { - const key = md5(style); + const key = style.replace(/\s+/g, ''); const map: Record = {}; - if (!key) return { ...map }; - const cacheStyle = stylesCaches[key]; - if (cacheStyle) return { ...cacheStyle }; + if (!key) return map; + const cacheStyle = stylesCaches.get(key); + if (cacheStyle) return Object.assign({}, cacheStyle); const reg = /\s*([\w\-]+)\s*:([^;]*)(;|$)/g; let match; @@ -118,8 +117,8 @@ export const getStyleMap = (style: string): Record => { } map[key] = val; } - stylesCaches[key] = map; - return { ...map }; + stylesCaches.set(key, map); + return Object.assign({}, map); }; /** diff --git a/plugins/backcolor/src/index.ts b/plugins/backcolor/src/index.ts index 9609e278..0fb7d012 100644 --- a/plugins/backcolor/src/index.ts +++ b/plugins/backcolor/src/index.ts @@ -12,6 +12,7 @@ export default class extends MarkPlugin { style = { 'background-color': '@var0', + background: '@var1', }; variable = { @@ -19,6 +20,7 @@ export default class extends MarkPlugin { required: true, value: '@color', }, + '@var1': '@color', }; conversion(): ConversionData { diff --git a/plugins/video/src/component/index.ts b/plugins/video/src/component/index.ts index 0a2951b7..efb2baea 100644 --- a/plugins/video/src/component/index.ts +++ b/plugins/video/src/component/index.ts @@ -225,6 +225,9 @@ class VideoComponent extends Card { return false; }; video.onloadedmetadata = () => { + const videoPlugin = + this.editor.plugin.findPlugin('video'); + const fullEditor = videoPlugin?.options.fullEditor; if (!value.naturalWidth) { value.naturalWidth = video.videoWidth || this.video?.width(); this.setValue({ @@ -236,6 +239,16 @@ class VideoComponent extends Card { (video.videoHeight || this.video?.height() || 1) / value.naturalWidth; } + if (fullEditor && value.naturalWidth) { + const fullWidth = this.editor.container.width(); + if (value.naturalWidth < fullWidth) { + const fullHeight = fullWidth * this.rate; + this.setValue({ + naturalWidth: fullWidth, + naturalHeight: fullHeight, + } as T); + } + } this.resetSize(); }; this.video = $(video); diff --git a/plugins/video/src/types.ts b/plugins/video/src/types.ts index 9bffb5c9..5840fc25 100644 --- a/plugins/video/src/types.ts +++ b/plugins/video/src/types.ts @@ -5,5 +5,12 @@ export interface VideoOptions extends PluginOptions { action: 'download' | 'query' | 'cover', url: string, ) => string; + /** + * 是否显示标题 + */ showTitle?: boolean; + /** + * 填满编辑器宽度 + */ + fullEditor?: boolean; }