Merge remote-tracking branch 'origin/main' into nspec
This commit is contained in:
@@ -324,7 +324,7 @@ const AutosuggestPlugin = ({
|
||||
dispatch(chooseEmoji(suggestion));
|
||||
replaceMatch($createEmojiNode(suggestion));
|
||||
} else if (suggestion[0] === '#') {
|
||||
node.setTextContent(`${suggestion} `);
|
||||
(node as TextNode).setTextContent(`${suggestion} `);
|
||||
node.select();
|
||||
} else {
|
||||
const account = selectAccount(getState(), suggestion)!;
|
||||
|
||||
@@ -7,7 +7,16 @@
|
||||
import { LinkPlugin as LexicalLinkPlugin } from '@lexical/react/LexicalLinkPlugin';
|
||||
import * as React from 'react';
|
||||
|
||||
import { validateUrl } from '../utils/url';
|
||||
// Source: https://stackoverflow.com/a/8234912/2013580
|
||||
const urlRegExp = new RegExp(
|
||||
/((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[\w]*))?)/,
|
||||
);
|
||||
|
||||
export const validateUrl = (url: string): boolean => {
|
||||
// TODO Fix UI for link insertion; it should never default to an invalid URL such as https://.
|
||||
// Maybe show a dialog where they user can type the URL before inserting it.
|
||||
return url === 'https://' || urlRegExp.test(url);
|
||||
};
|
||||
|
||||
const LinkPlugin = (): JSX.Element => {
|
||||
return <LexicalLinkPlugin validateUrl={validateUrl} />;
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
/**
|
||||
* This source code is derived from code from Meta Platforms, Inc.
|
||||
* and affiliates, licensed under the MIT license located in the
|
||||
* LICENSE file in the /src/features/compose/editor directory.
|
||||
*/
|
||||
|
||||
/* eslint-disable eqeqeq */
|
||||
|
||||
export const getDOMRangeRect = (
|
||||
nativeSelection: Selection,
|
||||
rootElement: HTMLElement,
|
||||
): DOMRect => {
|
||||
const domRange = nativeSelection.getRangeAt(0);
|
||||
|
||||
let rect;
|
||||
|
||||
if (nativeSelection.anchorNode === rootElement) {
|
||||
let inner = rootElement;
|
||||
while (inner.firstElementChild != null) {
|
||||
inner = inner.firstElementChild as HTMLElement;
|
||||
}
|
||||
rect = inner.getBoundingClientRect();
|
||||
} else {
|
||||
rect = domRange.getBoundingClientRect();
|
||||
}
|
||||
|
||||
return rect;
|
||||
};
|
||||
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* This source code is derived from code from Meta Platforms, Inc.
|
||||
* and affiliates, licensed under the MIT license located in the
|
||||
* LICENSE file in the /src/features/compose/editor directory.
|
||||
*/
|
||||
|
||||
import { $isAtNodeEnd } from '@lexical/selection';
|
||||
import { ElementNode, RangeSelection, TextNode } from 'lexical';
|
||||
|
||||
export const getSelectedNode = (
|
||||
selection: RangeSelection,
|
||||
): TextNode | ElementNode => {
|
||||
const anchor = selection.anchor;
|
||||
const focus = selection.focus;
|
||||
const anchorNode = selection.anchor.getNode();
|
||||
const focusNode = selection.focus.getNode();
|
||||
if (anchorNode === focusNode) {
|
||||
return anchorNode;
|
||||
}
|
||||
const isBackward = selection.isBackward();
|
||||
if (isBackward) {
|
||||
return $isAtNodeEnd(focus) ? anchorNode : focusNode;
|
||||
} else {
|
||||
return $isAtNodeEnd(anchor) ? focusNode : anchorNode;
|
||||
}
|
||||
};
|
||||
@@ -1,4 +0,0 @@
|
||||
const isHTMLElement = (x: unknown): x is HTMLElement => x instanceof HTMLElement;
|
||||
|
||||
export default isHTMLElement;
|
||||
export { isHTMLElement };
|
||||
@@ -1,57 +0,0 @@
|
||||
/**
|
||||
* This source code is derived from code from Meta Platforms, Inc.
|
||||
* and affiliates, licensed under the MIT license located in the
|
||||
* LICENSE file in the /src/features/compose/editor directory.
|
||||
*/
|
||||
|
||||
class Point {
|
||||
|
||||
private readonly _x: number;
|
||||
private readonly _y: number;
|
||||
|
||||
constructor(x: number, y: number) {
|
||||
this._x = x;
|
||||
this._y = y;
|
||||
}
|
||||
|
||||
get x(): number {
|
||||
return this._x;
|
||||
}
|
||||
|
||||
get y(): number {
|
||||
return this._y;
|
||||
}
|
||||
|
||||
public equals({ x, y }: Point): boolean {
|
||||
return this.x === x && this.y === y;
|
||||
}
|
||||
|
||||
public calcDeltaXTo({ x }: Point): number {
|
||||
return this.x - x;
|
||||
}
|
||||
|
||||
public calcDeltaYTo({ y }: Point): number {
|
||||
return this.y - y;
|
||||
}
|
||||
|
||||
public calcHorizontalDistanceTo(point: Point): number {
|
||||
return Math.abs(this.calcDeltaXTo(point));
|
||||
}
|
||||
|
||||
public calcVerticalDistance(point: Point): number {
|
||||
return Math.abs(this.calcDeltaYTo(point));
|
||||
}
|
||||
|
||||
public calcDistanceTo(point: Point): number {
|
||||
return Math.sqrt(
|
||||
Math.pow(this.calcDeltaXTo(point), 2) +
|
||||
Math.pow(this.calcDeltaYTo(point), 2),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const isPoint = (x: unknown): x is Point => x instanceof Point;
|
||||
|
||||
export default Point;
|
||||
export { Point, isPoint };
|
||||
@@ -1,163 +0,0 @@
|
||||
/* eslint-disable no-dupe-class-members */
|
||||
/**
|
||||
* This source code is derived from code from Meta Platforms, Inc.
|
||||
* and affiliates, licensed under the MIT license located in the
|
||||
* LICENSE file in the /src/features/compose/editor directory.
|
||||
*/
|
||||
|
||||
import { isPoint, Point } from './point';
|
||||
|
||||
type ContainsPointReturn = {
|
||||
result: boolean;
|
||||
reason: {
|
||||
isOnTopSide: boolean;
|
||||
isOnBottomSide: boolean;
|
||||
isOnLeftSide: boolean;
|
||||
isOnRightSide: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
class Rect {
|
||||
|
||||
private readonly _left: number;
|
||||
private readonly _top: number;
|
||||
private readonly _right: number;
|
||||
private readonly _bottom: number;
|
||||
|
||||
constructor(left: number, top: number, right: number, bottom: number) {
|
||||
const [physicTop, physicBottom] =
|
||||
top <= bottom ? [top, bottom] : [bottom, top];
|
||||
|
||||
const [physicLeft, physicRight] =
|
||||
left <= right ? [left, right] : [right, left];
|
||||
|
||||
this._top = physicTop;
|
||||
this._right = physicRight;
|
||||
this._left = physicLeft;
|
||||
this._bottom = physicBottom;
|
||||
}
|
||||
|
||||
get top(): number {
|
||||
return this._top;
|
||||
}
|
||||
|
||||
get right(): number {
|
||||
return this._right;
|
||||
}
|
||||
|
||||
get bottom(): number {
|
||||
return this._bottom;
|
||||
}
|
||||
|
||||
get left(): number {
|
||||
return this._left;
|
||||
}
|
||||
|
||||
get width(): number {
|
||||
return Math.abs(this._left - this._right);
|
||||
}
|
||||
|
||||
get height(): number {
|
||||
return Math.abs(this._bottom - this._top);
|
||||
}
|
||||
|
||||
public equals({ top, left, bottom, right }: Rect): boolean {
|
||||
return (
|
||||
top === this._top &&
|
||||
bottom === this._bottom &&
|
||||
left === this._left &&
|
||||
right === this._right
|
||||
);
|
||||
}
|
||||
|
||||
public contains({ x, y }: Point): ContainsPointReturn;
|
||||
public contains({ top, left, bottom, right }: Rect): boolean;
|
||||
public contains(target: Point | Rect): boolean | ContainsPointReturn {
|
||||
if (isPoint(target)) {
|
||||
const { x, y } = target;
|
||||
|
||||
const isOnTopSide = y < this._top;
|
||||
const isOnBottomSide = y > this._bottom;
|
||||
const isOnLeftSide = x < this._left;
|
||||
const isOnRightSide = x > this._right;
|
||||
|
||||
const result =
|
||||
!isOnTopSide && !isOnBottomSide && !isOnLeftSide && !isOnRightSide;
|
||||
|
||||
return {
|
||||
reason: {
|
||||
isOnBottomSide,
|
||||
isOnLeftSide,
|
||||
isOnRightSide,
|
||||
isOnTopSide,
|
||||
},
|
||||
result,
|
||||
};
|
||||
} else {
|
||||
const { top, left, bottom, right } = target;
|
||||
|
||||
return (
|
||||
top >= this._top &&
|
||||
top <= this._bottom &&
|
||||
bottom >= this._top &&
|
||||
bottom <= this._bottom &&
|
||||
left >= this._left &&
|
||||
left <= this._right &&
|
||||
right >= this._left &&
|
||||
right <= this._right
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public intersectsWith(rect: Rect): boolean {
|
||||
const { left: x1, top: y1, width: w1, height: h1 } = rect;
|
||||
const { left: x2, top: y2, width: w2, height: h2 } = this;
|
||||
const maxX = x1 + w1 >= x2 + w2 ? x1 + w1 : x2 + w2;
|
||||
const maxY = y1 + h1 >= y2 + h2 ? y1 + h1 : y2 + h2;
|
||||
const minX = x1 <= x2 ? x1 : x2;
|
||||
const minY = y1 <= y2 ? y1 : y2;
|
||||
return maxX - minX <= w1 + w2 && maxY - minY <= h1 + h2;
|
||||
}
|
||||
|
||||
public generateNewRect({
|
||||
left = this.left,
|
||||
top = this.top,
|
||||
right = this.right,
|
||||
bottom = this.bottom,
|
||||
}): Rect {
|
||||
return new Rect(left, top, right, bottom);
|
||||
}
|
||||
|
||||
static fromLTRB(
|
||||
left: number,
|
||||
top: number,
|
||||
right: number,
|
||||
bottom: number,
|
||||
): Rect {
|
||||
return new Rect(left, top, right, bottom);
|
||||
}
|
||||
|
||||
static fromLWTH(
|
||||
left: number,
|
||||
width: number,
|
||||
top: number,
|
||||
height: number,
|
||||
): Rect {
|
||||
return new Rect(left, top, left + width, top + height);
|
||||
}
|
||||
|
||||
static fromPoints(startPoint: Point, endPoint: Point): Rect {
|
||||
const { y: top, x: left } = startPoint;
|
||||
const { y: bottom, x: right } = endPoint;
|
||||
return Rect.fromLTRB(left, top, right, bottom);
|
||||
}
|
||||
|
||||
static fromDOM(dom: HTMLElement): Rect {
|
||||
const { top, width, left, height } = dom.getBoundingClientRect();
|
||||
return Rect.fromLWTH(left, width, top, height);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Rect;
|
||||
export { Rect };
|
||||
@@ -1,45 +0,0 @@
|
||||
/**
|
||||
* This source code is derived from code from Meta Platforms, Inc.
|
||||
* and affiliates, licensed under the MIT license located in the
|
||||
* LICENSE file in the /src/features/compose/editor directory.
|
||||
*/
|
||||
|
||||
const VERTICAL_GAP = 10;
|
||||
const HORIZONTAL_OFFSET = 5;
|
||||
|
||||
export const setFloatingElemPosition = (
|
||||
targetRect: ClientRect | null,
|
||||
floatingElem: HTMLElement,
|
||||
anchorElem: HTMLElement,
|
||||
verticalGap: number = VERTICAL_GAP,
|
||||
horizontalOffset: number = HORIZONTAL_OFFSET,
|
||||
): void => {
|
||||
const scrollerElem = anchorElem.parentElement;
|
||||
|
||||
if (targetRect === null || !scrollerElem) {
|
||||
floatingElem.style.opacity = '0';
|
||||
floatingElem.style.transform = 'translate(-10000px, -10000px)';
|
||||
return;
|
||||
}
|
||||
|
||||
const floatingElemRect = floatingElem.getBoundingClientRect();
|
||||
const anchorElementRect = anchorElem.getBoundingClientRect();
|
||||
const editorScrollerRect = scrollerElem.getBoundingClientRect();
|
||||
|
||||
let top = targetRect.top - floatingElemRect.height - verticalGap;
|
||||
let left = targetRect.left - horizontalOffset;
|
||||
|
||||
if (top < editorScrollerRect.top) {
|
||||
top += floatingElemRect.height + targetRect.height + verticalGap * 2;
|
||||
}
|
||||
|
||||
if (left + floatingElemRect.width > editorScrollerRect.right) {
|
||||
left = editorScrollerRect.right - floatingElemRect.width - horizontalOffset;
|
||||
}
|
||||
|
||||
top -= anchorElementRect.top;
|
||||
left -= anchorElementRect.left;
|
||||
|
||||
floatingElem.style.opacity = '1';
|
||||
floatingElem.style.transform = `translate(${left}px, ${top}px)`;
|
||||
};
|
||||
@@ -1,32 +0,0 @@
|
||||
/**
|
||||
* This source code is derived from code from Meta Platforms, Inc.
|
||||
* and affiliates, licensed under the MIT license located in the
|
||||
* LICENSE file in the /src/features/compose/editor directory.
|
||||
*/
|
||||
|
||||
export const sanitizeUrl = (url: string): string => {
|
||||
/** A pattern that matches safe URLs. */
|
||||
const SAFE_URL_PATTERN =
|
||||
/^(?:(?:https?|mailto|ftp|tel|file|sms):|[^&:/?#]*(?:[/?#]|$))/gi;
|
||||
|
||||
/** A pattern that matches safe data URLs. */
|
||||
const DATA_URL_PATTERN =
|
||||
/^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[a-z0-9+/]+=*$/i;
|
||||
|
||||
url = String(url).trim();
|
||||
|
||||
if (url.match(SAFE_URL_PATTERN) || url.match(DATA_URL_PATTERN)) return url;
|
||||
|
||||
return 'https://';
|
||||
};
|
||||
|
||||
// Source: https://stackoverflow.com/a/8234912/2013580
|
||||
const urlRegExp = new RegExp(
|
||||
/((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[\w]*))?)/,
|
||||
);
|
||||
|
||||
export const validateUrl = (url: string): boolean => {
|
||||
// TODO Fix UI for link insertion; it should never default to an invalid URL such as https://.
|
||||
// Maybe show a dialog where they user can type the URL before inserting it.
|
||||
return url === 'https://' || urlRegExp.test(url);
|
||||
};
|
||||
Reference in New Issue
Block a user