Fix regression around pasting links (#8537)
* Fix regression around pasting links * Add tests
This commit is contained in:
parent
7e21be06d0
commit
674aec4050
6 changed files with 163 additions and 29 deletions
|
@ -28,7 +28,7 @@ import { formatRange, formatRangeAsLink, replaceRangeAndMoveCaret, toggleInlineF
|
|||
from '../../../editor/operations';
|
||||
import { getCaretOffsetAndText, getRangeForSelection } from '../../../editor/dom';
|
||||
import Autocomplete, { generateCompletionDomId } from '../rooms/Autocomplete';
|
||||
import { getAutoCompleteCreator, Type } from '../../../editor/parts';
|
||||
import { getAutoCompleteCreator, Part, Type } from '../../../editor/parts';
|
||||
import { parseEvent, parsePlainTextMessage } from '../../../editor/deserialize';
|
||||
import { renderModel } from '../../../editor/render';
|
||||
import TypingStore from "../../../stores/TypingStore";
|
||||
|
@ -92,7 +92,7 @@ function selectionEquals(a: Partial<Selection>, b: Selection): boolean {
|
|||
interface IProps {
|
||||
model: EditorModel;
|
||||
room: Room;
|
||||
threadId: string;
|
||||
threadId?: string;
|
||||
placeholder?: string;
|
||||
label?: string;
|
||||
initialCaret?: DocumentOffset;
|
||||
|
@ -333,28 +333,29 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
|
|||
|
||||
private onPaste = (event: ClipboardEvent<HTMLDivElement>): boolean => {
|
||||
event.preventDefault(); // we always handle the paste ourselves
|
||||
if (this.props.onPaste && this.props.onPaste(event, this.props.model)) {
|
||||
if (this.props.onPaste?.(event, this.props.model)) {
|
||||
// to prevent double handling, allow props.onPaste to skip internal onPaste
|
||||
return true;
|
||||
}
|
||||
|
||||
const { model } = this.props;
|
||||
const { partCreator } = model;
|
||||
const plainText = event.clipboardData.getData("text/plain");
|
||||
const partsText = event.clipboardData.getData("application/x-element-composer");
|
||||
let parts;
|
||||
|
||||
let parts: Part[];
|
||||
if (partsText) {
|
||||
const serializedTextParts = JSON.parse(partsText);
|
||||
const deserializedParts = serializedTextParts.map(p => partCreator.deserializePart(p));
|
||||
parts = deserializedParts;
|
||||
parts = serializedTextParts.map(p => partCreator.deserializePart(p));
|
||||
} else {
|
||||
const text = event.clipboardData.getData("text/plain");
|
||||
parts = parsePlainTextMessage(text, partCreator, { shouldEscape: false });
|
||||
parts = parsePlainTextMessage(plainText, partCreator, { shouldEscape: false });
|
||||
}
|
||||
const textToInsert = event.clipboardData.getData("text/plain");
|
||||
|
||||
this.modifiedFlag = true;
|
||||
const range = getRangeForSelection(this.editorRef.current, model, document.getSelection());
|
||||
if (textToInsert && linkify.test(textToInsert)) {
|
||||
formatRangeAsLink(range, textToInsert);
|
||||
|
||||
if (plainText && range.length > 0 && linkify.test(plainText)) {
|
||||
formatRangeAsLink(range, plainText);
|
||||
} else {
|
||||
replaceRangeAndMoveCaret(range, parts);
|
||||
}
|
||||
|
|
|
@ -17,6 +17,8 @@ limitations under the License.
|
|||
|
||||
import { CARET_NODE_CHAR, isCaretNode } from "./render";
|
||||
import DocumentOffset from "./offset";
|
||||
import EditorModel from "./model";
|
||||
import Range from "./range";
|
||||
|
||||
type Predicate = (node: Node) => boolean;
|
||||
type Callback = (node: Node) => void;
|
||||
|
@ -122,7 +124,7 @@ function getTextAndOffsetToNode(editor: HTMLDivElement, selectionNode: Node) {
|
|||
let foundNode = false;
|
||||
let text = "";
|
||||
|
||||
function enterNodeCallback(node) {
|
||||
function enterNodeCallback(node: HTMLElement) {
|
||||
if (!foundNode) {
|
||||
if (node === selectionNode) {
|
||||
foundNode = true;
|
||||
|
@ -148,12 +150,12 @@ function getTextAndOffsetToNode(editor: HTMLDivElement, selectionNode: Node) {
|
|||
return true;
|
||||
}
|
||||
|
||||
function leaveNodeCallback(node) {
|
||||
function leaveNodeCallback(node: HTMLElement) {
|
||||
// if this is not the last DIV (which are only used as line containers atm)
|
||||
// we don't just check if there is a nextSibling because sometimes the caret ends up
|
||||
// after the last DIV and it creates a newline if you type then,
|
||||
// whereas you just want it to be appended to the current line
|
||||
if (node.tagName === "DIV" && node.nextSibling && node.nextSibling.tagName === "DIV") {
|
||||
if (node.tagName === "DIV" && (<HTMLElement>node.nextSibling)?.tagName === "DIV") {
|
||||
text += "\n";
|
||||
if (!foundNode) {
|
||||
offsetToNode += 1;
|
||||
|
@ -167,7 +169,7 @@ function getTextAndOffsetToNode(editor: HTMLDivElement, selectionNode: Node) {
|
|||
}
|
||||
|
||||
// get text value of text node, ignoring ZWS if it's a caret node
|
||||
function getTextNodeValue(node) {
|
||||
function getTextNodeValue(node: Node): string {
|
||||
const nodeText = node.nodeValue;
|
||||
// filter out ZWS for caret nodes
|
||||
if (isCaretNode(node.parentElement)) {
|
||||
|
@ -184,7 +186,7 @@ function getTextNodeValue(node) {
|
|||
}
|
||||
}
|
||||
|
||||
export function getRangeForSelection(editor, model, selection) {
|
||||
export function getRangeForSelection(editor: HTMLDivElement, model: EditorModel, selection: Selection): Range {
|
||||
const focusOffset = getSelectionOffsetAndText(
|
||||
editor,
|
||||
selection.focusNode,
|
||||
|
|
|
@ -219,14 +219,12 @@ export function formatRangeAsCode(range: Range): void {
|
|||
export function formatRangeAsLink(range: Range, text?: string) {
|
||||
const { model } = range;
|
||||
const { partCreator } = model;
|
||||
const linkRegex = /\[(.*?)\]\(.*?\)/g;
|
||||
const linkRegex = /\[(.*?)]\(.*?\)/g;
|
||||
const isFormattedAsLink = linkRegex.test(range.text);
|
||||
if (isFormattedAsLink) {
|
||||
const linkDescription = range.text.replace(linkRegex, "$1");
|
||||
const newParts = [partCreator.plain(linkDescription)];
|
||||
const prefixLength = 1;
|
||||
const suffixLength = range.length - (linkDescription.length + 2);
|
||||
replaceRangeAndAutoAdjustCaret(range, newParts, true, prefixLength, suffixLength);
|
||||
replaceRangeAndMoveCaret(range, newParts, 0);
|
||||
} else {
|
||||
// We set offset to -1 here so that the caret lands between the brackets
|
||||
replaceRangeAndMoveCaret(range, [partCreator.plain("[" + range.text + "]" + "(" + (text ?? "") + ")")], -1);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue