- video 插件增加 fullEditor 选项,初始化宽度与编辑器宽度对齐
- paste 优化粘贴嵌套的mark按优先级显示
This commit is contained in:
yanmao 2022-01-15 21:04:42 +08:00
parent 635aafe156
commit a38dfa7809
13 changed files with 153 additions and 44 deletions

View File

@ -1,4 +1,4 @@
import { $ } from '@aomao/engine'; import { $, removeUnit } from '@aomao/engine';
import type { import type {
PluginEntry, PluginEntry,
CardEntry, CardEntry,
@ -260,6 +260,17 @@ export const mentionOptions: MentionOptions = {
export const fontsizeOptions: FontsizeOptions = { export const fontsizeOptions: FontsizeOptions = {
//配置粘贴后需要过滤的字体大小 //配置粘贴后需要过滤的字体大小
filter: (fontSize: string) => { 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 ( return (
[ [
'12px', '12px',

View File

@ -219,7 +219,7 @@ class NativeEvent {
textNode.remove(); textNode.remove();
if (node.isEmpty(parent)) parent.remove(); if (node.isEmpty(parent)) parent.remove();
mark.unwrap(markTops.map((mark) => mark.clone())); mark.unwrap(markTops.map((mark) => mark.clone()));
node.insertText(event.data); node.insertText(event.data === '' ? '\xa0' : event.data);
mark.merge(); mark.merge();
range = change.range.get().cloneRange().shrinkToTextNode(); range = change.range.get().cloneRange().shrinkToTextNode();
startNode = range.startNode; startNode = range.startNode;

View File

@ -42,6 +42,7 @@ export default class Paste {
const defautlStyleKeys = Object.keys(defaultStyle); const defautlStyleKeys = Object.keys(defaultStyle);
const { inline } = this.engine; const { inline } = this.engine;
const nodeApi = this.engine.node; const nodeApi = this.engine.node;
const markApi = this.engine.mark;
$(fragment).traverse((node) => { $(fragment).traverse((node) => {
let parent = node.parent(); let parent = node.parent();
@ -415,7 +416,45 @@ export default class Paste {
nodeApi.isMark(nodeParent, this.schema) && nodeApi.isMark(nodeParent, this.schema) &&
nodeApi.isMark(node, 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); nodeApi.unwrap(node);
break; break;
} else { } else {

View File

@ -1087,7 +1087,7 @@ class Inline implements InlineModelInterface {
let parentMark: NodeInterface | undefined = let parentMark: NodeInterface | undefined =
markApi.closest(node); markApi.closest(node);
const element = node as NodeInterface; let element = node as NodeInterface;
while ( while (
parentMark && parentMark &&
!parentMark.equal(node) && !parentMark.equal(node) &&
@ -1096,32 +1096,36 @@ class Inline implements InlineModelInterface {
const cloneMark = parentMark.clone(); const cloneMark = parentMark.clone();
const cloneInline = node.clone(); const cloneInline = node.clone();
const children = parentMark.children(); const children = parentMark.children();
children.each((markChild, index) => { children.each((child) => {
// 零宽字符的文本跳过 // 零宽字符的文本跳过
if ( if (
markChild.nodeType === 3 && child.nodeType === 3 &&
/^\u200b$/.test(markChild.textContent || '') /^\u200b$/.test(child.textContent || '')
) { ) {
return; return;
} }
const childNode = $(child);
if ( if (
element.equal(markChild) || element.equal(childNode) ||
children.eq(index)?.contains(element) childNode.contains(element)
) { ) {
node = nodeApi.wrap( element = nodeApi.wrap(
nodeApi.replace(element, cloneMark), nodeApi.replace(element, cloneMark),
cloneInline, cloneInline,
); );
this.repairBoth(node); this.repairBoth(element);
} else { } else {
nodeApi.wrap(markChild, cloneMark); nodeApi.wrap(childNode, cloneMark);
} }
}); });
nodeApi.unwrap(parentMark); nodeApi.unwrap(parentMark);
parentMark = markApi.closest(node); parentMark = markApi.closest(element);
} }
return element;
} }
} }
return;
} }
} }

View File

@ -58,12 +58,13 @@ class NodeEntry implements NodeInterface {
}); });
this.length = nodes.length; this.length = nodes.length;
const baseNode = this[0];
if (this[0]) { if (baseNode) {
this.document = getDocument(context); this.document = getDocument(context);
this.context = context; this.context = context;
this.name = this[0].nodeName.toLowerCase(); const { nodeName, nodeType } = baseNode;
this.type = this[0].nodeType; this.name = nodeName.toLowerCase();
this.type = nodeType;
this.window = this.document.defaultView || window; this.window = this.document.defaultView || window;
} }
} }
@ -542,14 +543,13 @@ class NodeEntry implements NodeInterface {
const element = this.get<Element>(); const element = this.get<Element>();
if (!element) return {}; if (!element) return {};
const attrs = {}; const attrs = {};
const elementAttributes = element.attributes; const elementAttributes = element.attributes || [];
if (!elementAttributes || elementAttributes.length === 0)
return attrs;
let i = 0, let i = 0,
item = null; item = null;
while ((item = elementAttributes[i])) { while ((item = elementAttributes[i])) {
const { name, value } = item; // const { name, value } = item;
attrs[name] = value; attrs[item.name] = item.value;
i++; i++;
} }
return attrs; return attrs;

View File

@ -39,9 +39,10 @@ class NodeModel implements NodeModelInterface {
let name = typeof node === 'string' ? node : ''; let name = typeof node === 'string' ? node : '';
if (isNode(node)) name = node.nodeName.toLowerCase(); if (isNode(node)) name = node.nodeName.toLowerCase();
else if (isNodeEntry(node)) name = node.name; else if (isNodeEntry(node)) name = node.name;
return schema return (
.find((rule) => rule.name === name) schema.find((rule) => rule.name === name && rule.isVoid === true)
.some((rule) => rule.isVoid); .length > 0
);
} }
isMark( isMark(

View File

@ -181,6 +181,8 @@ class Parser implements ParserInterface {
Object.keys(style).length === 0 Object.keys(style).length === 0
) )
return; return;
const attrCount = Object.keys(attributes).length;
const styleCount = Object.keys(style).length;
//过滤不符合当前节点规则的属性样式 //过滤不符合当前节点规则的属性样式
schema.filter(node, attributes, style); schema.filter(node, attributes, style);
//复制一个节点 //复制一个节点
@ -206,8 +208,8 @@ class Parser implements ParserInterface {
}); });
// 如果这个节点过滤掉所有属性样式后还是一个有效的节点就替换掉当前节点 // 如果这个节点过滤掉所有属性样式后还是一个有效的节点就替换掉当前节点
if ( if (
filterAttrCount === attrKeys.length && filterAttrCount === attrCount &&
filterStyleCount === styleKeys.length && filterStyleCount === styleCount &&
schema.getType(newNode) === type schema.getType(newNode) === type
) { ) {
node.before(newNode); node.before(newNode);
@ -261,16 +263,34 @@ class Parser implements ParserInterface {
rule.type === 'mark' && rule.type === 'mark' &&
oldRules.indexOf(rule) < 0, 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节点使用新节点包裹旧节点子节点 //如果是mark节点使用新节点包裹旧节点子节点
let tempNode = node;
while (type === 'mark') { while (type === 'mark') {
const children = node.children(); const children = tempNode.children();
newNode.append( newNode.append(
children.length > 0 children.length > 0
? children ? children
: $('\u200b', null), : $('\u200b', null),
); );
node.append(newNode); tempNode.append(newNode);
tempNode = newNode;
newNode = filter(newNode); newNode = filter(newNode);
if (!newNode) break; if (!newNode) break;
//获取这个新的节点所属类型,并且不能是之前节点一样的规则 //获取这个新的节点所属类型,并且不能是之前节点一样的规则
@ -281,7 +301,18 @@ class Parser implements ParserInterface {
rule.type === 'mark' && rule.type === 'mark' &&
oldRules.indexOf(rule) < 0, 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); rule = schema.getRule(newNode);
if (!rule) break; if (!rule) break;
oldRules.push(rule); oldRules.push(rule);
@ -289,7 +320,7 @@ class Parser implements ParserInterface {
} }
} else if (nodeApi.isInline(node)) { } else if (nodeApi.isInline(node)) {
//当前节点是 inline 节点inline 节点不允许嵌套、不允许放入mark节点 //当前节点是 inline 节点inline 节点不允许嵌套、不允许放入mark节点
inlineApi.flat(node, schema); return inlineApi.flat(node, schema);
} }
} }
}); });

