fix: image markdown & scrollbar

- image markdown 的图片链接中如果包含其它字符会无法命中
- 滚动条在视窗大小改变后无法正确响应
- 删除操作后将滚动到光标位置处
This commit is contained in:
yanmao 2021-12-19 23:28:35 +08:00
parent 2373d36786
commit adfc8ef90f
10 changed files with 174 additions and 137 deletions

View File

@ -128,7 +128,7 @@ export const pluginConfig: { [key: string]: PluginOptions } = {
// 减去大纲的宽度
const width = editorLeft - $('.data-toc-wrapper').width();
// 留 16px 的间隔
return width <= 0 ? 100 : width - 16;
return width <= 0 ? 0 : width - 16;
},
maxRightWidth: () => {
// 编辑区域位置
@ -139,7 +139,7 @@ export const pluginConfig: { [key: string]: PluginOptions } = {
// 减去评论区域的宽度
const width = editorRigth - $('.doc-comment-layer').width();
// 留 16px 的间隔
return width <= 0 ? 100 : width - 16;
return width <= 0 ? 0 : width - 16;
},
},
},

View File

@ -399,14 +399,20 @@ class ChangeModel implements ChangeInterface {
};
}
let nextNode = firstNode.next();
let beforeNode = firstNode;
nodeApi.insert(firstNode, range);
while (nextNode && !nodeApi.isBlock(nextNode)) {
if (range.startContainer.nodeType === Node.TEXT_NODE)
range.enlargeToElementNode().collapse(false);
const newNext = nextNode.next();
nodeApi.insert(nextNode, range);
beforeNode.after(nextNode);
beforeNode = nextNode;
//nodeApi.insert(nextNode, range);
nextNode = newNext;
}
if (beforeNode !== firstNode) {
range.select(beforeNode, true).collapse(false);
}
if (childNodes.length === 0) {
apply(range);
return;

View File

@ -351,7 +351,8 @@ export default class Paste {
const nextReg = node.get<Text>()!.splitText(match.index);
const endReg = nextReg.splitText(match[0].length);
node.after(nextReg);
node.after(endReg);
nextReg.after(endReg);
if (!node.text()) node.remove();
}
}
// 删除包含Card的 pre 标签

View File

