update
- video 插件增加 fullEditor 选项,初始化宽度与编辑器宽度对齐 - paste 优化粘贴嵌套的mark按优先级显示
This commit is contained in:
parent
635aafe156
commit
a38dfa7809
|
@ -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',
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Element>();
|
||||
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;
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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<T extends PluginOptions = PluginOptions>
|
||||
|
|
|
@ -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<string, Record<string, string>> = {};
|
||||
const stylesCaches: Map<string, Record<string, string>> = new Map();
|
||||
/**
|
||||
* 将 style 样式转换为 map 数据类型
|
||||
* @param {string} style
|
||||
*/
|
||||
export const getStyleMap = (style: string): Record<string, string> => {
|
||||
const key = md5(style);
|
||||
const key = style.replace(/\s+/g, '');
|
||||
const map: Record<string, string> = {};
|
||||
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<string, string> => {
|
|||
}
|
||||
map[key] = val;
|
||||
}
|
||||
stylesCaches[key] = map;
|
||||
return { ...map };
|
||||
stylesCaches.set(key, map);
|
||||
return Object.assign({}, map);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -12,6 +12,7 @@ export default class<T extends BackcolorOptions> extends MarkPlugin<T> {
|
|||
|
||||
style = {
|
||||
'background-color': '@var0',
|
||||
background: '@var1',
|
||||
};
|
||||
|
||||
variable = {
|
||||
|
@ -19,6 +20,7 @@ export default class<T extends BackcolorOptions> extends MarkPlugin<T> {
|
|||
required: true,
|
||||
value: '@color',
|
||||
},
|
||||
'@var1': '@color',
|
||||
};
|
||||
|
||||
conversion(): ConversionData {
|
||||
|
|
|
@ -225,6 +225,9 @@ class VideoComponent<T extends VideoValue = VideoValue> extends Card<T> {
|
|||
return false;
|
||||
};
|
||||
video.onloadedmetadata = () => {
|
||||
const videoPlugin =
|
||||
this.editor.plugin.findPlugin<VideoOptions>('video');
|
||||
const fullEditor = videoPlugin?.options.fullEditor;
|
||||
if (!value.naturalWidth) {
|
||||
value.naturalWidth = video.videoWidth || this.video?.width();
|
||||
this.setValue({
|
||||
|
@ -236,6 +239,16 @@ class VideoComponent<T extends VideoValue = VideoValue> extends Card<T> {
|
|||
(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);
|
||||
|
|
|
@ -5,5 +5,12 @@ export interface VideoOptions extends PluginOptions {
|
|||
action: 'download' | 'query' | 'cover',
|
||||
url: string,
|
||||
) => string;
|
||||
/**
|
||||
* 是否显示标题
|
||||
*/
|
||||
showTitle?: boolean;
|
||||
/**
|
||||
* 填满编辑器宽度
|
||||
*/
|
||||
fullEditor?: boolean;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue