mirror of
https://github.com/facebook/lexical.git
synced 2025-05-17 15:18:47 +08:00
Fix all of the playground type errors (#2116)
* Fix several playground errors * Fix more types * Remove ts-ignores * Small fix * Small fix * Small changes * Remove unnecessary dep * Add katex types * Small fix
This commit is contained in:
12
libdefs/globals.d.ts
vendored
Normal file
12
libdefs/globals.d.ts
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
declare module '*.svg' {
|
||||
const content: any;
|
||||
export default content;
|
||||
}
|
||||
declare module '*.gif' {
|
||||
const content: any;
|
||||
export default content;
|
||||
}
|
||||
declare module '*.jpg' {
|
||||
const content: any;
|
||||
export default content;
|
||||
}
|
2760
package-lock.json
generated
2760
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -149,5 +149,8 @@
|
||||
"@playwright/test": {
|
||||
"playwright-core": "1.16.0-next-alpha-trueadm-fork"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/katex": "^0.14.0"
|
||||
}
|
||||
}
|
||||
|
@ -13,12 +13,14 @@ import type {
|
||||
EditorThemeClasses,
|
||||
} from 'lexical';
|
||||
|
||||
import {Class} from 'utility-types';
|
||||
|
||||
export function createHeadlessEditor(editorConfig?: {
|
||||
namespace?: string;
|
||||
editorState?: EditorState;
|
||||
theme?: EditorThemeClasses;
|
||||
parentEditor?: LexicalEditor;
|
||||
nodes?: $ReadOnlyArray<Class<LexicalNode>>;
|
||||
nodes?: ReadonlyArray<Class<LexicalNode>>;
|
||||
onError: (error: Error) => void;
|
||||
disableEvents?: boolean;
|
||||
readOnly?: boolean;
|
||||
|
8
packages/lexical-link/LexicalLink.d.ts
vendored
8
packages/lexical-link/LexicalLink.d.ts
vendored
@ -39,13 +39,17 @@ export declare class LinkNode extends ElementNode {
|
||||
}
|
||||
export function convertAnchorElement(domNode: Node): DOMConversionOutput;
|
||||
export function $createLinkNode(url: string): LinkNode;
|
||||
export function $isLinkNode(node: ?LexicalNode): node is LinkNode;
|
||||
export function $isLinkNode(
|
||||
node: LinkNode | LexicalNode | null | undefined,
|
||||
): node is LinkNode;
|
||||
export declare class AutoLinkNode extends LinkNode {
|
||||
static getType(): string;
|
||||
static clone(node: AutoLinkNode): AutoLinkNode;
|
||||
insertNewAfter(selection: RangeSelection): null | ElementNode;
|
||||
}
|
||||
export function $createAutoLinkNode(url: string): AutoLinkNode;
|
||||
export function $isAutoLinkNode(node: ?LexicalNode): node is AutoLinkNode;
|
||||
export function $isAutoLinkNode(
|
||||
node: LinkNode | LexicalNode | null | undefined,
|
||||
): node is AutoLinkNode;
|
||||
|
||||
export var TOGGLE_LINK_COMMAND: LexicalCommand<string | null>;
|
||||
|
2
packages/lexical-mark/LexicalMark.d.ts
vendored
2
packages/lexical-mark/LexicalMark.d.ts
vendored
@ -10,7 +10,7 @@ import {ElementNode, LexicalNode} from 'lexical';
|
||||
export declare class MarkNode extends ElementNode {
|
||||
__ids: Array<string>;
|
||||
clone(node: MarkNode): MarkNode;
|
||||
constructor(ids: Array<string>, key?: NodeKey): void;
|
||||
constructor(ids: Array<string>, key?: NodeKey);
|
||||
hasID(id: string): boolean;
|
||||
getIDs(): Array<string>;
|
||||
addID(id: string): void;
|
||||
|
@ -57,6 +57,13 @@ import YouTubePlugin from './plugins/YouTubePlugin';
|
||||
import ContentEditable from './ui/ContentEditable';
|
||||
import Placeholder from './ui/Placeholder';
|
||||
|
||||
interface customWindow extends Window {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
parent: any;
|
||||
}
|
||||
|
||||
declare const window: customWindow;
|
||||
|
||||
const skipCollaborationInit =
|
||||
window.parent != null && window.parent.frames.right === window;
|
||||
|
||||
|
@ -6,8 +6,6 @@
|
||||
*
|
||||
*/
|
||||
|
||||
import type {Provider} from '@lexical/yjs';
|
||||
|
||||
import {WebsocketProvider} from 'y-websocket';
|
||||
import {Doc} from 'yjs';
|
||||
|
||||
@ -21,7 +19,7 @@ const WEBSOCKET_ID = params.get('collabId') || '0';
|
||||
export function createWebsocketProvider(
|
||||
id: string,
|
||||
yjsDocMap: Map<string, Doc>,
|
||||
): Provider {
|
||||
): WebsocketProvider {
|
||||
let doc = yjsDocMap.get(id);
|
||||
|
||||
if (doc === undefined) {
|
||||
|
@ -22,13 +22,13 @@ const Context: React.Context<SettingsContextShape> = createContext({
|
||||
setOption: () => {
|
||||
return;
|
||||
},
|
||||
settings: {},
|
||||
settings: DEFAULT_SETTINGS,
|
||||
});
|
||||
|
||||
export const SettingsContext = ({
|
||||
children,
|
||||
}: {
|
||||
children: JSX.Element;
|
||||
children: JSX.Element | string | (JSX.Element | string)[];
|
||||
}): JSX.Element => {
|
||||
const [settings, setSettings] = useState(DEFAULT_SETTINGS);
|
||||
const setOption = useCallback((setting: SettingName, value: boolean) => {
|
||||
|
@ -16,14 +16,14 @@ type ContextShape = {
|
||||
historyState?: HistoryState;
|
||||
};
|
||||
|
||||
const Context: React$Context<ContextShape> = createContext({
|
||||
const Context: React.Context<ContextShape> = createContext({
|
||||
historyState: {current: null, redoStack: [], undoStack: []},
|
||||
});
|
||||
|
||||
export const SharedHistoryContext = ({
|
||||
children,
|
||||
}: {
|
||||
children: JSX.Element;
|
||||
children: JSX.Element | string | (JSX.Element | string)[];
|
||||
}): JSX.Element => {
|
||||
const historyContext = useMemo(
|
||||
() => ({historyState: createEmptyHistoryState()}),
|
||||
|
@ -10,7 +10,6 @@ import './setupEnv';
|
||||
import './index.css';
|
||||
|
||||
import * as React from 'react';
|
||||
// $FlowFixMe: Flow doesn't understand react-dom
|
||||
import {createRoot} from 'react-dom/client';
|
||||
|
||||
import App from './App';
|
||||
|
@ -11,7 +11,7 @@ import type {EditorConfig, LexicalNode, NodeKey} from 'lexical';
|
||||
import {TextNode} from 'lexical';
|
||||
|
||||
export class EmojiNode extends TextNode {
|
||||
__className: string;
|
||||
__className?: string;
|
||||
|
||||
static getType(): string {
|
||||
return 'emoji';
|
||||
@ -21,7 +21,7 @@ export class EmojiNode extends TextNode {
|
||||
return new EmojiNode(node.__className, node.__text, node.__key);
|
||||
}
|
||||
|
||||
constructor(className: string, text: string, key: void | NodeKey) {
|
||||
constructor(className: string, text: string, key?: NodeKey) {
|
||||
super(text, key);
|
||||
this.__className = className;
|
||||
}
|
||||
@ -40,17 +40,18 @@ export class EmojiNode extends TextNode {
|
||||
dom: HTMLElement,
|
||||
config: EditorConfig,
|
||||
): boolean {
|
||||
// $FlowFixMe: this will always be an element or null
|
||||
const inner: null | HTMLElement = dom.firstChild;
|
||||
const inner = dom.firstChild;
|
||||
if (inner === null) {
|
||||
return true;
|
||||
}
|
||||
super.updateDOM(prevNode, inner, config);
|
||||
super.updateDOM(prevNode, inner as HTMLElement, config);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function $isEmojiNode(node: LexicalNode | null): node is EmojiNode {
|
||||
export function $isEmojiNode(
|
||||
node: LexicalNode | null | undefined,
|
||||
): node is EmojiNode {
|
||||
return node instanceof EmojiNode;
|
||||
}
|
||||
|
||||
|
@ -152,7 +152,7 @@ export class EquationNode extends DecoratorNode<JSX.Element> {
|
||||
}
|
||||
|
||||
setEquation(equation: string): void {
|
||||
const writable = this.getWritable();
|
||||
const writable = this.getWritable<EquationNode>();
|
||||
writable.__equation = equation;
|
||||
}
|
||||
|
||||
@ -176,7 +176,7 @@ export function $createEquationNode(
|
||||
}
|
||||
|
||||
export function $isEquationNode(
|
||||
node: LexicalNode | null,
|
||||
node: LexicalNode | null | undefined,
|
||||
): node is EquationNode {
|
||||
return node instanceof EquationNode;
|
||||
}
|
||||
|
@ -6,12 +6,12 @@
|
||||
*
|
||||
*/
|
||||
|
||||
// $FlowFixMe: node modules are ignored by flow
|
||||
import {exportToSvg} from '@excalidraw/excalidraw';
|
||||
import {
|
||||
ExcalidrawElement,
|
||||
NonDeleted,
|
||||
} from '@excalidraw/excalidraw/types/element/types';
|
||||
import {AppState} from '@excalidraw/excalidraw/types/types';
|
||||
import * as React from 'react';
|
||||
import {useEffect, useState} from 'react';
|
||||
|
||||
@ -21,7 +21,7 @@ type Props = {
|
||||
/**
|
||||
* Configures the export setting for SVG/Canvas
|
||||
*/
|
||||
appState?: mixed;
|
||||
appState?: Partial<Omit<AppState, 'offsetTop' | 'offsetLeft'>> | null;
|
||||
/**
|
||||
* The css class applied to image to be rendered
|
||||
*/
|
||||
@ -37,7 +37,7 @@ type Props = {
|
||||
/**
|
||||
* The ref object to be used to render the image
|
||||
*/
|
||||
imageContainerRef: {current: null | HTMLElement};
|
||||
imageContainerRef: {current: null | HTMLDivElement};
|
||||
/**
|
||||
* The type of image to be rendered
|
||||
*/
|
||||
@ -78,9 +78,6 @@ const removeStyleFromSvg_HACK = (svg) => {
|
||||
export default function ExcalidrawImage({
|
||||
elements,
|
||||
imageContainerRef,
|
||||
className = '',
|
||||
height = null,
|
||||
width = null,
|
||||
appState = null,
|
||||
rootClassName = null,
|
||||
}: Props): JSX.Element {
|
||||
@ -88,6 +85,10 @@ export default function ExcalidrawImage({
|
||||
|
||||
useEffect(() => {
|
||||
const setContent = async () => {
|
||||
if (!appState) {
|
||||
return;
|
||||
}
|
||||
|
||||
const svg: Element = await exportToSvg({
|
||||
appState,
|
||||
elements,
|
||||
@ -107,8 +108,8 @@ export default function ExcalidrawImage({
|
||||
return (
|
||||
<div
|
||||
ref={imageContainerRef}
|
||||
className={rootClassName}
|
||||
dangerouslySetInnerHTML={{__html: Svg?.outerHTML}}
|
||||
className={rootClassName ?? ''}
|
||||
dangerouslySetInnerHTML={{__html: Svg?.outerHTML ?? ''}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -6,14 +6,11 @@
|
||||
*
|
||||
*/
|
||||
|
||||
// $FlowFixMe: node modules are ignored by flow
|
||||
import './ExcalidrawModal.css';
|
||||
|
||||
// $FlowFixMe: Flow doesn't have types for Excalidraw
|
||||
import Excalidraw from '@excalidraw/excalidraw';
|
||||
import * as React from 'react';
|
||||
import {useEffect, useRef, useState} from 'react';
|
||||
// $FlowFixMe: Flow doesn't see react-dom module
|
||||
import {ReactPortal, useEffect, useRef, useState} from 'react';
|
||||
import {createPortal} from 'react-dom';
|
||||
|
||||
import Button from '../../ui/Button';
|
||||
@ -40,11 +37,11 @@ type Props = {
|
||||
/**
|
||||
* Handle modal closing
|
||||
*/
|
||||
onHide: () => mixed;
|
||||
onHide: () => void;
|
||||
/**
|
||||
* Callback when the save button is clicked
|
||||
*/
|
||||
onSave: (elements: ReadonlyArray<ExcalidrawElementFragment>) => mixed;
|
||||
onSave: (elements: ReadonlyArray<ExcalidrawElementFragment>) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -59,7 +56,7 @@ export default function ExcalidrawModal({
|
||||
isShown = false,
|
||||
onHide,
|
||||
onDelete,
|
||||
}: Props): React.Portal | null {
|
||||
}: Props): ReactPortal | null {
|
||||
const excalidrawRef = useRef(null);
|
||||
const excaliDrawModelRef = useRef(null);
|
||||
const [discardModalOpen, setDiscardModalOpen] = useState(false);
|
||||
@ -75,8 +72,7 @@ export default function ExcalidrawModal({
|
||||
useEffect(() => {
|
||||
let modalOverlayElement = null;
|
||||
const clickOutsideHandler = (event: MouseEvent) => {
|
||||
// $FlowFixMe: event.target is always a Node on the DOM
|
||||
const target: HTMLElement = event.target;
|
||||
const target = event.target;
|
||||
if (
|
||||
excaliDrawModelRef.current !== null &&
|
||||
!excaliDrawModelRef.current.contains(target) &&
|
||||
|
@ -35,13 +35,13 @@ function ExcalidrawComponent({
|
||||
}: {
|
||||
data: string;
|
||||
nodeKey: NodeKey;
|
||||
}): React.Node {
|
||||
}): JSX.Element {
|
||||
const [editor] = useLexicalComposerContext();
|
||||
const [isModalOpen, setModalOpen] = useState<boolean>(
|
||||
data === '[]' && !editor.isReadOnly(),
|
||||
);
|
||||
const imageContainerRef = useRef<HTMLElement | null>(null);
|
||||
const buttonRef = useRef<HTMLElement | null>(null);
|
||||
const imageContainerRef = useRef<HTMLImageElement | null>(null);
|
||||
const buttonRef = useRef<HTMLButtonElement | null>(null);
|
||||
const [isSelected, setSelected, clearSelection] =
|
||||
useLexicalNodeSelection(nodeKey);
|
||||
const [isResizing, setIsResizing] = useState<boolean>(false);
|
||||
@ -79,14 +79,13 @@ function ExcalidrawComponent({
|
||||
CLICK_COMMAND,
|
||||
(event: MouseEvent) => {
|
||||
const buttonElem = buttonRef.current;
|
||||
// $FlowFixMe: this will work
|
||||
const eventTarget: Element = event.target;
|
||||
const eventTarget = event.target;
|
||||
|
||||
if (isResizing) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (buttonElem !== null && buttonElem.contains(eventTarget)) {
|
||||
if (buttonElem !== null && buttonElem.contains(eventTarget as Node)) {
|
||||
if (!event.shiftKey) {
|
||||
clearSelection();
|
||||
}
|
||||
@ -226,7 +225,7 @@ export class ExcalidrawNode extends DecoratorNode<JSX.Element> {
|
||||
}
|
||||
|
||||
setData(data: string): void {
|
||||
const self = this.getWritable();
|
||||
const self = this.getWritable<ExcalidrawNode>();
|
||||
self.__data = data;
|
||||
}
|
||||
|
||||
|
@ -65,7 +65,7 @@ function useSuspenseImage(src: string) {
|
||||
img.src = src;
|
||||
img.onload = () => {
|
||||
imageCache.add(src);
|
||||
resolve();
|
||||
resolve(null);
|
||||
};
|
||||
});
|
||||
}
|
||||
@ -83,11 +83,11 @@ function LazyImage({
|
||||
altText: string;
|
||||
className: string | null;
|
||||
height: 'inherit' | number;
|
||||
imageRef: {current: null | HTMLElement};
|
||||
imageRef: {current: null | HTMLImageElement};
|
||||
maxWidth: number;
|
||||
src: string;
|
||||
width: 'inherit' | number;
|
||||
}): React.Node {
|
||||
}): JSX.Element {
|
||||
useSuspenseImage(src);
|
||||
return (
|
||||
<img
|
||||
@ -124,7 +124,7 @@ function ImageComponent({
|
||||
showCaption: boolean;
|
||||
src: string;
|
||||
width: 'inherit' | number;
|
||||
}): React.Node {
|
||||
}): JSX.Element {
|
||||
const ref = useRef(null);
|
||||
const [isSelected, setSelected, clearSelection] =
|
||||
useLexicalNodeSelection(nodeKey);
|
||||
@ -135,7 +135,7 @@ function ImageComponent({
|
||||
const [selection, setSelection] = useState(null);
|
||||
|
||||
const onDelete = useCallback(
|
||||
(payload) => {
|
||||
(payload: KeyboardEvent) => {
|
||||
if (isSelected && $isNodeSelection($getSelection())) {
|
||||
const event: KeyboardEvent = payload;
|
||||
event.preventDefault();
|
||||
@ -157,10 +157,10 @@ function ImageComponent({
|
||||
editor.registerUpdateListener(({editorState}) => {
|
||||
setSelection(editorState.read(() => $getSelection()));
|
||||
}),
|
||||
editor.registerCommand(
|
||||
editor.registerCommand<MouseEvent>(
|
||||
CLICK_COMMAND,
|
||||
(payload) => {
|
||||
const event: MouseEvent = payload;
|
||||
const event = payload;
|
||||
|
||||
if (isResizing) {
|
||||
return true;
|
||||
@ -273,7 +273,7 @@ function ImageComponent({
|
||||
}
|
||||
initialEditorState={null}
|
||||
/>
|
||||
{showNestedEditorTreeView && <TreeViewPlugin />}
|
||||
{showNestedEditorTreeView === true ? <TreeViewPlugin /> : null}
|
||||
</LexicalNestedComposer>
|
||||
</div>
|
||||
)}
|
||||
@ -352,13 +352,13 @@ export class ImageNode extends DecoratorNode<JSX.Element> {
|
||||
width: 'inherit' | number,
|
||||
height: 'inherit' | number,
|
||||
): void {
|
||||
const writable = this.getWritable();
|
||||
const writable = this.getWritable<ImageNode>();
|
||||
writable.__width = width;
|
||||
writable.__height = height;
|
||||
}
|
||||
|
||||
setShowCaption(showCaption: boolean): void {
|
||||
const writable = this.getWritable();
|
||||
const writable = this.getWritable<ImageNode>();
|
||||
writable.__showCaption = showCaption;
|
||||
}
|
||||
|
||||
@ -411,6 +411,8 @@ export function $createImageNode(
|
||||
return new ImageNode(src, altText, maxWidth);
|
||||
}
|
||||
|
||||
export function $isImageNode(node: LexicalNode | null): node is ImageNode {
|
||||
export function $isImageNode(
|
||||
node: LexicalNode | null | undefined,
|
||||
): node is ImageNode {
|
||||
return node instanceof ImageNode;
|
||||
}
|
||||
|
@ -43,6 +43,8 @@ export function $createKeywordNode(keyword: string): KeywordNode {
|
||||
return new KeywordNode(keyword);
|
||||
}
|
||||
|
||||
export function $isKeywordNode(node: LexicalNode | null | undefined): boolean {
|
||||
export function $isKeywordNode(
|
||||
node: LexicalNode | null | undefined | undefined,
|
||||
): boolean {
|
||||
return node instanceof KeywordNode;
|
||||
}
|
||||
|
@ -45,6 +45,8 @@ export function $createMentionNode(mentionName: string): MentionNode {
|
||||
return mentionNode;
|
||||
}
|
||||
|
||||
export function $isMentionNode(node: LexicalNode | null): node is MentionNode {
|
||||
export function $isMentionNode(
|
||||
node: LexicalNode | null | undefined,
|
||||
): node is MentionNode {
|
||||
return node instanceof MentionNode;
|
||||
}
|
||||
|
@ -208,14 +208,14 @@ export class PollNode extends DecoratorNode<JSX.Element> {
|
||||
}
|
||||
|
||||
addOption(option: Option): void {
|
||||
const self = this.getWritable();
|
||||
const self = this.getWritable<PollNode>();
|
||||
const options = Array.from(self.__options);
|
||||
options.push(option);
|
||||
self.__options = options;
|
||||
}
|
||||
|
||||
deleteOption(option: Option): void {
|
||||
const self = this.getWritable();
|
||||
const self = this.getWritable<PollNode>();
|
||||
const options = Array.from(self.__options);
|
||||
const index = options.indexOf(option);
|
||||
options.splice(index, 1);
|
||||
@ -223,7 +223,7 @@ export class PollNode extends DecoratorNode<JSX.Element> {
|
||||
}
|
||||
|
||||
setOptionText(option: Option, text: string): void {
|
||||
const self = this.getWritable();
|
||||
const self = this.getWritable<PollNode>();
|
||||
const clonedOption = cloneOption(option, text);
|
||||
const options = Array.from(self.__options);
|
||||
const index = options.indexOf(option);
|
||||
@ -232,7 +232,7 @@ export class PollNode extends DecoratorNode<JSX.Element> {
|
||||
}
|
||||
|
||||
toggleVote(option: Option, clientID: number): void {
|
||||
const self = this.getWritable();
|
||||
const self = this.getWritable<PollNode>();
|
||||
const votes = option.votes;
|
||||
const votesClone = Array.from(votes);
|
||||
const voteIndex = votes.indexOf(clientID);
|
||||
@ -273,6 +273,8 @@ export function $createPollNode(question: string): PollNode {
|
||||
return new PollNode(question);
|
||||
}
|
||||
|
||||
export function $isPollNode(node: LexicalNode | null): node is PollNode {
|
||||
export function $isPollNode(
|
||||
node: LexicalNode | null | undefined,
|
||||
): node is PollNode {
|
||||
return node instanceof PollNode;
|
||||
}
|
||||
|
@ -26,7 +26,6 @@ import {
|
||||
} from 'lexical';
|
||||
import * as React from 'react';
|
||||
import {useEffect, useRef} from 'react';
|
||||
// $FlowFixMe
|
||||
import {createPortal} from 'react-dom';
|
||||
import useLayoutEffect from 'shared/useLayoutEffect';
|
||||
|
||||
@ -59,7 +58,7 @@ function StickyComponent({
|
||||
y: number;
|
||||
}): JSX.Element {
|
||||
const [editor] = useLexicalComposerContext();
|
||||
const stickyContainerRef = useRef<null | HTMLElement>(null);
|
||||
const stickyContainerRef = useRef<null | HTMLDivElement>(null);
|
||||
const positioningRef = useRef<{
|
||||
isDragging: boolean;
|
||||
offsetX: number;
|
||||
@ -228,14 +227,14 @@ function StickyComponent({
|
||||
onClick={handleDelete}
|
||||
className="delete"
|
||||
aria-label="Delete sticky note"
|
||||
type="Delete">
|
||||
title="Delete">
|
||||
X
|
||||
</button>
|
||||
<button
|
||||
onClick={handleColorChange}
|
||||
className="color"
|
||||
aria-label="Change sticky note color"
|
||||
type="Color">
|
||||
title="Color">
|
||||
<i className="bucket" />
|
||||
</button>
|
||||
<LexicalNestedComposer
|
||||
@ -312,14 +311,14 @@ export class StickyNode extends DecoratorNode<JSX.Element> {
|
||||
}
|
||||
|
||||
setPosition(x: number, y: number): void {
|
||||
const writable = this.getWritable();
|
||||
const writable = this.getWritable<StickyNode>();
|
||||
writable.__x = x;
|
||||
writable.__y = y;
|
||||
$setSelection(null);
|
||||
}
|
||||
|
||||
toggleColor(): void {
|
||||
const writable = this.getWritable();
|
||||
const writable = this.getWritable<StickyNode>();
|
||||
writable.__color = writable.__color === 'pink' ? 'yellow' : 'pink';
|
||||
}
|
||||
|
||||
@ -341,7 +340,9 @@ export class StickyNode extends DecoratorNode<JSX.Element> {
|
||||
}
|
||||
}
|
||||
|
||||
export function $isStickyNode(node: LexicalNode | null): node is StickyNode {
|
||||
export function $isStickyNode(
|
||||
node: LexicalNode | null | undefined,
|
||||
): node is StickyNode {
|
||||
return node instanceof StickyNode;
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,13 @@ import {DecoratorBlockNode} from '@lexical/react/LexicalDecoratorBlockNode';
|
||||
import * as React from 'react';
|
||||
import {useCallback, useEffect, useRef, useState} from 'react';
|
||||
|
||||
interface customWindow extends Window {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
twttr?: any;
|
||||
}
|
||||
|
||||
declare const window: customWindow;
|
||||
|
||||
const WIDGET_SCRIPT_URL = 'https://platform.twitter.com/widgets.js';
|
||||
|
||||
const getHasScriptCached = () =>
|
||||
@ -20,9 +27,9 @@ const getHasScriptCached = () =>
|
||||
|
||||
type TweetComponentProps = Readonly<{
|
||||
format: ElementFormatType | null;
|
||||
loadingComponent?: JSX.Element;
|
||||
loadingComponent?: JSX.Element | string;
|
||||
nodeKey: NodeKey;
|
||||
onError?: (error?: Error) => void;
|
||||
onError?: (error: string) => void;
|
||||
onLoad?: () => void;
|
||||
tweetID: string;
|
||||
}>;
|
||||
@ -37,7 +44,7 @@ function TweetComponent({
|
||||
}: TweetComponentProps) {
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const previousTweetIDRef = useRef(null);
|
||||
const previousTweetIDRef = useRef<string>('');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const createTweet = useCallback(async () => {
|
||||
@ -49,9 +56,9 @@ function TweetComponent({
|
||||
if (onLoad) {
|
||||
onLoad();
|
||||
}
|
||||
} catch (e) {
|
||||
} catch (error) {
|
||||
if (onError) {
|
||||
onError(e);
|
||||
onError(String(error));
|
||||
}
|
||||
}
|
||||
}, [onError, onLoad, tweetID]);
|
||||
@ -71,7 +78,9 @@ function TweetComponent({
|
||||
createTweet();
|
||||
}
|
||||
|
||||
previousTweetIDRef.current = tweetID;
|
||||
if (previousTweetIDRef) {
|
||||
previousTweetIDRef.current = tweetID;
|
||||
}
|
||||
}
|
||||
}, [createTweet, onError, tweetID]);
|
||||
|
||||
@ -97,7 +106,7 @@ export class TweetNode extends DecoratorBlockNode<JSX.Element> {
|
||||
return new TweetNode(node.__id, node.__format, node.__key);
|
||||
}
|
||||
|
||||
constructor(id: string, format: ElementFormatType | null, key?: NodeKey) {
|
||||
constructor(id: string, format?: ElementFormatType | null, key?: NodeKey) {
|
||||
super(format, key);
|
||||
this.__id = id;
|
||||
}
|
||||
@ -126,6 +135,8 @@ export function $createTweetNode(tweetID: string): TweetNode {
|
||||
return new TweetNode(tweetID);
|
||||
}
|
||||
|
||||
export function $isTweetNode(node: LexicalNode | null): node is TweetNode {
|
||||
export function $isTweetNode(
|
||||
node: TweetNode | LexicalNode | null | undefined,
|
||||
): node is TweetNode {
|
||||
return node instanceof TweetNode;
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ export class YouTubeNode extends DecoratorBlockNode<JSX.Element> {
|
||||
return new YouTubeNode(node.__id, node.__format, node.__key);
|
||||
}
|
||||
|
||||
constructor(id: string, format: ElementFormatType | null, key?: NodeKey) {
|
||||
constructor(id: string, format?: ElementFormatType | null, key?: NodeKey) {
|
||||
super(format, key);
|
||||
this.__id = id;
|
||||
}
|
||||
@ -73,6 +73,8 @@ export function $createYouTubeNode(videoID: string): YouTubeNode {
|
||||
return new YouTubeNode(videoID);
|
||||
}
|
||||
|
||||
export function $isYouTubeNode(node: LexicalNode | null): node is YouTubeNode {
|
||||
export function $isYouTubeNode(
|
||||
node: YouTubeNode | LexicalNode | null | undefined,
|
||||
): node is YouTubeNode {
|
||||
return node instanceof YouTubeNode;
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ export default function ActionsPlugin({
|
||||
editor.registerReadOnlyListener((readOnly) => {
|
||||
setIsReadyOnly(readOnly);
|
||||
}),
|
||||
editor.registerCommand(
|
||||
editor.registerCommand<boolean>(
|
||||
CONNECTED_COMMAND,
|
||||
(payload) => {
|
||||
const isConnected = payload;
|
||||
@ -113,7 +113,10 @@ export default function ActionsPlugin({
|
||||
{SUPPORT_SPEECH_RECOGNITION && (
|
||||
<button
|
||||
onClick={() => {
|
||||
editor.dispatchCommand(SPEECH_TO_TEXT_COMMAND, !isSpeechToText);
|
||||
editor.dispatchCommand<boolean>(
|
||||
SPEECH_TO_TEXT_COMMAND,
|
||||
!isSpeechToText,
|
||||
);
|
||||
setIsSpeechToText(!isSpeechToText);
|
||||
}}
|
||||
className={
|
||||
@ -207,7 +210,7 @@ function ShowClearDialog({
|
||||
<div className="Modal__content">
|
||||
<Button
|
||||
onClick={() => {
|
||||
editor.dispatchCommand(CLEAR_EDITOR_COMMAND);
|
||||
editor.dispatchCommand<void>(CLEAR_EDITOR_COMMAND, undefined);
|
||||
editor.focus();
|
||||
onClose();
|
||||
}}>
|
||||
|
@ -70,7 +70,7 @@ function useTypeahead(editor: LexicalEditor): void {
|
||||
let anchorNode = anchor.getNode();
|
||||
let anchorNodeOffset = anchor.offset;
|
||||
if (anchorNode.getKey() === currentTypeaheadNode.getKey()) {
|
||||
anchorNode = anchorNode.getPreviousSibling();
|
||||
anchorNode = anchorNode.getPreviousSibling() as TextNode;
|
||||
if ($isTextNode(anchorNode)) {
|
||||
anchorNodeOffset = anchorNode.getTextContent().length;
|
||||
}
|
||||
@ -78,7 +78,7 @@ function useTypeahead(editor: LexicalEditor): void {
|
||||
let focusNode = focus.getNode();
|
||||
let focusNodeOffset = focus.offset;
|
||||
if (focusNode.getKey() === currentTypeaheadNode.getKey()) {
|
||||
focusNode = focusNode.getPreviousSibling();
|
||||
focusNode = focusNode.getPreviousSibling() as TextNode;
|
||||
if ($isTextNode(focusNode)) {
|
||||
focusNodeOffset = focusNode.getTextContent().length;
|
||||
}
|
||||
@ -269,7 +269,7 @@ class TypeaheadServer {
|
||||
): {cancel: () => void; promise: () => Promise<string | null>} => {
|
||||
let isCancelled = false;
|
||||
|
||||
const promise = () =>
|
||||
const promise = (): Promise<string | null> =>
|
||||
new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
const response = this.DATABASE[text] ?? null;
|
||||
|
@ -6,8 +6,6 @@
|
||||
*
|
||||
*/
|
||||
|
||||
import type {ElementNode, LexicalEditor, RangeSelection} from 'lexical';
|
||||
|
||||
import {$isCodeHighlightNode} from '@lexical/code';
|
||||
import {$isLinkNode, TOGGLE_LINK_COMMAND} from '@lexical/link';
|
||||
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
|
||||
@ -18,13 +16,15 @@ import {
|
||||
$isRangeSelection,
|
||||
$isTextNode,
|
||||
COMMAND_PRIORITY_LOW,
|
||||
ElementNode,
|
||||
FORMAT_TEXT_COMMAND,
|
||||
LexicalEditor,
|
||||
RangeSelection,
|
||||
SELECTION_CHANGE_COMMAND,
|
||||
TextNode,
|
||||
} from 'lexical';
|
||||
import {useCallback, useEffect, useRef, useState} from 'react';
|
||||
import * as React from 'react';
|
||||
// $FlowFixMe
|
||||
import {createPortal} from 'react-dom';
|
||||
|
||||
function setPopupPosition(
|
||||
@ -71,7 +71,7 @@ function FloatingCharacterStylesEditor({
|
||||
isSuperscript: boolean;
|
||||
isUnderline: boolean;
|
||||
}): JSX.Element {
|
||||
const popupCharStylesEditorRef = useRef<HTMLElement | null>(null);
|
||||
const popupCharStylesEditorRef = useRef<HTMLDivElement | null>(null);
|
||||
const mouseDownRef = useRef(false);
|
||||
|
||||
const insertLink = useCallback(() => {
|
||||
@ -95,6 +95,7 @@ function FloatingCharacterStylesEditor({
|
||||
const rootElement = editor.getRootElement();
|
||||
if (
|
||||
selection !== null &&
|
||||
nativeSelection !== null &&
|
||||
!nativeSelection.isCollapsed &&
|
||||
rootElement !== null &&
|
||||
rootElement.contains(nativeSelection.anchorNode)
|
||||
@ -106,7 +107,7 @@ function FloatingCharacterStylesEditor({
|
||||
if (nativeSelection.anchorNode === rootElement) {
|
||||
let inner = rootElement;
|
||||
while (inner.firstElementChild != null) {
|
||||
inner = inner.firstElementChild;
|
||||
inner = inner.firstElementChild as HTMLElement;
|
||||
}
|
||||
rect = inner.getBoundingClientRect();
|
||||
} else {
|
||||
@ -240,7 +241,7 @@ function getSelectedNode(selection: RangeSelection): TextNode | ElementNode {
|
||||
}
|
||||
}
|
||||
|
||||
function useCharacterStylesPopup(editor: LexicalEditor): JSX.Element {
|
||||
function useCharacterStylesPopup(editor: LexicalEditor): JSX.Element | null {
|
||||
const [isText, setIsText] = useState(false);
|
||||
const [isLink, setIsLink] = useState(false);
|
||||
const [isBold, setIsBold] = useState(false);
|
||||
@ -258,14 +259,19 @@ function useCharacterStylesPopup(editor: LexicalEditor): JSX.Element {
|
||||
const rootElement = editor.getRootElement();
|
||||
|
||||
if (
|
||||
!$isRangeSelection(selection) ||
|
||||
rootElement === null ||
|
||||
!rootElement.contains(nativeSelection.anchorNode)
|
||||
nativeSelection !== null &&
|
||||
(!$isRangeSelection(selection) ||
|
||||
rootElement === null ||
|
||||
!rootElement.contains(nativeSelection.anchorNode))
|
||||
) {
|
||||
setIsText(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$isRangeSelection(selection)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const node = getSelectedNode(selection);
|
||||
|
||||
// Update text format
|
||||
@ -329,7 +335,7 @@ function useCharacterStylesPopup(editor: LexicalEditor): JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
export default function CharacterStylesPopupPlugin(): JSX.Element {
|
||||
export default function CharacterStylesPopupPlugin(): JSX.Element | null {
|
||||
const [editor] = useLexicalComposerContext();
|
||||
return useCharacterStylesPopup(editor);
|
||||
}
|
||||
|
@ -39,7 +39,6 @@ import {
|
||||
} from 'lexical';
|
||||
import {useCallback, useEffect, useMemo, useRef, useState} from 'react';
|
||||
import * as React from 'react';
|
||||
// $FlowFixMe: Flow doesn't see react-dom module
|
||||
import {createPortal} from 'react-dom';
|
||||
import useLayoutEffect from 'shared/useLayoutEffect';
|
||||
|
||||
@ -50,8 +49,6 @@ import Button from '../ui/Button';
|
||||
import ContentEditable from '../ui/ContentEditable.jsx';
|
||||
import Placeholder from '../ui/Placeholder.jsx';
|
||||
|
||||
type RtfObject = Record<string, unknown>;
|
||||
|
||||
function AddCommentBox({
|
||||
anchorKey,
|
||||
editor,
|
||||
@ -385,7 +382,7 @@ function CommentsPanelFooter({
|
||||
footerRef,
|
||||
submitAddComment,
|
||||
}: {
|
||||
footerRef: {current: null | HTMLElement};
|
||||
footerRef: {current: null | HTMLDivElement};
|
||||
submitAddComment: (
|
||||
commentOrThread: Comment | Thread,
|
||||
isInlineComment: boolean,
|
||||
@ -448,7 +445,7 @@ function CommentsPanelListComment({
|
||||
// eslint-disable-next-line no-shadow
|
||||
thread?: Thread,
|
||||
) => void;
|
||||
rtf: RtfObject;
|
||||
rtf: Intl.RelativeTimeFormat;
|
||||
thread?: Thread;
|
||||
}): JSX.Element {
|
||||
const seconds = Math.round((comment.timeStamp - performance.now()) / 1000);
|
||||
@ -496,7 +493,7 @@ function CommentsPanelList({
|
||||
activeIDs: Array<string>;
|
||||
comments: Comments;
|
||||
deleteComment: (commentOrThread: Comment, thread?: Thread) => void;
|
||||
listRef: {current: null | HTMLElement};
|
||||
listRef: {current: null | HTMLUListElement};
|
||||
markNodeMap: Map<string, Set<NodeKey>>;
|
||||
submitAddComment: (
|
||||
commentOrThread: Comment | Thread,
|
||||
@ -506,9 +503,8 @@ function CommentsPanelList({
|
||||
}): JSX.Element {
|
||||
const [editor] = useLexicalComposerContext();
|
||||
const [counter, setCounter] = useState(0);
|
||||
const rtf: RtfObject = useMemo(
|
||||
const rtf = useMemo(
|
||||
() =>
|
||||
// $FlowFixMe: Flow hasn't got types yet
|
||||
new Intl.RelativeTimeFormat('en', {
|
||||
localeMatcher: 'best fit',
|
||||
numeric: 'auto',
|
||||
@ -545,7 +541,7 @@ function CommentsPanelList({
|
||||
editor.update(
|
||||
() => {
|
||||
const markNodeKey = Array.from(markNodeKeys)[0];
|
||||
const markNode = $getNodeByKey(markNodeKey);
|
||||
const markNode = $getNodeByKey<MarkNode>(markNodeKey);
|
||||
if ($isMarkNode(markNode)) {
|
||||
markNode.selectStart();
|
||||
}
|
||||
@ -554,7 +550,7 @@ function CommentsPanelList({
|
||||
onUpdate() {
|
||||
// Restore selection to the previous element
|
||||
if (activeElement !== null) {
|
||||
activeElement.focus();
|
||||
(activeElement as HTMLElement).focus();
|
||||
}
|
||||
},
|
||||
},
|
||||
|
@ -21,10 +21,13 @@ import {useEffect} from 'react';
|
||||
|
||||
import {$createEquationNode, EquationNode} from '../nodes/EquationNode';
|
||||
|
||||
export const INSERT_EQUATION_COMMAND: LexicalCommand<{
|
||||
type CommandPayload = {
|
||||
equation: string;
|
||||
inline: boolean;
|
||||
}> = createCommand();
|
||||
};
|
||||
|
||||
export const INSERT_EQUATION_COMMAND: LexicalCommand<CommandPayload> =
|
||||
createCommand();
|
||||
|
||||
export default function EquationsPlugin(): JSX.Element | null {
|
||||
const [editor] = useLexicalComposerContext();
|
||||
@ -36,7 +39,7 @@ export default function EquationsPlugin(): JSX.Element | null {
|
||||
);
|
||||
}
|
||||
|
||||
return editor.registerCommand(
|
||||
return editor.registerCommand<CommandPayload>(
|
||||
INSERT_EQUATION_COMMAND,
|
||||
(payload) => {
|
||||
const {equation, inline} = payload;
|
||||
|
@ -34,7 +34,7 @@ export default function ImagesPlugin(): JSX.Element {
|
||||
throw new Error('ImagesPlugin: ImageNode not registered on editor');
|
||||
}
|
||||
|
||||
return editor.registerCommand(
|
||||
return editor.registerCommand<InsertImagePayload>(
|
||||
INSERT_IMAGE_COMMAND,
|
||||
(payload) => {
|
||||
const selection = $getSelection();
|
||||
|
@ -199,7 +199,7 @@ export const TABLE: ElementTransformer = {
|
||||
table.append(tableRow);
|
||||
|
||||
for (let i = 0; i < maxCells; i++) {
|
||||
tableRow.append(i < cells.length ? cells[i] : createTableCell());
|
||||
tableRow.append(i < cells.length ? cells[i] : createTableCell(null));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,6 @@ import {
|
||||
} from 'lexical';
|
||||
import {startTransition, useCallback, useEffect, useRef, useState} from 'react';
|
||||
import * as React from 'react';
|
||||
// $FlowFixMe
|
||||
import {createPortal} from 'react-dom';
|
||||
import useLayoutEffect from 'shared/useLayoutEffect';
|
||||
|
||||
@ -677,10 +676,10 @@ function MentionsTypeahead({
|
||||
|
||||
useEffect(() => {
|
||||
return mergeRegister(
|
||||
editor.registerCommand(
|
||||
editor.registerCommand<KeyboardEvent>(
|
||||
KEY_ARROW_DOWN_COMMAND,
|
||||
(payload) => {
|
||||
const event: KeyboardEvent = payload;
|
||||
const event = payload;
|
||||
if (results !== null && selectedIndex !== null) {
|
||||
if (
|
||||
selectedIndex < SUGGESTION_LIST_LENGTH_LIMIT - 1 &&
|
||||
@ -695,10 +694,10 @@ function MentionsTypeahead({
|
||||
},
|
||||
COMMAND_PRIORITY_LOW,
|
||||
),
|
||||
editor.registerCommand(
|
||||
editor.registerCommand<KeyboardEvent>(
|
||||
KEY_ARROW_UP_COMMAND,
|
||||
(payload) => {
|
||||
const event: KeyboardEvent = payload;
|
||||
const event = payload;
|
||||
if (results !== null && selectedIndex !== null) {
|
||||
if (selectedIndex !== 0) {
|
||||
updateSelectedIndex(selectedIndex - 1);
|
||||
@ -710,10 +709,10 @@ function MentionsTypeahead({
|
||||
},
|
||||
COMMAND_PRIORITY_LOW,
|
||||
),
|
||||
editor.registerCommand(
|
||||
editor.registerCommand<KeyboardEvent>(
|
||||
KEY_ESCAPE_COMMAND,
|
||||
(payload) => {
|
||||
const event: KeyboardEvent = payload;
|
||||
const event = payload;
|
||||
if (results === null || selectedIndex === null) {
|
||||
return false;
|
||||
}
|
||||
@ -724,10 +723,10 @@ function MentionsTypeahead({
|
||||
},
|
||||
COMMAND_PRIORITY_LOW,
|
||||
),
|
||||
editor.registerCommand(
|
||||
editor.registerCommand<KeyboardEvent>(
|
||||
KEY_TAB_COMMAND,
|
||||
(payload) => {
|
||||
const event: KeyboardEvent = payload;
|
||||
const event = payload;
|
||||
if (results === null || selectedIndex === null) {
|
||||
return false;
|
||||
}
|
||||
|
@ -29,13 +29,13 @@ export default function PollPlugin(): JSX.Element {
|
||||
throw new Error('PollPlugin: PollNode not registered on editor');
|
||||
}
|
||||
|
||||
return editor.registerCommand(
|
||||
return editor.registerCommand<string>(
|
||||
INSERT_POLL_COMMAND,
|
||||
(payload) => {
|
||||
const selection = $getSelection();
|
||||
|
||||
if ($isRangeSelection(selection)) {
|
||||
const question: string = payload;
|
||||
const question = payload;
|
||||
const pollNode = $createPollNode(question);
|
||||
|
||||
if ($isRootNode(selection.anchor.getNode())) {
|
||||
|
@ -21,6 +21,15 @@ import {useEffect, useRef, useState} from 'react';
|
||||
|
||||
import useReport from '../hooks/useReport';
|
||||
|
||||
interface customWindow extends Window {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
webkitSpeechRecognition?: any;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
SpeechRecognition?: any;
|
||||
}
|
||||
|
||||
declare const window: customWindow;
|
||||
|
||||
export const SPEECH_TO_TEXT_COMMAND: LexicalCommand<boolean> = createCommand();
|
||||
|
||||
const VOICE_COMMANDS: Readonly<
|
||||
@ -33,10 +42,10 @@ const VOICE_COMMANDS: Readonly<
|
||||
selection.insertParagraph();
|
||||
},
|
||||
redo: ({editor}) => {
|
||||
editor.dispatchCommand(REDO_COMMAND);
|
||||
editor.dispatchCommand(REDO_COMMAND, undefined);
|
||||
},
|
||||
undo: ({editor}) => {
|
||||
editor.dispatchCommand(UNDO_COMMAND);
|
||||
editor.dispatchCommand(UNDO_COMMAND, undefined);
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -30,8 +30,7 @@ import {
|
||||
$setSelection,
|
||||
} from 'lexical';
|
||||
import * as React from 'react';
|
||||
import {useCallback, useEffect, useMemo, useRef, useState} from 'react';
|
||||
// $FlowFixMe
|
||||
import {ReactPortal, useCallback, useEffect, useRef, useState} from 'react';
|
||||
import {createPortal} from 'react-dom';
|
||||
|
||||
type TableCellActionMenuProps = Readonly<{
|
||||
@ -48,7 +47,7 @@ function TableActionMenu({
|
||||
contextRef,
|
||||
}: TableCellActionMenuProps) {
|
||||
const [editor] = useLexicalComposerContext();
|
||||
const dropDownRef = useRef();
|
||||
const dropDownRef = useRef<HTMLDivElement>();
|
||||
const [tableCellNode, updateTableCellNode] = useState(_tableCellNode);
|
||||
const [selectionCounts, updateSelectionCounts] = useState({
|
||||
columns: 1,
|
||||
@ -62,7 +61,7 @@ function TableActionMenu({
|
||||
|
||||
if (nodeUpdated) {
|
||||
editor.getEditorState().read(() => {
|
||||
updateTableCellNode(tableCellNode.getLatest());
|
||||
updateTableCellNode(tableCellNode.getLatest<TableCellNode>());
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -133,7 +132,7 @@ function TableActionMenu({
|
||||
tableSelection.clearHighlight();
|
||||
|
||||
tableNode.markDirty();
|
||||
updateTableCellNode(tableCellNode.getLatest());
|
||||
updateTableCellNode(tableCellNode.getLatest<TableCellNode>());
|
||||
}
|
||||
|
||||
$setSelection(null);
|
||||
@ -252,78 +251,72 @@ function TableActionMenu({
|
||||
});
|
||||
}, [editor, tableCellNode, clearTableSelection, onClose]);
|
||||
|
||||
const toggleTableRowIsHeader = useCallback(
|
||||
(isHeader) => {
|
||||
editor.update(() => {
|
||||
const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode);
|
||||
const toggleTableRowIsHeader = useCallback(() => {
|
||||
editor.update(() => {
|
||||
const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode);
|
||||
|
||||
const tableRowIndex = $getTableRowIndexFromTableCellNode(tableCellNode);
|
||||
const tableRowIndex = $getTableRowIndexFromTableCellNode(tableCellNode);
|
||||
|
||||
const tableRows = tableNode.getChildren();
|
||||
const tableRows = tableNode.getChildren();
|
||||
|
||||
if (tableRowIndex >= tableRows.length || tableRowIndex < 0) {
|
||||
throw new Error('Expected table cell to be inside of table row.');
|
||||
if (tableRowIndex >= tableRows.length || tableRowIndex < 0) {
|
||||
throw new Error('Expected table cell to be inside of table row.');
|
||||
}
|
||||
|
||||
const tableRow = tableRows[tableRowIndex];
|
||||
|
||||
if (!$isTableRowNode(tableRow)) {
|
||||
throw new Error('Expected table row');
|
||||
}
|
||||
|
||||
tableRow.getChildren().forEach((tableCell) => {
|
||||
if (!$isTableCellNode(tableCell)) {
|
||||
throw new Error('Expected table cell');
|
||||
}
|
||||
|
||||
const tableRow = tableRows[tableRowIndex];
|
||||
tableCell.toggleHeaderStyle(TableCellHeaderStates.ROW);
|
||||
});
|
||||
|
||||
clearTableSelection();
|
||||
onClose();
|
||||
});
|
||||
}, [editor, tableCellNode, clearTableSelection, onClose]);
|
||||
|
||||
const toggleTableColumnIsHeader = useCallback(() => {
|
||||
editor.update(() => {
|
||||
const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode);
|
||||
|
||||
const tableColumnIndex =
|
||||
$getTableColumnIndexFromTableCellNode(tableCellNode);
|
||||
|
||||
const tableRows = tableNode.getChildren();
|
||||
|
||||
for (let r = 0; r < tableRows.length; r++) {
|
||||
const tableRow = tableRows[r];
|
||||
|
||||
if (!$isTableRowNode(tableRow)) {
|
||||
throw new Error('Expected table row');
|
||||
}
|
||||
|
||||
tableRow.getChildren().forEach((tableCell) => {
|
||||
if (!$isTableCellNode(tableCell)) {
|
||||
throw new Error('Expected table cell');
|
||||
}
|
||||
const tableCells = tableRow.getChildren();
|
||||
|
||||
tableCell.toggleHeaderStyle(TableCellHeaderStates.ROW);
|
||||
});
|
||||
|
||||
clearTableSelection();
|
||||
onClose();
|
||||
});
|
||||
},
|
||||
[editor, tableCellNode, clearTableSelection, onClose],
|
||||
);
|
||||
|
||||
const toggleTableColumnIsHeader = useCallback(
|
||||
(isHeader) => {
|
||||
editor.update(() => {
|
||||
const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode);
|
||||
|
||||
const tableColumnIndex =
|
||||
$getTableColumnIndexFromTableCellNode(tableCellNode);
|
||||
|
||||
const tableRows = tableNode.getChildren();
|
||||
|
||||
for (let r = 0; r < tableRows.length; r++) {
|
||||
const tableRow = tableRows[r];
|
||||
|
||||
if (!$isTableRowNode(tableRow)) {
|
||||
throw new Error('Expected table row');
|
||||
}
|
||||
|
||||
const tableCells = tableRow.getChildren();
|
||||
|
||||
if (tableColumnIndex >= tableCells.length || tableColumnIndex < 0) {
|
||||
throw new Error('Expected table cell to be inside of table row.');
|
||||
}
|
||||
|
||||
const tableCell = tableCells[tableColumnIndex];
|
||||
|
||||
if (!$isTableCellNode(tableCell)) {
|
||||
throw new Error('Expected table cell');
|
||||
}
|
||||
|
||||
tableCell.toggleHeaderStyle(TableCellHeaderStates.COLUMN);
|
||||
if (tableColumnIndex >= tableCells.length || tableColumnIndex < 0) {
|
||||
throw new Error('Expected table cell to be inside of table row.');
|
||||
}
|
||||
|
||||
clearTableSelection();
|
||||
onClose();
|
||||
});
|
||||
},
|
||||
[editor, tableCellNode, clearTableSelection, onClose],
|
||||
);
|
||||
const tableCell = tableCells[tableColumnIndex];
|
||||
|
||||
if (!$isTableCellNode(tableCell)) {
|
||||
throw new Error('Expected table cell');
|
||||
}
|
||||
|
||||
tableCell.toggleHeaderStyle(TableCellHeaderStates.COLUMN);
|
||||
}
|
||||
|
||||
clearTableSelection();
|
||||
onClose();
|
||||
});
|
||||
}, [editor, tableCellNode, clearTableSelection, onClose]);
|
||||
|
||||
return createPortal(
|
||||
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
||||
@ -404,7 +397,7 @@ function TableActionMenu({
|
||||
);
|
||||
}
|
||||
|
||||
function TableCellActionMenuContainer(): React.MixedElement {
|
||||
function TableCellActionMenuContainer(): JSX.Element {
|
||||
const [editor] = useLexicalComposerContext();
|
||||
|
||||
const menuButtonRef = useRef(null);
|
||||
@ -531,15 +524,6 @@ function TableCellActionMenuContainer(): React.MixedElement {
|
||||
);
|
||||
}
|
||||
|
||||
export default function TableActionMenuPlugin(): React.Portal {
|
||||
const [editor] = useLexicalComposerContext();
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
createPortal(
|
||||
<TableCellActionMenuContainer editor={editor} />,
|
||||
document.body,
|
||||
),
|
||||
[editor],
|
||||
);
|
||||
export default function TableActionMenuPlugin(): ReactPortal {
|
||||
return createPortal(<TableCellActionMenuContainer />, document.body);
|
||||
}
|
||||
|
@ -27,8 +27,14 @@ import {
|
||||
SELECTION_CHANGE_COMMAND,
|
||||
} from 'lexical';
|
||||
import * as React from 'react';
|
||||
import {useCallback, useEffect, useMemo, useRef, useState} from 'react';
|
||||
// $FlowFixMe
|
||||
import {
|
||||
ReactPortal,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import {createPortal} from 'react-dom';
|
||||
|
||||
type MousePosition = {
|
||||
@ -43,7 +49,7 @@ const MIN_COLUMN_WIDTH = 50;
|
||||
|
||||
function TableCellResizer({editor}: {editor: LexicalEditor}): JSX.Element {
|
||||
const targetRef = useRef<HTMLElement | null>(null);
|
||||
const resizerRef = useRef<HTMLElement | null>(null);
|
||||
const resizerRef = useRef<HTMLDivElement | null>(null);
|
||||
const tableRectRef = useRef<ClientRect | null>(null);
|
||||
|
||||
const mouseStartPosRef = useRef<MousePosition | null>(null);
|
||||
@ -83,8 +89,7 @@ function TableCellResizer({editor}: {editor: LexicalEditor}): JSX.Element {
|
||||
useEffect(() => {
|
||||
const onMouseMove = (event: MouseEvent) => {
|
||||
setTimeout(() => {
|
||||
// $FlowFixMe: event.target is always a Node on the DOM
|
||||
const target: HTMLElement = event.target;
|
||||
const target = event.target;
|
||||
|
||||
if (draggingDirection) {
|
||||
updateMouseCurrentPos({
|
||||
@ -94,13 +99,13 @@ function TableCellResizer({editor}: {editor: LexicalEditor}): JSX.Element {
|
||||
return;
|
||||
}
|
||||
|
||||
if (resizerRef.current && resizerRef.current.contains(target)) {
|
||||
if (resizerRef.current && resizerRef.current.contains(target as Node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (targetRef.current !== target) {
|
||||
targetRef.current = target;
|
||||
const cell = getCellFromTarget(target);
|
||||
targetRef.current = target as HTMLElement;
|
||||
const cell = getCellFromTarget(target as HTMLElement);
|
||||
|
||||
if (cell && activeCell !== cell) {
|
||||
editor.update(() => {
|
||||
@ -117,7 +122,7 @@ function TableCellResizer({editor}: {editor: LexicalEditor}): JSX.Element {
|
||||
throw new Error('TableCellResizer: Table element not found.');
|
||||
}
|
||||
|
||||
targetRef.current = target;
|
||||
targetRef.current = target as HTMLElement;
|
||||
tableRectRef.current = tableElement.getBoundingClientRect();
|
||||
updateActiveCell(cell);
|
||||
});
|
||||
@ -364,7 +369,7 @@ function TableCellResizer({editor}: {editor: LexicalEditor}): JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
export default function TableCellResizerPlugin(): React.Portal {
|
||||
export default function TableCellResizerPlugin(): ReactPortal {
|
||||
const [editor] = useLexicalComposerContext();
|
||||
|
||||
return useMemo(
|
||||
|
@ -117,7 +117,7 @@ function getPathFromNodeToEditor(node: Node, rootElement) {
|
||||
while (currentNode !== rootElement) {
|
||||
path.unshift(
|
||||
Array.from(currentNode?.parentNode?.childNodes ?? []).indexOf(
|
||||
currentNode,
|
||||
currentNode as ChildNode,
|
||||
),
|
||||
);
|
||||
currentNode = currentNode?.parentNode;
|
||||
@ -136,7 +136,13 @@ const keyPresses = new Set([
|
||||
'ArrowDown',
|
||||
]);
|
||||
|
||||
type Steps = Array<unknown>;
|
||||
type Step = {
|
||||
value: number;
|
||||
count: number;
|
||||
name: string;
|
||||
};
|
||||
|
||||
type Steps = Step[];
|
||||
|
||||
function useTestRecorder(editor: LexicalEditor): [JSX.Element, JSX.Element] {
|
||||
const [steps, setSteps] = useState<Steps>([]);
|
||||
|
@ -59,7 +59,6 @@ import {
|
||||
} from 'lexical';
|
||||
import * as React from 'react';
|
||||
import {useCallback, useEffect, useRef, useState} from 'react';
|
||||
// $FlowFixMe
|
||||
import {createPortal} from 'react-dom';
|
||||
import {IS_APPLE} from 'shared/environment';
|
||||
|
||||
@ -106,7 +105,7 @@ const blockTypeToBlockName = {
|
||||
quote: 'Quote',
|
||||
};
|
||||
|
||||
const CODE_LANGUAGE_OPTIONS = [
|
||||
const CODE_LANGUAGE_OPTIONS: [string, string][] = [
|
||||
['', '- Select language -'],
|
||||
['c', 'C'],
|
||||
['clike', 'C-like'],
|
||||
@ -162,7 +161,7 @@ function positionEditorElement(editor, rect) {
|
||||
}
|
||||
|
||||
function FloatingLinkEditor({editor}: {editor: LexicalEditor}): JSX.Element {
|
||||
const editorRef = useRef<HTMLElement | null>(null);
|
||||
const editorRef = useRef<HTMLDivElement | null>(null);
|
||||
const inputRef = useRef(null);
|
||||
const mouseDownRef = useRef(false);
|
||||
const [linkUrl, setLinkUrl] = useState('');
|
||||
@ -202,7 +201,7 @@ function FloatingLinkEditor({editor}: {editor: LexicalEditor}): JSX.Element {
|
||||
if (nativeSelection.anchorNode === rootElement) {
|
||||
let inner = rootElement;
|
||||
while (inner.firstElementChild != null) {
|
||||
inner = inner.firstElementChild;
|
||||
inner = inner.firstElementChild as HTMLElement;
|
||||
}
|
||||
rect = inner.getBoundingClientRect();
|
||||
} else {
|
||||
@ -350,7 +349,7 @@ function InsertImageUploadedDialogBody({
|
||||
|
||||
const isDisabled = src === '';
|
||||
|
||||
const loadImage = (files: File[]) => {
|
||||
const loadImage = (files: FileList) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = function () {
|
||||
if (typeof reader.result === 'string') {
|
||||
@ -627,25 +626,25 @@ function BlockFormatDropDown({
|
||||
|
||||
const formatBulletList = () => {
|
||||
if (blockType !== 'bullet') {
|
||||
editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND);
|
||||
editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined);
|
||||
} else {
|
||||
editor.dispatchCommand(REMOVE_LIST_COMMAND);
|
||||
editor.dispatchCommand(REMOVE_LIST_COMMAND, undefined);
|
||||
}
|
||||
};
|
||||
|
||||
const formatCheckList = () => {
|
||||
if (blockType !== 'check') {
|
||||
editor.dispatchCommand(INSERT_CHECK_LIST_COMMAND);
|
||||
editor.dispatchCommand(INSERT_CHECK_LIST_COMMAND, undefined);
|
||||
} else {
|
||||
editor.dispatchCommand(REMOVE_LIST_COMMAND);
|
||||
editor.dispatchCommand(REMOVE_LIST_COMMAND, undefined);
|
||||
}
|
||||
};
|
||||
|
||||
const formatNumberedList = () => {
|
||||
if (blockType !== 'number') {
|
||||
editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND);
|
||||
editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined);
|
||||
} else {
|
||||
editor.dispatchCommand(REMOVE_LIST_COMMAND);
|
||||
editor.dispatchCommand(REMOVE_LIST_COMMAND, undefined);
|
||||
}
|
||||
};
|
||||
|
||||
@ -748,7 +747,7 @@ function Select({
|
||||
}: {
|
||||
className: string;
|
||||
onChange: (event: {target: {value: string}}) => void;
|
||||
options: Array<[string, string]>;
|
||||
options: [string, string][];
|
||||
value: string;
|
||||
}): JSX.Element {
|
||||
return (
|
||||
@ -816,7 +815,10 @@ export default function ToolbarPlugin(): JSX.Element {
|
||||
if (elementDOM !== null) {
|
||||
setSelectedElementKey(elementKey);
|
||||
if ($isListNode(element)) {
|
||||
const parentList = $getNearestNodeOfType(anchorNode, ListNode);
|
||||
const parentList = $getNearestNodeOfType<ListNode>(
|
||||
anchorNode,
|
||||
ListNode,
|
||||
);
|
||||
const type = parentList
|
||||
? parentList.getListType()
|
||||
: element.getListType();
|
||||
@ -864,7 +866,7 @@ export default function ToolbarPlugin(): JSX.Element {
|
||||
updateToolbar();
|
||||
});
|
||||
}),
|
||||
activeEditor.registerCommand(
|
||||
activeEditor.registerCommand<boolean>(
|
||||
CAN_UNDO_COMMAND,
|
||||
(payload) => {
|
||||
setCanUndo(payload);
|
||||
@ -872,7 +874,7 @@ export default function ToolbarPlugin(): JSX.Element {
|
||||
},
|
||||
COMMAND_PRIORITY_CRITICAL,
|
||||
),
|
||||
activeEditor.registerCommand(
|
||||
activeEditor.registerCommand<boolean>(
|
||||
CAN_REDO_COMMAND,
|
||||
(payload) => {
|
||||
setCanRedo(payload);
|
||||
@ -939,7 +941,7 @@ export default function ToolbarPlugin(): JSX.Element {
|
||||
<button
|
||||
disabled={!canUndo}
|
||||
onClick={() => {
|
||||
activeEditor.dispatchCommand(UNDO_COMMAND);
|
||||
activeEditor.dispatchCommand(UNDO_COMMAND, undefined);
|
||||
}}
|
||||
title={IS_APPLE ? 'Undo (⌘Z)' : 'Undo (Ctrl+Z)'}
|
||||
className="toolbar-item spaced"
|
||||
@ -949,7 +951,7 @@ export default function ToolbarPlugin(): JSX.Element {
|
||||
<button
|
||||
disabled={!canRedo}
|
||||
onClick={() => {
|
||||
activeEditor.dispatchCommand(REDO_COMMAND);
|
||||
activeEditor.dispatchCommand(REDO_COMMAND, undefined);
|
||||
}}
|
||||
title={IS_APPLE ? 'Redo (⌘Y)' : 'Undo (Ctrl+Y)'}
|
||||
className="toolbar-item"
|
||||
@ -1059,7 +1061,7 @@ export default function ToolbarPlugin(): JSX.Element {
|
||||
onClick={insertLink}
|
||||
className={'toolbar-item spaced ' + (isLink ? 'active' : '')}
|
||||
aria-label="Insert link"
|
||||
type="Insert link">
|
||||
title="Insert link">
|
||||
<i className="format link" />
|
||||
</button>
|
||||
{isLink &&
|
||||
@ -1123,7 +1125,10 @@ export default function ToolbarPlugin(): JSX.Element {
|
||||
buttonIconClassName="icon plus">
|
||||
<button
|
||||
onClick={() => {
|
||||
activeEditor.dispatchCommand(INSERT_HORIZONTAL_RULE_COMMAND);
|
||||
activeEditor.dispatchCommand(
|
||||
INSERT_HORIZONTAL_RULE_COMMAND,
|
||||
undefined,
|
||||
);
|
||||
}}
|
||||
className="item">
|
||||
<i className="icon horizontal-rule" />
|
||||
@ -1155,7 +1160,10 @@ export default function ToolbarPlugin(): JSX.Element {
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
activeEditor.dispatchCommand(INSERT_EXCALIDRAW_COMMAND);
|
||||
activeEditor.dispatchCommand(
|
||||
INSERT_EXCALIDRAW_COMMAND,
|
||||
undefined,
|
||||
);
|
||||
}}
|
||||
className="item">
|
||||
<i className="icon diagram-2" />
|
||||
@ -1282,7 +1290,7 @@ export default function ToolbarPlugin(): JSX.Element {
|
||||
<Divider />
|
||||
<button
|
||||
onClick={() => {
|
||||
activeEditor.dispatchCommand(OUTDENT_CONTENT_COMMAND);
|
||||
activeEditor.dispatchCommand(OUTDENT_CONTENT_COMMAND, undefined);
|
||||
}}
|
||||
className="item">
|
||||
<i className={'icon ' + (isRTL ? 'indent' : 'outdent')} />
|
||||
@ -1290,7 +1298,7 @@ export default function ToolbarPlugin(): JSX.Element {
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
activeEditor.dispatchCommand(INDENT_CONTENT_COMMAND);
|
||||
activeEditor.dispatchCommand(INDENT_CONTENT_COMMAND, undefined);
|
||||
}}
|
||||
className="item">
|
||||
<i className={'icon ' + (isRTL ? 'outdent' : 'indent')} />
|
||||
|
@ -30,7 +30,7 @@ export default function TwitterPlugin(): JSX.Element {
|
||||
throw new Error('TwitterPlugin: TweetNode not registered on editor');
|
||||
}
|
||||
|
||||
return editor.registerCommand(
|
||||
return editor.registerCommand<string>(
|
||||
INSERT_TWEET_COMMAND,
|
||||
(payload) => {
|
||||
const selection = $getSelection();
|
||||
|
@ -30,7 +30,7 @@ export default function YouTubePlugin(): JSX.Element {
|
||||
throw new Error('YouTubePlugin: YouTubeNode not registered on editor');
|
||||
}
|
||||
|
||||
return editor.registerCommand(
|
||||
return editor.registerCommand<string>(
|
||||
INSERT_YOUTUBE_COMMAND,
|
||||
(payload) => {
|
||||
const selection = $getSelection();
|
||||
|
@ -22,7 +22,7 @@ export default function Button({
|
||||
title,
|
||||
}: {
|
||||
'data-test-id'?: string;
|
||||
children: JSX.Element | string;
|
||||
children: JSX.Element | string | (JSX.Element | string)[];
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
onClick: () => void;
|
||||
|
@ -8,7 +8,6 @@
|
||||
|
||||
import {useEffect, useRef, useState} from 'react';
|
||||
import * as React from 'react';
|
||||
// $FlowFixMe
|
||||
import {createPortal} from 'react-dom';
|
||||
|
||||
export default function DropDown({
|
||||
@ -22,10 +21,10 @@ export default function DropDown({
|
||||
buttonClassName: string;
|
||||
buttonIconClassName?: string;
|
||||
buttonLabel?: string;
|
||||
children: React.Node;
|
||||
children: JSX.Element | string | (JSX.Element | string)[];
|
||||
}): JSX.Element {
|
||||
const dropDownRef = useRef<HTMLElement | null>(null);
|
||||
const buttonRef = useRef<HTMLElement | null>(null);
|
||||
const dropDownRef = useRef<HTMLDivElement | null>(null);
|
||||
const buttonRef = useRef<HTMLButtonElement | null>(null);
|
||||
const [showDropDown, setShowDropDown] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
@ -47,8 +46,8 @@ export default function DropDown({
|
||||
|
||||
if (button !== null && showDropDown) {
|
||||
const handle = (event: MouseEvent) => {
|
||||
const target: HTMLElement = event.target;
|
||||
if (!button.contains(target)) {
|
||||
const target = event.target;
|
||||
if (!button.contains(target as Node)) {
|
||||
setShowDropDown(false);
|
||||
}
|
||||
};
|
||||
|
@ -8,12 +8,13 @@
|
||||
|
||||
import './EquationEditor.css';
|
||||
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import {ChangeEvent, RefObject} from 'react';
|
||||
|
||||
type BaseEquationEditorProps = {
|
||||
equation: string;
|
||||
inline: boolean;
|
||||
inputRef: {current: null | HTMLElement};
|
||||
inputRef: {current: null | HTMLInputElement | HTMLTextAreaElement};
|
||||
setEquation: (string) => void;
|
||||
};
|
||||
|
||||
@ -34,16 +35,22 @@ export default function EquationEditor({
|
||||
};
|
||||
|
||||
return inline ? (
|
||||
<InlineEquationEditor {...props} />
|
||||
<InlineEquationEditor
|
||||
{...props}
|
||||
inputRef={inputRef as RefObject<HTMLInputElement>}
|
||||
/>
|
||||
) : (
|
||||
<BlockEquationEditor {...props} />
|
||||
<BlockEquationEditor
|
||||
{...props}
|
||||
inputRef={inputRef as RefObject<HTMLTextAreaElement>}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
type EquationEditorImplProps = {
|
||||
equation: string;
|
||||
inputRef: {current: null | HTMLElement};
|
||||
onChange: (event: SyntheticInputEvent<HTMLInputElement>) => void;
|
||||
inputRef: {current: null | HTMLInputElement};
|
||||
onChange: (event: ChangeEvent<HTMLInputElement>) => void;
|
||||
};
|
||||
|
||||
function InlineEquationEditor({
|
||||
@ -66,11 +73,17 @@ function InlineEquationEditor({
|
||||
);
|
||||
}
|
||||
|
||||
type BlockEquationEditorImplProps = {
|
||||
equation: string;
|
||||
inputRef: {current: null | HTMLTextAreaElement};
|
||||
onChange: (event: ChangeEvent<HTMLTextAreaElement>) => void;
|
||||
};
|
||||
|
||||
function BlockEquationEditor({
|
||||
equation,
|
||||
onChange,
|
||||
inputRef,
|
||||
}: EquationEditorImplProps): JSX.Element {
|
||||
}: BlockEquationEditorImplProps): JSX.Element {
|
||||
return (
|
||||
<div className="EquationEditor_inputBackground">
|
||||
<span className="EquationEditor_dollarSign">{'$$\n'}</span>
|
||||
|
@ -14,7 +14,7 @@ type Props = Readonly<{
|
||||
'data-test-id'?: string;
|
||||
accept?: string;
|
||||
label: string;
|
||||
onChange: (files: File[]) => void;
|
||||
onChange: (files: FileList) => void;
|
||||
}>;
|
||||
|
||||
export default function FileInput({
|
||||
|
@ -38,7 +38,7 @@ export default function ImageResizer({
|
||||
onResizeStart: () => void;
|
||||
setShowCaption: (boolean) => void;
|
||||
showCaption: boolean;
|
||||
}): React.Node {
|
||||
}): JSX.Element {
|
||||
const buttonRef = useRef(null);
|
||||
const positioningRef = useRef<{
|
||||
currentHeight: 'inherit' | number;
|
||||
@ -110,7 +110,10 @@ export default function ImageResizer({
|
||||
}
|
||||
};
|
||||
|
||||
const handlePointerDown = (event: PointerEvent, direction: number) => {
|
||||
const handlePointerDown = (
|
||||
event: React.PointerEvent<HTMLDivElement>,
|
||||
direction: number,
|
||||
) => {
|
||||
const image = imageRef.current;
|
||||
if (image !== null) {
|
||||
const {width, height} = image.getBoundingClientRect();
|
||||
@ -188,7 +191,7 @@ export default function ImageResizer({
|
||||
}
|
||||
}
|
||||
};
|
||||
const handlePointerUp = (_event: PointerEvent) => {
|
||||
const handlePointerUp = () => {
|
||||
const image = imageRef.current;
|
||||
const positioning = positioningRef.current;
|
||||
if (image !== null && positioning.isResizing) {
|
||||
|
@ -6,7 +6,6 @@
|
||||
*
|
||||
*/
|
||||
|
||||
// $FlowFixMe
|
||||
import katex from 'katex';
|
||||
import * as React from 'react';
|
||||
import {useEffect, useRef} from 'react';
|
||||
|
@ -10,7 +10,6 @@ import './Modal.css';
|
||||
|
||||
import * as React from 'react';
|
||||
import {useEffect, useRef} from 'react';
|
||||
// $FlowFixMe
|
||||
import {createPortal} from 'react-dom';
|
||||
|
||||
function PortalImpl({
|
||||
@ -19,12 +18,12 @@ function PortalImpl({
|
||||
title,
|
||||
closeOnClickOutside,
|
||||
}: {
|
||||
children: JSX.Element;
|
||||
children: JSX.Element | string | (JSX.Element | string)[];
|
||||
closeOnClickOutside: boolean;
|
||||
onClose: () => void;
|
||||
title: string;
|
||||
}) {
|
||||
const modalRef = useRef<HTMLElement | null>(null);
|
||||
const modalRef = useRef<HTMLDivElement>();
|
||||
|
||||
useEffect(() => {
|
||||
if (modalRef.current !== null) {
|
||||
@ -40,11 +39,10 @@ function PortalImpl({
|
||||
}
|
||||
};
|
||||
const clickOutsideHandler = (event: MouseEvent) => {
|
||||
// $FlowFixMe: event.target is always a Node on the DOM
|
||||
const target: HTMLElement = event.target;
|
||||
const target = event.target;
|
||||
if (
|
||||
modalRef.current !== null &&
|
||||
!modalRef.current.contains(target) &&
|
||||
!modalRef.current.contains(target as Node) &&
|
||||
closeOnClickOutside
|
||||
) {
|
||||
onClose();
|
||||
@ -74,7 +72,7 @@ function PortalImpl({
|
||||
<button
|
||||
className="Modal__closeButton"
|
||||
aria-label="Close modal"
|
||||
type="Close"
|
||||
type="button"
|
||||
onClick={onClose}>
|
||||
X
|
||||
</button>
|
||||
@ -90,7 +88,7 @@ export default function Modal({
|
||||
title,
|
||||
closeOnClickOutside = false,
|
||||
}: {
|
||||
children: JSX.Element;
|
||||
children: JSX.Element | string | (JSX.Element | string)[];
|
||||
closeOnClickOutside?: boolean;
|
||||
onClose: () => void;
|
||||
title: string;
|
||||
|
@ -14,7 +14,7 @@ export default function Placeholder({
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
children: string;
|
||||
children: JSX.Element | string | (JSX.Element | string)[];
|
||||
className?: string;
|
||||
}): JSX.Element {
|
||||
return <div className={className || 'Placeholder__root'}>{children}</div>;
|
||||
|
@ -6,8 +6,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
import {$ReadOnly} from 'utility-types';
|
||||
type Props = $ReadOnly<{
|
||||
type Props = Readonly<{
|
||||
scrollRef: {current: HTMLElement | null};
|
||||
}>;
|
||||
export default function LexicalAutoScrollPlugin(props: Props): null;
|
||||
|
18
packages/lexical-react/LexicalBlockWithAlignableContents.d.ts
vendored
Normal file
18
packages/lexical-react/LexicalBlockWithAlignableContents.d.ts
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict
|
||||
*/
|
||||
|
||||
import type {ElementFormatType, NodeKey} from 'lexical';
|
||||
|
||||
type Props = Readonly<{
|
||||
children: JSX.Element | string | (JSX.Element | string)[];
|
||||
format: ElementFormatType | null;
|
||||
nodeKey: NodeKey;
|
||||
}>;
|
||||
|
||||
declare function BlockWithAlignableContents(Props): JSX.Element;
|
9
packages/lexical-react/LexicalCheckListPlugin.d.ts
vendored
Normal file
9
packages/lexical-react/LexicalCheckListPlugin.d.ts
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
|
||||
export default function LexicalCheckListPlugin(): null;
|
@ -6,6 +6,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
import {WebsocketProvider} from 'y-websocket';
|
||||
import type {Doc, RelativePosition} from 'yjs';
|
||||
export type UserState = {
|
||||
anchorPos: null | RelativePosition;
|
||||
@ -21,34 +22,15 @@ export type ProviderAwareness = {
|
||||
on: (type: 'update', cb: () => void) => void;
|
||||
off: (type: 'update', cb: () => void) => void;
|
||||
};
|
||||
export interface Provider {
|
||||
connect(): void | Promise<void>;
|
||||
disconnect(): void;
|
||||
awareness: ProviderAwareness;
|
||||
on(type: 'sync', cb: (isSynced: boolean) => void): void;
|
||||
on(type: 'status', cb: (arg0: {status: string}) => void): void;
|
||||
// $FlowFixMe: temp
|
||||
on(type: 'update', cb: (arg0: any) => void): void;
|
||||
on(type: 'reload', cb: (doc: Doc) => boolean): void;
|
||||
off(type: 'sync', cb: (isSynced: boolean) => void): void;
|
||||
// $FlowFixMe: temp
|
||||
off(type: 'update', cb: (arg0: any) => void): void;
|
||||
off(type: 'status', cb: (arg0: {status: string}) => void): void;
|
||||
off(type: 'reload', cb: (doc: Doc) => boolean): void;
|
||||
}
|
||||
type CollaborationContextType = {
|
||||
clientID: number;
|
||||
color: string;
|
||||
name: string;
|
||||
yjsDocMap: Map<string, Doc>;
|
||||
};
|
||||
export type ProviderFactory = (
|
||||
id: string,
|
||||
yjsDocMap: Map<string, Doc>,
|
||||
) => Provider;
|
||||
export function CollaborationPlugin(arg0: {
|
||||
id: string;
|
||||
providerFactory: ProviderFactory;
|
||||
providerFactory(id: string, yjsDocMap: Map<string, Doc>): WebsocketProvider;
|
||||
shouldBootstrap: boolean;
|
||||
username?: string;
|
||||
}): JSX.Element | null;
|
||||
|
23
packages/lexical-react/LexicalDecoratorBlockNode.d.ts
vendored
Normal file
23
packages/lexical-react/LexicalDecoratorBlockNode.d.ts
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict
|
||||
*/
|
||||
|
||||
import type {ElementFormatType, LexicalNode, NodeKey} from 'lexical';
|
||||
|
||||
import {DecoratorNode} from 'lexical';
|
||||
|
||||
declare class DecoratorBlockNode<T> extends DecoratorNode<T> {
|
||||
__format: ElementFormatType;
|
||||
constructor(format?: ElementFormatType | null, key?: NodeKey);
|
||||
createDOM(): HTMLElement;
|
||||
setFormat(format: ElementFormatType): void;
|
||||
}
|
||||
|
||||
declare function $isDecoratorBlockNode<T>(
|
||||
node: LexicalNode,
|
||||
): node is DecoratorBlockNode<T>;
|
@ -6,6 +6,8 @@
|
||||
*
|
||||
*/
|
||||
|
||||
import type {Transformer} from '@lexical/markdown';
|
||||
|
||||
export default function LexicalMarkdownShortcutPlugin(arg0: {
|
||||
transformers: Array<Transformer>;
|
||||
}): JSX.Element | null;
|
||||
|
@ -11,5 +11,5 @@ import type {LexicalEditor, EditorThemeClasses} from 'lexical';
|
||||
export default function LexicalNestedComposer(arg0: {
|
||||
initialEditor: LexicalEditor;
|
||||
initialTheme?: EditorThemeClasses;
|
||||
children: JSX.Element | JSX.Element[] | null;
|
||||
children: JSX.Element | (JSX.Element | string | null)[] | null;
|
||||
}): JSX.Element | null;
|
||||
|
@ -27,7 +27,9 @@ export declare class QuoteNode extends ElementNode {
|
||||
collapseAtStart(): true;
|
||||
}
|
||||
export function $createQuoteNode(): QuoteNode;
|
||||
export function $isQuoteNode(node: ?LexicalNode): node is QuoteNode;
|
||||
export function $isQuoteNode(
|
||||
node: LexicalNode | null | undefined,
|
||||
): node is QuoteNode;
|
||||
export type HeadingTagType = 'h1' | 'h2' | 'h3' | 'h4' | 'h5';
|
||||
export declare class HeadingNode extends ElementNode {
|
||||
__tag: HeadingTagType;
|
||||
@ -42,7 +44,9 @@ export declare class HeadingNode extends ElementNode {
|
||||
collapseAtStart(): true;
|
||||
}
|
||||
export function $createHeadingNode(headingTag: HeadingTagType): HeadingNode;
|
||||
export function $isHeadingNode(node: ?LexicalNode): node is HeadingNode;
|
||||
export function $isHeadingNode(
|
||||
node: LexicalNode | null | undefined,
|
||||
): node is HeadingNode;
|
||||
export function registerRichText(
|
||||
editor: LexicalEditor,
|
||||
initialEditorState?: InitialEditorStateType,
|
||||
|
43
packages/lexical-table/LexicalTable.d.ts
vendored
43
packages/lexical-table/LexicalTable.d.ts
vendored
@ -18,9 +18,9 @@ import type {
|
||||
TextFormatType,
|
||||
LexicalCommand,
|
||||
} from 'lexical';
|
||||
import {TableSelection} from './src/TableSelection';
|
||||
import {$Values} from 'utility-types';
|
||||
|
||||
export enum TableCellHeaderState {
|
||||
export enum TableCellHeaderStates {
|
||||
NO_STATUS = 0,
|
||||
ROW = 1,
|
||||
COLUMN = 2,
|
||||
@ -31,24 +31,16 @@ export enum TableCellHeaderState {
|
||||
* LexicalTableCellNode
|
||||
*/
|
||||
|
||||
export const TableCellHeaderStates = {
|
||||
NO_STATUS: 0,
|
||||
ROW: 1,
|
||||
COLUMN: 2,
|
||||
BOTH: 3,
|
||||
};
|
||||
|
||||
export type TableCellHeaderState = $Values<typeof TableCellHeaderStates>;
|
||||
|
||||
export declare class TableCellNode extends ElementNode {
|
||||
static getType(): string;
|
||||
static clone(node: TableCellNode): TableCellNode;
|
||||
constructor(
|
||||
headerState?: TableCellHeaderState,
|
||||
headerState?: TableCellHeaderStates,
|
||||
colSpan?: number,
|
||||
width?: ?number,
|
||||
width?: number | null | undefined,
|
||||
key?: NodeKey,
|
||||
);
|
||||
__headerState: TableCellHeaderStates;
|
||||
createDOM(config: EditorConfig): HTMLElement;
|
||||
updateDOM(prevNode: TableCellNode, dom: HTMLElement): boolean;
|
||||
insertNewAfter(
|
||||
@ -56,17 +48,22 @@ export declare class TableCellNode extends ElementNode {
|
||||
): null | ParagraphNode | TableCellNode;
|
||||
collapseAtStart(): true;
|
||||
getTag(): string;
|
||||
setHeaderState(headerState: TableCellHeaderState): TableCellHeaderState;
|
||||
getHeaderState(): TableCellHeaderState;
|
||||
toggleHeaderState(headerState: TableCellHeaderState): TableCellNode;
|
||||
setHeaderState(headerState: TableCellHeaderStates): TableCellHeaderStates;
|
||||
getHeaderState(): TableCellHeaderStates;
|
||||
toggleHeaderState(headerState: TableCellHeaderStates): TableCellNode;
|
||||
hasHeader(): boolean;
|
||||
setWidth(width: number): ?number;
|
||||
getWidth(): ?number;
|
||||
setWidth(width: number): number | null | undefined;
|
||||
getWidth(): number | null | undefined;
|
||||
toggleHeaderStyle(headerState: TableCellHeaderStates): TableCellNode;
|
||||
updateDOM(prevNode: TableCellNode): boolean;
|
||||
collapseAtStart(): true;
|
||||
canBeEmpty(): false;
|
||||
}
|
||||
export declare function $createTableCellNode(): TableCellNode;
|
||||
declare function $createTableCellNode(
|
||||
headerState: TableCellHeaderStates,
|
||||
colSpan?: number,
|
||||
width?: number | null | undefined,
|
||||
): TableCellNode;
|
||||
export declare function $isTableCellNode(
|
||||
node?: LexicalNode,
|
||||
): node is TableCellNode;
|
||||
@ -84,7 +81,7 @@ export declare class TableNode extends ElementNode {
|
||||
insertNewAfter(selection: RangeSelection): null | ParagraphNode | TableNode;
|
||||
collapseAtStart(): true;
|
||||
getCordsFromCellNode(tableCellNode: TableCellNode): {x: number; y: number};
|
||||
getCellFromCords(x: number, y: number, grid: Grid): ?Cell;
|
||||
getCellFromCords(x: number, y: number, grid: Grid): Cell | null | undefined;
|
||||
getCellFromCordsOrThrow(x: number, y: number, grid: Grid): Cell;
|
||||
getCellNodeFromCords(x: number, y: number): TableCellNode | null;
|
||||
getCellNodeFromCordsOrThrow(x: number, y: number): TableCellNode;
|
||||
@ -102,14 +99,14 @@ declare function $isTableNode(node?: LexicalNode): node is TableNode;
|
||||
declare class TableRowNode extends ElementNode {
|
||||
static getType(): string;
|
||||
static clone(node: TableRowNode): TableRowNode;
|
||||
constructor(key?: NodeKey, height?: ?number);
|
||||
constructor(key?: NodeKey, height?: number | null | undefined);
|
||||
createDOM(config: EditorConfig): HTMLElement;
|
||||
updateDOM(prevNode: TableRowNode, dom: HTMLElement): boolean;
|
||||
insertNewAfter(
|
||||
selection: RangeSelection,
|
||||
): null | ParagraphNode | TableRowNode;
|
||||
setHeight(height: number): ?number;
|
||||
getHeight(): ?number;
|
||||
setHeight(height: number): number | null | undefined;
|
||||
getHeight(): number | null | undefined;
|
||||
collapseAtStart(): true;
|
||||
}
|
||||
declare function $createTableRowNode(): TableRowNode;
|
||||
|
3
packages/lexical-text/LexicalText.d.ts
vendored
3
packages/lexical-text/LexicalText.d.ts
vendored
@ -6,6 +6,7 @@
|
||||
*
|
||||
*/
|
||||
import type {ElementNode, LexicalEditor, RootNode, TextNode} from 'lexical';
|
||||
import {Class} from 'utility-types';
|
||||
export type TextNodeWithOffset = {
|
||||
node: TextNode;
|
||||
offset: number;
|
||||
@ -27,7 +28,7 @@ export function $findNodeWithOffsetFromJoinedText(
|
||||
joinedTextLength: number,
|
||||
separatorLength: number,
|
||||
elementNode: ElementNode,
|
||||
): ?TextNodeWithOffset;
|
||||
): TextNodeWithOffset | null | undefined;
|
||||
export function $isRootTextContentEmpty(
|
||||
isEditorComposing: boolean,
|
||||
trim?: boolean,
|
||||
|
6
packages/lexical-utils/LexicalUtils.d.ts
vendored
6
packages/lexical-utils/LexicalUtils.d.ts
vendored
@ -5,6 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
import {Class} from 'utility-types';
|
||||
|
||||
import type {LexicalNode, ElementNode, LexicalEditor} from 'lexical';
|
||||
export type DFSNode = Readonly<{
|
||||
depth: number;
|
||||
@ -25,7 +27,7 @@ declare function $dfs(
|
||||
declare function $getDepth(node: LexicalNode): number;
|
||||
declare function $getNearestNodeOfType<T extends LexicalNode>(
|
||||
node: LexicalNode,
|
||||
klass: T,
|
||||
klass: Class<T>,
|
||||
): T | null;
|
||||
export type DOMNodeToLexicalConversion = (element: Node) => LexicalNode;
|
||||
export type DOMNodeToLexicalConversionMap = Record<
|
||||
@ -44,7 +46,7 @@ declare function $getNearestBlockElementAncestorOrThrow(
|
||||
|
||||
declare function registerNestedElementResolver<N extends ElementNode>(
|
||||
editor: LexicalEditor,
|
||||
targetNode: N,
|
||||
targetNode: Class<N>,
|
||||
cloneNode: (from: N) => N,
|
||||
handleOverlap: (from: N, to: N) => void,
|
||||
): () => void;
|
||||
|
21
packages/lexical-yjs/LexicalYjs.d.ts
vendored
21
packages/lexical-yjs/LexicalYjs.d.ts
vendored
@ -8,7 +8,6 @@
|
||||
import type {
|
||||
Doc,
|
||||
RelativePosition,
|
||||
TextOperation,
|
||||
UndoManager,
|
||||
XmlElement,
|
||||
XmlText,
|
||||
@ -26,7 +25,6 @@ import type {
|
||||
TextNode,
|
||||
IntentionallyMarkedAsDirtyElement,
|
||||
} from 'lexical';
|
||||
// @ts-expect-error: todo
|
||||
export type YjsEvent = Record<string, any>;
|
||||
export type UserState = {
|
||||
anchorPos: null | RelativePosition;
|
||||
@ -121,30 +119,35 @@ export declare class CollabDecoratorNode {
|
||||
destroy(binding: Binding): void;
|
||||
}
|
||||
export declare class CollabLineBreakNode {
|
||||
_map: YMap;
|
||||
_map: YMap<unknown>;
|
||||
_key: NodeKey;
|
||||
_parent: CollabElementNode;
|
||||
_type: 'linebreak';
|
||||
constructor(map: YMap, parent: CollabElementNode);
|
||||
constructor(map: YMap<unknown>, parent: CollabElementNode);
|
||||
getNode(): null | LineBreakNode;
|
||||
getKey(): NodeKey;
|
||||
getSharedType(): YMap;
|
||||
getSharedType(): YMap<unknown>;
|
||||
getType(): string;
|
||||
getSize(): number;
|
||||
getOffset(): number;
|
||||
destroy(binding: Binding): void;
|
||||
}
|
||||
export declare class CollabTextNode {
|
||||
_map: YMap;
|
||||
_map: YMap<unknown>;
|
||||
_key: NodeKey;
|
||||
_parent: CollabElementNode;
|
||||
_text: string;
|
||||
_type: string;
|
||||
_normalized: boolean;
|
||||
constructor(map: YMap, text: string, parent: CollabElementNode, type: string);
|
||||
constructor(
|
||||
map: YMap<unknown>,
|
||||
text: string,
|
||||
parent: CollabElementNode,
|
||||
type: string,
|
||||
);
|
||||
getPrevNode(nodeMap: null | NodeMap): null | TextNode;
|
||||
getNode(): null | TextNode;
|
||||
getSharedType(): YMap;
|
||||
getSharedType(): YMap<unknown>;
|
||||
getType(): string;
|
||||
getKey(): NodeKey;
|
||||
getSize(): number;
|
||||
@ -185,7 +188,7 @@ export declare class CollabElementNode {
|
||||
binding: Binding,
|
||||
keysChanged: null | Set<string>,
|
||||
): void;
|
||||
applyChildrenYjsDelta(binding: Binding, deltas: Array<TextOperation>): void;
|
||||
applyChildrenYjsDelta(binding: Binding, deltas: Array<unknown>): void;
|
||||
syncChildrenFromYjs(binding: Binding): void;
|
||||
syncPropertiesFromLexical(
|
||||
binding: Binding,
|
||||
|
2
packages/lexical/Lexical.d.ts
vendored
2
packages/lexical/Lexical.d.ts
vendored
@ -149,6 +149,7 @@ export declare class LexicalEditor {
|
||||
): () => void;
|
||||
dispatchCommand<P>(type: LexicalCommand<P>, payload: P): boolean;
|
||||
hasNodes(nodes: Array<Class<LexicalNode>>): boolean;
|
||||
getKey(): string;
|
||||
getDecorators<X>(): Record<NodeKey, X>;
|
||||
getRootElement(): null | HTMLElement;
|
||||
setRootElement(rootElement: null | HTMLElement): void;
|
||||
@ -617,6 +618,7 @@ export declare class TextNode extends LexicalNode {
|
||||
canInsertTextAfter(): boolean;
|
||||
splitText(...splitOffsets: Array<number>): Array<TextNode>;
|
||||
mergeWithSibling(target: TextNode): TextNode;
|
||||
isTextEntity(): boolean;
|
||||
}
|
||||
export function $createTextNode(text?: string): TextNode;
|
||||
export function $isTextNode(
|
||||
|
@ -5,6 +5,6 @@
|
||||
"packages/lexical-dragon/**/*",
|
||||
"packages/lexical-history/**/*"
|
||||
],
|
||||
"exclude": ["packages/lexical-playground/**/*"],
|
||||
"exclude": ["node_modules/**/*", "packages/lexical-playground/**/*"],
|
||||
"extends": "./tsconfig.json"
|
||||
}
|
||||
|
@ -199,5 +199,5 @@
|
||||
"shared/useLayoutEffect": ["packages/shared/src/useLayoutEffect.js"]
|
||||
}
|
||||
},
|
||||
"exclude": ["./node_modules/**/*"]
|
||||
"exclude": ["node_modules/**/*"]
|
||||
}
|
||||
|
Reference in New Issue
Block a user