@ -1038,22 +1038,11 @@ class NodeEntry implements NodeInterface {
innerHeight: 0,
innerWidth: 0,
};
let top, left, bottom, right;
if (this.length > 0) {
const element = this.get<Element>()!;
const rect = element.getBoundingClientRect();
top = rect.top;
left = rect.left;
bottom = rect.bottom;
right = rect.right;
} else {
const element = this.get<Element>()!;
const rect = element.getBoundingClientRect();
top = rect.top;
left = rect.left;
bottom = rect.bottom;
right = rect.right;
}
const element = this.get<Element>()!;
const rect = element.getBoundingClientRect();
const { top, left, bottom, right } = rect;
return {
top,
left,
@ -1081,10 +1070,15 @@ class NodeEntry implements NodeInterface {
if (viewNode) viewNode.parentNode?.removeChild(viewNode);
// 简单模式,只判断任一方向是否在视口内
if (simpleMode) {
return top <= vp.bottom || bottom <= vp.bottom;
return (
(top > 0 && top <= vp.bottom) ||
(bottom > 0 && bottom <= vp.bottom)
);
}
return (
top > 0 &&
top >= vp.top &&
left > 0 &&
left >= vp.left &&
bottom <= vp.bottom &&
right <= vp.right

View File

@ -827,7 +827,6 @@ class NodeModel implements NodeModelInterface {
isBegin = true;
if (text.length === 0) {
childNode.remove();
break;
}
}
//移除末尾换行符
@ -836,11 +835,11 @@ class NodeModel implements NodeModelInterface {
childNode.text(text.substr(0, match.index));
cloneNode.append(childNode);
break;
} else if (isBegin) {
} else if (isBegin && childNode.length > 0) {
childNode.text(text);
}
}
cloneNode.append(childNode);
if (childNode.length > 0) cloneNode.append(childNode);
//判断下一个节点的开头是换行符,有换行符就跳出
if (nextNode?.isText()) {
const text = nextNode.text();

View File

@ -501,9 +501,9 @@ class Parser implements ParserInterface {
if (result.length > 0 && /^\n+/g.test(result[0])) {
result[0] = result[0].replace(/^\n+/g, '');
}
if (result.length > 0 && /^\n+/g.test(result[result.length - 1])) {
if (result.length > 0 && /\n+$/g.test(result[result.length - 1])) {
result[result.length - 1] = result[result.length - 1].replace(
/^\n+/g,
/\n+$/g,
'',
);
}

View File

@ -1,5 +1,5 @@
import { EventEmitter2 } from 'eventemitter2';
import { throttle } from 'lodash-es';
import { debounce, throttle } from 'lodash-es';
import { DATA_ELEMENT, UI } from '../constants';
import { NodeInterface } from '../types';
import { $ } from '../node';
@ -37,7 +37,6 @@ class Scrollbar extends EventEmitter2 {
private sHeight: number = 0;
private xWidth: number = 0;
private yHeight: number = 0;
private maxScrollLeft: number = 0;
#observer?: MutationObserver;
#reverse?: boolean;
#content?: NodeInterface;
@ -120,85 +119,121 @@ class Scrollbar extends EventEmitter2 {
}
}
refresh = () => {
getWidth() {
const element = this.container.get<HTMLElement>();
if (element) {
const offsetWidth = this.#scroll?.getOffsetWidth
? this.#scroll.getOffsetWidth(element.offsetWidth)
: element.offsetWidth;
if (!element) return 0;
const offsetWidth = this.#scroll?.getOffsetWidth
? this.#scroll.getOffsetWidth(element.offsetWidth)
: element.offsetWidth;
return offsetWidth;
}
const { offsetHeight, scrollTop } = element;
const contentElement = this.#content?.get<HTMLElement>();
const sPLeft = removeUnit(this.container.css('padding-left'));
const sPRight = removeUnit(this.container.css('padding-right'));
const sPTop = removeUnit(this.container.css('padding-top'));
const sPBottom = removeUnit(this.container.css('padding-bottom'));
const scrollWidth = contentElement
? contentElement.offsetWidth + sPLeft + sPRight
: element.scrollWidth;
const scrollHeight = contentElement
? contentElement.offsetHeight + sPTop + sPBottom
: element.scrollHeight;
this.oWidth =
offsetWidth -
removeUnit(this.container.css('border-left-width')) -
removeUnit(this.container.css('border-right-width'));
this.oHeight =
offsetHeight -
removeUnit(this.container.css('border-top-width')) -
removeUnit(this.container.css('border-bottom-width'));
this.sWidth = scrollWidth;
this.sHeight = scrollHeight;
this.xWidth = Math.floor((this.oWidth * this.oWidth) / scrollWidth);
this.yHeight = Math.floor(
(this.oHeight * this.oHeight) / scrollHeight,
);
this.maxScrollLeft = scrollWidth - this.oWidth;
if (this.x) {
this.slideX?.css('width', this.xWidth + 'px');
const display =
this.oWidth - sPLeft - sPRight === this.sWidth ||
(contentElement &&
contentElement.offsetWidth <=
this.oWidth - sPLeft - sPRight)
? 'none'
: 'block';
this.slideX?.css('display', display);
this.emit('display', display);
this.shadowLeft?.css('display', display);
this.shadowRight?.css('display', display);
}
if (this.y) {
this.slideY?.css('height', this.yHeight + 'px');
const display =
this.oHeight - sPTop - sPBottom === this.sHeight ||
(contentElement &&
contentElement.offsetHeight <=
this.oHeight - sPTop - sPBottom)
? 'none'
: 'block';
this.slideY?.css('display', display);
this.emit('display', display);
}
// 实际内容宽度小于容器滚动宽度(有内容删除了)
if (
this.x &&
contentElement &&
element.scrollWidth - sPLeft - sPRight >
contentElement.offsetWidth
) {
let left =
element.scrollWidth -
sPLeft -
sPRight -
contentElement.offsetWidth;
refresh = debounce(
() => {
const element = this.container.get<HTMLElement>();
if (element) {
const { offsetHeight, scrollTop } = element;
const contentElement = this.#content?.get<HTMLElement>();
const sPLeft = removeUnit(this.container.css('padding-left'));
const sPRight = removeUnit(this.container.css('padding-right'));
const sPTop = removeUnit(this.container.css('padding-top'));
const sPBottom = removeUnit(
this.container.css('padding-bottom'),
);
const scrollWidth = contentElement
? contentElement.offsetWidth + sPLeft + sPRight
: element.scrollWidth;
const scrollHeight = contentElement
? contentElement.offsetHeight + sPTop + sPBottom
: element.scrollHeight;
this.oWidth = this.getWidth();
this.oHeight =
offsetHeight -
removeUnit(this.container.css('border-top-width')) -
removeUnit(this.container.css('border-bottom-width'));
this.sWidth = scrollWidth;
this.sHeight = scrollHeight;
this.xWidth = Math.floor(
(this.oWidth * this.oWidth) / scrollWidth,
);
this.yHeight = Math.floor(
(this.oHeight * this.oHeight) / scrollHeight,
);
if (this.x) {
this.slideX?.css('width', this.xWidth + 'px');
const display =
this.oWidth - sPLeft - sPRight === this.sWidth ||
(contentElement &&
contentElement.offsetWidth <=
this.oWidth - sPLeft - sPRight)
? 'none'
: 'block';
this.slideX?.css('display', display);
this.emit('display', display);
this.shadowLeft?.css('display', display);
this.shadowRight?.css('display', display);
}
if (this.y) {
this.slideY?.css('height', this.yHeight + 'px');
const display =
this.oHeight - sPTop - sPBottom === this.sHeight ||
(contentElement &&
contentElement.offsetHeight <=
this.oHeight - sPTop - sPBottom)
? 'none'
: 'block';
this.slideY?.css('display', display);
this.emit('display', display);
}
// 实际内容宽度小于容器滚动宽度(有内容删除了)
if (
this.x &&
contentElement &&
element.scrollWidth - sPLeft - sPRight >
contentElement.offsetWidth
) {
let left =
element.scrollWidth -
sPLeft -
sPRight -
contentElement.offsetWidth;
if (this.#scroll) {
const { onScrollX, getScrollLeft } = this.#scroll;
left = getScrollLeft
? getScrollLeft(-0) + element.scrollLeft - left
: element.scrollLeft - left;
if (left < 0) left = 0;
if (onScrollX) {
const result = onScrollX(left);
if (result > 0) element.scrollLeft = result;
else element.scrollLeft = 0;
}
this.scroll({ left });
} else {
element.scrollLeft -= left;
}
return;
}
// 实际内容高度小于容器滚动高度(有内容删除了)
if (
this.y &&
contentElement &&
element.scrollHeight - sPTop - sPBottom !==
contentElement.offsetHeight
) {
element.scrollTop -=
element.scrollHeight -
sPTop -
sPBottom -
contentElement.offsetHeight;
return;
}
const left = this.#scroll?.getScrollLeft
? this.#scroll.getScrollLeft(element.scrollLeft)
: element.scrollLeft;
if (this.#scroll) {
const { onScrollX, getScrollLeft } = this.#scroll;
left = getScrollLeft
? getScrollLeft(-0) + element.scrollLeft - left
: element.scrollLeft - left;
if (left < 0) left = 0;
const { onScrollX } = this.#scroll;
if (onScrollX) {
const result = onScrollX(left);
if (result > 0) element.scrollLeft = result;
@ -206,31 +241,14 @@ class Scrollbar extends EventEmitter2 {
}
this.scroll({ left });
} else {
element.scrollLeft -= left;
this.reRenderX(left);
}
return;
this.reRenderY(scrollTop);
}
// 实际内容高度小于容器滚动高度(有内容删除了)
if (
this.y &&
contentElement &&
element.scrollHeight - sPTop - sPBottom !==
contentElement.offsetHeight
) {
element.scrollTop -=
element.scrollHeight -
sPTop -
sPBottom -
contentElement.offsetHeight;
return;
}
const left = this.#scroll?.getScrollLeft
? this.#scroll.getScrollLeft(element.scrollLeft)
: element.scrollLeft;
this.reRenderX(left);
this.reRenderY(scrollTop);
}
};
},
50,
{ leading: true },
);
/**
* 使
@ -591,13 +609,12 @@ class Scrollbar extends EventEmitter2 {
let min = value <= 0 ? 0 : left / value;
min = Math.min(1, min);
this.slideX?.css('left', (this.oWidth - this.xWidth) * min + 'px');
this.reRenderShadow(left);
if (left === removeUnit(this.scrollBarX?.css('left') || '0'))
return;
this.emit('change', {
x: left,
y: removeUnit(this.scrollBarY?.css('top') || '0'),
});
this.oWidth = this.getWidth();
this.reRenderShadow(left);
}
};
@ -608,7 +625,6 @@ class Scrollbar extends EventEmitter2 {
let min = value <= 0 ? 0 : top / value;
min = Math.min(1, min);
this.slideY?.css('top', (this.oHeight - this.yHeight) * min + 'px');
if (top === removeUnit(this.scrollBarX?.css('top') || '0')) return;
this.emit('change', {
x: removeUnit(this.scrollBarX?.css('left') || '0'),
y: top,
@ -652,6 +668,7 @@ class Scrollbar extends EventEmitter2 {
}
this.#observer?.disconnect();
window.removeEventListener('resize', this.refresh);
window.removeEventListener('scroll', this.refresh);
}
}

View File

@ -78,7 +78,14 @@ class Backspace implements TypingHandleInterface {
result = listener(event);
if (result === false) break;
}
if (result === false) return;
if (result === false) {
if (this.engine.scrollNode)
range.scrollIntoViewIfNeeded(
this.engine.container,
this.engine.scrollNode,
);
return;
}
// 范围为展开状态
if (!range.collapsed) {
event.preventDefault();
@ -88,6 +95,11 @@ class Backspace implements TypingHandleInterface {
}
change.delete(range);
change.apply(range);
if (this.engine.scrollNode)
range.scrollIntoViewIfNeeded(
this.engine.container,
this.engine.scrollNode,
);
return;
} else {
let brNode: NodeInterface | undefined = undefined;

View File

@ -685,13 +685,13 @@ export default class extends Plugin<Options> {
if (!text) return;
// 带跳转链接的图片
let reg =
/(\[!\[([^\]]{0,})\]\((https?:\/\/[^\)]{5,})\)\]\(([\S]+?)\))|(!\[([^\]]{0,})\]\((https?:\/\/[^\)]{5,})\))/;
/(\[!\[([^\]]{0,})\]\((\S{0,})(https?:\/\/[^\)]{5,})\)\]\(([\S]+?)\))|(!\[([^\]]{0,})\]\((https?:\/\/[^\)]{5,})\))/i;
let match = reg.exec(text);
if (!match) {
// 无跳转链接的图片
//![aomao-preview](https://user-images.githubusercontent.com/55792257/125074830-62d79300-e0f0-11eb-8d0f-bb96a7775568.png)
reg = /(!\[([^\]]{0,})\]\((https?:\/\/[^\)]{5,})\))/;
reg = /(!\[([^\]]{0,})\]\((\S{0,})(https?:\/\/[^\)]{5,})\))/i;
match = reg.exec(text);
}
return {
@ -720,9 +720,9 @@ export default class extends Plugin<Options> {
//从匹配结束位置分割
textNode = regNode.splitText(match[0].length);
const isLink = match[0].startsWith('[');
const alt = isLink ? match[2] : match[6];
const src = isLink ? match[3] : match[7];
const link = isLink ? match[4] : '';
const alt = match[2] || match[6];
const src = match[4] || match[8];
const link = isLink ? match[5] : '';
const cardNode = card.replaceNode($(regNode), 'image', {
src,

View File

@ -18,6 +18,14 @@ div[data-card-key="table"].card-selected .data-table, div[data-card-key="table"]
background: transparent
}
.am-engine [data-card-key="table"].card-selected [data-card-element="center"].data-card-background-selected {
background: transparent;
}
.am-engine [data-card-key="table"].card-selected [data-card-element="center"].data-card-background-selected .table-wrapper {
background: rgba(27, 162, 227, 0.2);
}
.am-engine-mobile div[data-card-key="table"].card-activated {
margin-left: 20px;
}