View File

@ -1,4 +1,4 @@
import { cloneDeep, isEqual, merge, omit } from 'lodash'; import { assign, cloneDeep, isEqual, merge, omit } from 'lodash';
import { import {
NodeInterface, NodeInterface,
SchemaAttributes, SchemaAttributes,
@ -370,14 +370,13 @@ class Schema implements SchemaInterface {
if (!rule) return; if (!rule) return;
const { globals } = this.data; const { globals } = this.data;
const globalRule = globals[rule.type] ? rule.type : undefined; const globalRule = globals[rule.type] ? rule.type : undefined;
const allRule = { const allRule = Object.assign({}, rule, {
...omit(rule, 'attributes'),
attributes: merge( attributes: merge(
{}, {},
rule.attributes, rule.attributes,
globalRule ? globals[globalRule] : {}, globalRule ? globals[globalRule] : {},
), ),
}; });
this.filterAttributes(attributes, allRule); this.filterAttributes(attributes, allRule);
this.filterStyles(styles, allRule); this.filterStyles(styles, allRule);
} }

View File

@ -61,7 +61,10 @@ export interface InlineModelInterface {
* inline节点mark标签内inline标签 * inline节点mark标签内inline标签
* @param node * @param node
*/ */
flat(node: NodeInterface | RangeInterface, schema?: SchemaInterface): void; flat(
node: NodeInterface | RangeInterface,
schema?: SchemaInterface,
): void | NodeInterface;
} }
export interface InlineInterface<T extends PluginOptions = PluginOptions> export interface InlineInterface<T extends PluginOptions = PluginOptions>

