fix: 优化粘贴的word格式文本

This commit is contained in:
yanmao 2022-01-20 15:12:38 +08:00
parent 07157acacf
commit 4259d75920
7 changed files with 151 additions and 48 deletions

View File

@ -415,6 +415,21 @@ export default class Paste {
nodeParent &&
nodeApi.isMark(nodeParent, this.schema) &&
nodeApi.isMark(node, this.schema)
) {
if (markApi.compare(nodeParent.clone(), node, true)) {
nodeApi.unwrap(node);
return;
} else {
nodeParent = nodeParent.parent();
}
}
// mark 按级别排序
nodeParent = parent;
if (
node.length > 0 &&
nodeParent &&
nodeApi.isMark(nodeParent, this.schema) &&
nodeApi.isMark(node, this.schema)
) {
const pMarkPlugin = markApi.findPlugin(nodeParent);
const cMarkPlugin = markApi.findPlugin(node);
@ -454,12 +469,6 @@ export default class Paste {
return node;
}
}
if (markApi.compare(nodeParent.clone(), node, true)) {
nodeApi.unwrap(node);
break;
} else {
nodeParent = nodeParent.parent();
}
}
return undefined;
});

View File

@ -120,6 +120,7 @@ class Parser implements ParserInterface {
let value = conversion.transform(node);
const oldRules: Array<ConversionRule> = [];
const nodeApi = this.editor.node;
let convertAfterNode: NodeInterface | null = null;
while (value) {
const { rule } = value;
oldRules.push(rule);
@ -139,13 +140,22 @@ class Parser implements ParserInterface {
if (!nodeApi.isBlock(newNode, schema)) {
if (value.replace) {
node.replaceWith(newNode);
node = newNode;
} else {
//把包含旧子节点的新节点追加到旧节点下
node.append(newNode);
}
if (!convertAfterNode || convertAfterNode.length === 0)
convertAfterNode = newNode;
} else {
// 替换
node.replaceWith(newNode);
if (value.replace) {
node.replaceWith(newNode);
node = newNode;
} else {
node.append(newNode);
}
if (!convertAfterNode || convertAfterNode.length === 0)
convertAfterNode = newNode;
//排除之前的过滤规则后再次过滤
value = conversion.transform(
newNode,
@ -157,7 +167,7 @@ class Parser implements ParserInterface {
//排除之前的过滤规则后再次过滤
value = conversion.transform(node, (r) => oldRules.indexOf(r) < 0);
}
return;
return convertAfterNode;
}
normalize(
root: NodeInterface,
@ -199,10 +209,11 @@ class Parser implements ParserInterface {
return;
const attrCount = Object.keys(attributes).length;
const styleCount = Object.keys(style).length;
//过滤不符合当前节点规则的属性样式
schema.filter(node, attributes, style);
//复制一个节点
const newNode = node.clone();
//过滤不符合当前节点规则的属性样式
schema.filter(node, attributes, style, true);
//移除 data-id以免在下次判断类型的时候使用缓存
newNode.removeAttributes(DATA_ID);
//移除符合当前节点的属性样式,剩余的属性样式组成新的节点
@ -257,8 +268,7 @@ class Parser implements ParserInterface {
if (conversion && (!schema.getType(node) || isCard)) {
const newNode = this.convert(conversion, node, schema);
if (newNode) {
this.normalize(newNode, schema, conversion);
return;
return newNode;
}
}
if (isCard) return;
@ -281,16 +291,23 @@ class Parser implements ParserInterface {
);
if (!type) {
if (conversion) {
this.convert(conversion, newNode, schema);
const newChildren = newNode.children();
if (newChildren.length > 0) {
const newChildren = this.convert(
conversion,
newNode,
schema,
);
if (newChildren && newChildren.length > 0) {
const children = node.children();
newChildren.append(
children.length > 0
? children
: $('\u200b', null),
);
node.append(newChildren);
node.append(
newNode.length === 0
? newChildren
: newNode.children(),
);
return;
}
}
@ -300,7 +317,16 @@ class Parser implements ParserInterface {
let tempNode = node;
while (type === 'mark') {
const children = tempNode.children();
newNode.append(
let appendTarget = newNode;
while (true) {
const children = appendTarget.children();
if (children.length > 0) {
appendTarget = children;
} else {
break;
}
}
appendTarget.append(
children.length > 0
? children
: $('\u200b', null),
@ -319,10 +345,16 @@ class Parser implements ParserInterface {
);
if (!type) {
if (conversion) {
this.convert(conversion, newNode, schema);
const newChildren = newNode.children();
if (newChildren.length > 0) {
newNode = newChildren;
const newChildren = this.convert(
conversion,
newNode,
schema,
);
if (newChildren && newChildren.length > 0) {
newNode =
newNode.length > 0
? newNode.children()
: newChildren;
type = 'mark';
continue;
}

View File

@ -320,7 +320,11 @@ class Schema implements SchemaInterface {
* @param styles
* @param rule
*/
filterStyles(styles: { [k: string]: string }, rule: SchemaRule) {
filterStyles(
styles: { [k: string]: string },
rule: SchemaRule,
callback?: (name: string, value: string) => void,
) {
Object.keys(styles).forEach((styleName) => {
if (
!rule.attributes?.style ||
@ -330,8 +334,10 @@ class Schema implements SchemaInterface {
styles[styleName],
true,
)
)
) {
if (callback) callback(styleName, styles[styleName]);
delete styles[styleName];
}
});
}
/**
@ -339,7 +345,11 @@ class Schema implements SchemaInterface {
* @param attributes
* @param rule
*/
filterAttributes(attributes: { [k: string]: string }, rule: SchemaRule) {
filterAttributes(
attributes: { [k: string]: string },
rule: SchemaRule,
callback?: (name: string, value: string) => void,
) {
Object.keys(attributes).forEach((attributesName) => {
if (
!rule.attributes ||
@ -349,8 +359,11 @@ class Schema implements SchemaInterface {
attributes[attributesName],
true,
)
)
) {
if (callback)
callback(attributesName, attributes[attributesName]);
delete attributes[attributesName];
}
});
}
@ -359,12 +372,14 @@ class Schema implements SchemaInterface {
* @param node
* @param attributes
* @param styles
* @param apply
* @returns
*/
filter(
node: NodeInterface,
attributes: { [k: string]: string },
styles: { [k: string]: string },
apply: boolean = false,
) {
const rule = this.getRule(node);
if (!rule) return;
@ -377,8 +392,16 @@ class Schema implements SchemaInterface {
globalRule ? globals[globalRule] : {},
),
});
this.filterAttributes(attributes, allRule);
this.filterStyles(styles, allRule);
this.filterAttributes(
attributes,
allRule,
apply ? (name) => node.removeAttributes(name) : undefined,
);
this.filterStyles(
styles,
allRule,
apply ? (name) => node.css(name, '') : undefined,
);
}
/**

View File

@ -147,16 +147,23 @@ export interface SchemaInterface {
*
* @param styles
* @param rule
* @param callback
*/
filterStyles(styles: { [k: string]: string }, rule: SchemaRule): void;
filterStyles(
styles: { [k: string]: string },
rule: SchemaRule,
callback?: (name: string, value: string) => void,
): void;
/**
*
* @param attributes
* @param rule
* @param callback
*/
filterAttributes(
attributes: { [k: string]: string },
rule: SchemaRule,
callback?: (name: string, value: string) => void,
): void;
/**
* node节点规则的属性和样式
@ -169,6 +176,7 @@ export interface SchemaInterface {
node: NodeInterface,
attributes: { [k: string]: string },
styles: { [k: string]: string },
apply?: boolean,
): void;
/**
* schema对象

View File

@ -1,4 +1,10 @@
import { MarkPlugin, PluginOptions } from '@aomao/engine';
import {
$,
ConversionFromValue,
ConversionToValue,
MarkPlugin,
PluginOptions,
} from '@aomao/engine';
export interface StrikethroughOptions extends PluginOptions {
hotkey?: string | Array<string>;
@ -20,17 +26,28 @@ export default class<
markdown =
this.options.markdown === undefined ? '~~' : this.options.markdown;
conversion() {
conversion(): { from: ConversionFromValue; to: ConversionToValue }[] {
return [
{
from: {
span: {
style: {
'text-decoration': 'line-through',
},
},
from: (name, style) => {
return (
name === 'span' &&
(style['text-decoration'] || '').includes(
'line-through',
)
);
},
to: (_, style, attrs) => {
const newNode = $(`<${this.tagName} />`);
style['text-decoration'] = style['text-decoration']
.split(/\s+/)
.filter((value) => value !== 'line-through')
.join(' ')
.trim();
newNode.css(style);
newNode.attributes(attrs);
return newNode;
},
to: this.tagName,
},
{
from: 's',

View File

@ -62,7 +62,7 @@
position: absolute;
border: 1px solid #e8e8e8;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
z-index: 124;
z-index: 130;
text-indent: 0;
top:0;
padding: 8px 0;

View File

@ -1,4 +1,9 @@
import { MarkPlugin, PluginOptions } from '@aomao/engine';
import { $, MarkPlugin } from '@aomao/engine';
import type {
ConversionFromValue,
ConversionToValue,
PluginOptions,
} from '@aomao/engine';
export interface UnderlineOptions extends PluginOptions {
hotkey?: string | Array<string>;
@ -16,17 +21,26 @@ export default class<
return this.options.hotkey || 'mod+u';
}
conversion() {
conversion(): { from: ConversionFromValue; to: ConversionToValue }[] {
return [
{
from: {
span: {
style: {
'text-decoration': 'underline',
},
},
from: (name, style) => {
return (
name === 'span' &&
(style['text-decoration'] || '').includes('underline')
);
},
to: (_, style, attrs) => {
const newNode = $(`<${this.tagName} />`);
style['text-decoration'] = style['text-decoration']
.split(/\s+/)
.filter((value) => value !== 'underline')
.join(' ')
.trim();
newNode.css(style);
newNode.attributes(attrs);
return newNode;
},
to: this.tagName,
},
];
}