View File

@ -7,7 +7,6 @@ import {
} from '../constants/card'; } from '../constants/card';
import { DATA_ELEMENT } from '../constants/root'; import { DATA_ELEMENT } from '../constants/root';
import { isMacos } from './user-agent'; import { isMacos } from './user-agent';
import md5 from 'blueimp-md5';
/** /**
* *
@ -96,17 +95,17 @@ export const getAttrMap = (value: string): { [k: string]: string } => {
return map; return map;
}; };
const stylesCaches: Record<string, Record<string, string>> = {}; const stylesCaches: Map<string, Record<string, string>> = new Map();
/** /**
* style map * style map
* @param {string} style * @param {string} style
*/ */
export const getStyleMap = (style: string): Record<string, string> => { export const getStyleMap = (style: string): Record<string, string> => {
const key = md5(style); const key = style.replace(/\s+/g, '');
const map: Record<string, string> = {}; const map: Record<string, string> = {};
if (!key) return { ...map }; if (!key) return map;
const cacheStyle = stylesCaches[key]; const cacheStyle = stylesCaches.get(key);
if (cacheStyle) return { ...cacheStyle }; if (cacheStyle) return Object.assign({}, cacheStyle);
const reg = /\s*([\w\-]+)\s*:([^;]*)(;|$)/g; const reg = /\s*([\w\-]+)\s*:([^;]*)(;|$)/g;
let match; let match;
@ -118,8 +117,8 @@ export const getStyleMap = (style: string): Record<string, string> => {
} }
map[key] = val; map[key] = val;
} }
stylesCaches[key] = map; stylesCaches.set(key, map);
return { ...map }; return Object.assign({}, map);
}; };
/** /**

View File

@ -12,6 +12,7 @@ export default class<T extends BackcolorOptions> extends MarkPlugin<T> {
style = { style = {
'background-color': '@var0', 'background-color': '@var0',
background: '@var1',
}; };
variable = { variable = {
@ -19,6 +20,7 @@ export default class<T extends BackcolorOptions> extends MarkPlugin<T> {
required: true, required: true,
value: '@color', value: '@color',
}, },
'@var1': '@color',
}; };
conversion(): ConversionData { conversion(): ConversionData {

View File

@ -225,6 +225,9 @@ class VideoComponent<T extends VideoValue = VideoValue> extends Card<T> {
return false; return false;
}; };
video.onloadedmetadata = () => { video.onloadedmetadata = () => {
const videoPlugin =
this.editor.plugin.findPlugin<VideoOptions>('video');
const fullEditor = videoPlugin?.options.fullEditor;
if (!value.naturalWidth) { if (!value.naturalWidth) {
value.naturalWidth = video.videoWidth || this.video?.width(); value.naturalWidth = video.videoWidth || this.video?.width();
this.setValue({ this.setValue({
@ -236,6 +239,16 @@ class VideoComponent<T extends VideoValue = VideoValue> extends Card<T> {
(video.videoHeight || this.video?.height() || 1) / (video.videoHeight || this.video?.height() || 1) /
value.naturalWidth; 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.resetSize();
}; };
this.video = $(video); this.video = $(video);

View File

@ -5,5 +5,12 @@ export interface VideoOptions extends PluginOptions {
action: 'download' | 'query' | 'cover', action: 'download' | 'query' | 'cover',
url: string, url: string,
) => string; ) => string;
/**
*
*/
showTitle?: boolean; showTitle?: boolean;
/**
*
*/
fullEditor?: boolean;
} }