@lexical/react 0.49.1-nightly.20260901.0 → 0.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/LexicalAutoEmbedPlugin.d.ts +4 -47
- package/dist/LexicalAutoEmbedPlugin.dev.js +5 -31
- package/dist/LexicalAutoEmbedPlugin.dev.mjs +5 -30
- package/dist/LexicalAutoEmbedPlugin.prod.js +1 -1
- package/dist/LexicalAutoEmbedPlugin.prod.mjs +1 -1
- package/dist/LexicalAutoEmbedPluginUtils.d.ts +53 -0
- package/dist/LexicalAutoEmbedPluginUtils.dev.js +66 -0
- package/dist/LexicalAutoEmbedPluginUtils.dev.mjs +62 -0
- package/dist/LexicalAutoEmbedPluginUtils.js +11 -0
- package/dist/LexicalAutoEmbedPluginUtils.js.flow +40 -0
- package/dist/LexicalAutoEmbedPluginUtils.mjs +14 -0
- package/dist/LexicalAutoEmbedPluginUtils.node.mjs +12 -0
- package/dist/LexicalAutoEmbedPluginUtils.prod.js +9 -0
- package/dist/LexicalAutoEmbedPluginUtils.prod.mjs +9 -0
- package/dist/LexicalCollaborationContext.d.ts +1 -26
- package/dist/LexicalCollaborationContext.dev.js +5 -55
- package/dist/LexicalCollaborationContext.dev.mjs +4 -53
- package/dist/LexicalCollaborationContext.prod.js +2 -2
- package/dist/LexicalCollaborationContext.prod.mjs +2 -2
- package/dist/LexicalCollaborationContextUtils.d.ts +35 -0
- package/dist/LexicalCollaborationContextUtils.dev.js +83 -0
- package/dist/LexicalCollaborationContextUtils.dev.mjs +79 -0
- package/dist/LexicalCollaborationContextUtils.js +11 -0
- package/dist/LexicalCollaborationContextUtils.js.flow +25 -0
- package/dist/LexicalCollaborationContextUtils.mjs +14 -0
- package/dist/LexicalCollaborationContextUtils.node.mjs +12 -0
- package/dist/LexicalCollaborationContextUtils.prod.js +9 -0
- package/dist/LexicalCollaborationContextUtils.prod.mjs +9 -0
- package/dist/LexicalMenuOption.d.ts +29 -0
- package/dist/LexicalMenuOption.dev.js +51 -0
- package/dist/LexicalMenuOption.dev.mjs +49 -0
- package/dist/LexicalMenuOption.js +11 -0
- package/dist/LexicalMenuOption.js.flow +20 -0
- package/dist/LexicalMenuOption.mjs +12 -0
- package/dist/LexicalMenuOption.node.mjs +10 -0
- package/dist/LexicalMenuOption.prod.js +9 -0
- package/dist/LexicalMenuOption.prod.mjs +9 -0
- package/dist/LexicalNodeMenuPlugin.dev.js +5 -28
- package/dist/LexicalNodeMenuPlugin.dev.mjs +4 -27
- package/dist/LexicalNodeMenuPlugin.prod.js +1 -1
- package/dist/LexicalNodeMenuPlugin.prod.mjs +1 -1
- package/dist/LexicalTypeaheadMenuPlugin.d.ts +2 -34
- package/dist/LexicalTypeaheadMenuPlugin.dev.js +10 -78
- package/dist/LexicalTypeaheadMenuPlugin.dev.mjs +9 -76
- package/dist/LexicalTypeaheadMenuPlugin.prod.js +1 -1
- package/dist/LexicalTypeaheadMenuPlugin.prod.mjs +1 -1
- package/dist/LexicalTypeaheadMenuPluginUtils.d.ts +44 -0
- package/dist/LexicalTypeaheadMenuPluginUtils.dev.js +81 -0
- package/dist/LexicalTypeaheadMenuPluginUtils.dev.mjs +76 -0
- package/dist/LexicalTypeaheadMenuPluginUtils.js +11 -0
- package/dist/LexicalTypeaheadMenuPluginUtils.js.flow +34 -0
- package/dist/LexicalTypeaheadMenuPluginUtils.mjs +15 -0
- package/dist/LexicalTypeaheadMenuPluginUtils.node.mjs +13 -0
- package/dist/LexicalTypeaheadMenuPluginUtils.prod.js +9 -0
- package/dist/LexicalTypeaheadMenuPluginUtils.prod.mjs +9 -0
- package/dist/shared/LexicalMenu.d.ts +5 -19
- package/package.json +164 -20
- package/src/LexicalAutoEmbedPlugin.tsx +12 -70
- package/src/LexicalAutoEmbedPluginUtils.ts +80 -0
- package/src/LexicalCollaborationContext.tsx +11 -90
- package/src/LexicalCollaborationContextUtils.ts +99 -0
- package/src/LexicalMenuOption.ts +39 -0
- package/src/LexicalTypeaheadMenuPlugin.tsx +6 -80
- package/src/LexicalTypeaheadMenuPluginUtils.ts +93 -0
- package/src/shared/LexicalMenu.tsx +12 -28
|
@@ -7,10 +7,14 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import {$isLinkNode, AutoLinkNode, LinkNode} from '@lexical/link';
|
|
10
|
+
import {
|
|
11
|
+
type AutoEmbedOption,
|
|
12
|
+
type EmbedConfig,
|
|
13
|
+
INSERT_EMBED_COMMAND,
|
|
14
|
+
} from '@lexical/react/LexicalAutoEmbedPluginUtils';
|
|
10
15
|
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
|
|
11
16
|
import {
|
|
12
17
|
LexicalNodeMenuPlugin,
|
|
13
|
-
MenuOption,
|
|
14
18
|
type MenuRenderFn,
|
|
15
19
|
} from '@lexical/react/LexicalNodeMenuPlugin';
|
|
16
20
|
import {objectKlassEquals} from '@lexical/utils';
|
|
@@ -22,10 +26,6 @@ import {
|
|
|
22
26
|
COMMAND_PRIORITY_EDITOR,
|
|
23
27
|
COMMAND_PRIORITY_LOW,
|
|
24
28
|
type CommandListenerPriority,
|
|
25
|
-
createCommand,
|
|
26
|
-
type LexicalCommand,
|
|
27
|
-
type LexicalEditor,
|
|
28
|
-
type LexicalNode,
|
|
29
29
|
mergeRegister,
|
|
30
30
|
type MutationListener,
|
|
31
31
|
type NodeKey,
|
|
@@ -35,71 +35,13 @@ import {
|
|
|
35
35
|
} from 'lexical';
|
|
36
36
|
import {type JSX, useCallback, useEffect, useMemo, useState} from 'react';
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
data?: TEmbedMatchResult;
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Describes a kind of embed (for example YouTube, a tweet, or Google Maps) that
|
|
50
|
-
* {@link LexicalAutoEmbedPlugin} can detect and insert. Each config has a `type`
|
|
51
|
-
* identifier, a `parseUrl` function that decides whether a URL matches and
|
|
52
|
-
* extracts its data, and an `insertNode` function that inserts the corresponding
|
|
53
|
-
* Lexical node.
|
|
54
|
-
*/
|
|
55
|
-
export interface EmbedConfig<
|
|
56
|
-
TEmbedMatchResultData = unknown,
|
|
57
|
-
TEmbedMatchResult = EmbedMatchResult<TEmbedMatchResultData>,
|
|
58
|
-
> {
|
|
59
|
-
// Used to identify this config e.g. youtube, tweet, google-maps.
|
|
60
|
-
type: string;
|
|
61
|
-
// Determine if a given URL is a match and return url data.
|
|
62
|
-
parseUrl: (
|
|
63
|
-
text: string,
|
|
64
|
-
) => Promise<TEmbedMatchResult | null> | TEmbedMatchResult | null;
|
|
65
|
-
// Create the Lexical embed node from the url data.
|
|
66
|
-
insertNode: (editor: LexicalEditor, result: TEmbedMatchResult) => void;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* A general-purpose regular expression for detecting URLs, provided as a
|
|
71
|
-
* convenience for implementing an {@link EmbedConfig}'s `parseUrl`.
|
|
72
|
-
*/
|
|
73
|
-
export const URL_MATCHER =
|
|
74
|
-
/((https?:\/\/(www\.)?)|(www\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/;
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Command dispatched to start inserting an embed. Its payload is the `type` of
|
|
78
|
-
* the {@link EmbedConfig} to use; {@link LexicalAutoEmbedPlugin} listens for it
|
|
79
|
-
* and runs that config's URL detection flow.
|
|
80
|
-
*/
|
|
81
|
-
export const INSERT_EMBED_COMMAND: LexicalCommand<EmbedConfig['type']> =
|
|
82
|
-
createCommand('INSERT_EMBED_COMMAND');
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* A {@link MenuOption} for the auto-embed menu, pairing a display `title` with
|
|
86
|
-
* an `onSelect` callback invoked when the user chooses to embed the detected
|
|
87
|
-
* URL.
|
|
88
|
-
*/
|
|
89
|
-
export class AutoEmbedOption extends MenuOption {
|
|
90
|
-
title: string;
|
|
91
|
-
onSelect: (targetNode: LexicalNode | null) => void;
|
|
92
|
-
constructor(
|
|
93
|
-
title: string,
|
|
94
|
-
options: {
|
|
95
|
-
onSelect: (targetNode: LexicalNode | null) => void;
|
|
96
|
-
},
|
|
97
|
-
) {
|
|
98
|
-
super(title);
|
|
99
|
-
this.title = title;
|
|
100
|
-
this.onSelect = options.onSelect.bind(this);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
38
|
+
export {
|
|
39
|
+
AutoEmbedOption,
|
|
40
|
+
type EmbedConfig,
|
|
41
|
+
type EmbedMatchResult,
|
|
42
|
+
INSERT_EMBED_COMMAND,
|
|
43
|
+
URL_MATCHER,
|
|
44
|
+
} from '@lexical/react/LexicalAutoEmbedPluginUtils';
|
|
103
45
|
|
|
104
46
|
type LexicalAutoEmbedPluginProps<TEmbedConfig extends EmbedConfig> = {
|
|
105
47
|
/**
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {MenuOption} from '@lexical/react/LexicalMenuOption';
|
|
10
|
+
import {
|
|
11
|
+
createCommand,
|
|
12
|
+
type LexicalCommand,
|
|
13
|
+
type LexicalEditor,
|
|
14
|
+
type LexicalNode,
|
|
15
|
+
} from 'lexical';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The result of matching a URL for an embed: the matched `url`, an `id`
|
|
19
|
+
* identifying the embedded resource, and optional provider-specific `data`.
|
|
20
|
+
*/
|
|
21
|
+
export type EmbedMatchResult<TEmbedMatchResult = unknown> = {
|
|
22
|
+
url: string;
|
|
23
|
+
id: string;
|
|
24
|
+
data?: TEmbedMatchResult;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Describes a kind of embed (for example YouTube, a tweet, or Google Maps) that
|
|
29
|
+
* {@link LexicalAutoEmbedPlugin} can detect and insert. Each config has a `type`
|
|
30
|
+
* identifier, a `parseUrl` function that decides whether a URL matches and
|
|
31
|
+
* extracts its data, and an `insertNode` function that inserts the corresponding
|
|
32
|
+
* Lexical node.
|
|
33
|
+
*/
|
|
34
|
+
export interface EmbedConfig<
|
|
35
|
+
TEmbedMatchResultData = unknown,
|
|
36
|
+
TEmbedMatchResult = EmbedMatchResult<TEmbedMatchResultData>,
|
|
37
|
+
> {
|
|
38
|
+
type: string;
|
|
39
|
+
// Determine if a given URL is a match and return url data.
|
|
40
|
+
parseUrl: (
|
|
41
|
+
text: string,
|
|
42
|
+
) => Promise<TEmbedMatchResult | null> | TEmbedMatchResult | null;
|
|
43
|
+
// Create the Lexical embed node from the url data.
|
|
44
|
+
insertNode: (editor: LexicalEditor, result: TEmbedMatchResult) => void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* A general-purpose regular expression for detecting URLs, provided as a
|
|
49
|
+
* convenience for implementing an {@link EmbedConfig}'s `parseUrl`.
|
|
50
|
+
*/
|
|
51
|
+
export const URL_MATCHER =
|
|
52
|
+
/((https?:\/\/(www\.)?)|(www\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Command dispatched to start inserting an embed. Its payload is the `type` of
|
|
56
|
+
* the {@link EmbedConfig} to use; {@link LexicalAutoEmbedPlugin} listens for it
|
|
57
|
+
* and runs that config's URL detection flow.
|
|
58
|
+
*/
|
|
59
|
+
export const INSERT_EMBED_COMMAND: LexicalCommand<EmbedConfig['type']> =
|
|
60
|
+
createCommand('INSERT_EMBED_COMMAND');
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* A {@link MenuOption} for the auto-embed menu, pairing a display `title` with
|
|
64
|
+
* an `onSelect` callback invoked when the user chooses to embed the detected
|
|
65
|
+
* URL.
|
|
66
|
+
*/
|
|
67
|
+
export class AutoEmbedOption extends MenuOption {
|
|
68
|
+
title: string;
|
|
69
|
+
onSelect: (targetNode: LexicalNode | null) => void;
|
|
70
|
+
constructor(
|
|
71
|
+
title: string,
|
|
72
|
+
options: {
|
|
73
|
+
onSelect: (targetNode: LexicalNode | null) => void;
|
|
74
|
+
},
|
|
75
|
+
) {
|
|
76
|
+
super(title);
|
|
77
|
+
this.title = title;
|
|
78
|
+
this.onSelect = options.onSelect.bind(this);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -6,64 +6,17 @@
|
|
|
6
6
|
*
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
color: string;
|
|
21
|
-
isCollabActive: boolean;
|
|
22
|
-
name: string;
|
|
23
|
-
yjsDocMap: Map<string, Doc>;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
const entries = [
|
|
27
|
-
['Cat', 'rgb(125, 50, 0)'],
|
|
28
|
-
['Dog', 'rgb(100, 0, 0)'],
|
|
29
|
-
['Rabbit', 'rgb(150, 0, 0)'],
|
|
30
|
-
['Frog', 'rgb(200, 0, 0)'],
|
|
31
|
-
['Fox', 'rgb(200, 75, 0)'],
|
|
32
|
-
['Hedgehog', 'rgb(0, 75, 0)'],
|
|
33
|
-
['Pigeon', 'rgb(0, 125, 0)'],
|
|
34
|
-
['Squirrel', 'rgb(75, 100, 0)'],
|
|
35
|
-
['Bear', 'rgb(125, 100, 0)'],
|
|
36
|
-
['Tiger', 'rgb(0, 0, 150)'],
|
|
37
|
-
['Leopard', 'rgb(0, 0, 200)'],
|
|
38
|
-
['Zebra', 'rgb(0, 0, 250)'],
|
|
39
|
-
['Wolf', 'rgb(0, 100, 150)'],
|
|
40
|
-
['Owl', 'rgb(0, 100, 100)'],
|
|
41
|
-
['Gull', 'rgb(100, 0, 100)'],
|
|
42
|
-
['Squid', 'rgb(150, 0, 150)'],
|
|
43
|
-
];
|
|
44
|
-
|
|
45
|
-
const randomEntry = entries[Math.floor(Math.random() * entries.length)];
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* The React context that holds the shared {@link CollaborationContextType} for
|
|
49
|
-
* collaborative editors. Provide it with {@link LexicalCollaboration} and read
|
|
50
|
-
* it with {@link useCollaborationContext}.
|
|
51
|
-
*/
|
|
52
|
-
export const CollaborationContext =
|
|
53
|
-
createContext<CollaborationContextType | null>(null);
|
|
54
|
-
|
|
55
|
-
function newContext() {
|
|
56
|
-
return {
|
|
57
|
-
color: randomEntry[1],
|
|
58
|
-
isCollabActive: false,
|
|
59
|
-
name: randomEntry[0],
|
|
60
|
-
yjsDocMap: new Map(),
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// This is here to help the transition post-#7818, however should be removed in a future release as
|
|
65
|
-
// a shared context across editors is likely to lead to bugs.
|
|
66
|
-
const UNSAFE_GLOBAL_CONTEXT = newContext();
|
|
9
|
+
import {
|
|
10
|
+
CollaborationContext,
|
|
11
|
+
newContext,
|
|
12
|
+
} from '@lexical/react/LexicalCollaborationContextUtils';
|
|
13
|
+
import {useMemo} from 'react';
|
|
14
|
+
|
|
15
|
+
export {
|
|
16
|
+
CollaborationContext,
|
|
17
|
+
type CollaborationContextType,
|
|
18
|
+
useCollaborationContext,
|
|
19
|
+
} from '@lexical/react/LexicalCollaborationContextUtils';
|
|
67
20
|
|
|
68
21
|
/**
|
|
69
22
|
* A provider component that creates a fresh {@link CollaborationContextType}
|
|
@@ -82,35 +35,3 @@ export function LexicalCollaboration({children}: {children: React.ReactNode}) {
|
|
|
82
35
|
</CollaborationContext.Provider>
|
|
83
36
|
);
|
|
84
37
|
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Reads the current {@link CollaborationContextType} from the nearest
|
|
88
|
-
* {@link LexicalCollaboration} provider. Optionally pass `username` and `color`
|
|
89
|
-
* to set the local user's display name and cursor color.
|
|
90
|
-
*
|
|
91
|
-
* @returns The active collaboration context.
|
|
92
|
-
*/
|
|
93
|
-
export function useCollaborationContext(
|
|
94
|
-
username?: string,
|
|
95
|
-
color?: string,
|
|
96
|
-
): CollaborationContextType {
|
|
97
|
-
let collabContext = useContext(CollaborationContext);
|
|
98
|
-
devInvariant(
|
|
99
|
-
collabContext != null,
|
|
100
|
-
'useCollaborationContext: no context provider found',
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
collabContext = collabContext ?? UNSAFE_GLOBAL_CONTEXT;
|
|
104
|
-
|
|
105
|
-
if (username != null) {
|
|
106
|
-
// eslint-disable-next-line react-hooks/immutability
|
|
107
|
-
collabContext.name = username;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (color != null) {
|
|
111
|
-
// eslint-disable-next-line react-hooks/immutability
|
|
112
|
-
collabContext.color = color;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return collabContext;
|
|
116
|
-
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type {Doc} from 'yjs';
|
|
10
|
+
|
|
11
|
+
import devInvariant from '@lexical/internal/devInvariant';
|
|
12
|
+
import {createContext, useContext} from 'react';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The value stored in the {@link CollaborationContext}: the local user's
|
|
16
|
+
* display `name` and cursor `color`, whether collaboration is currently active,
|
|
17
|
+
* and the map of Yjs documents shared by the editors under this provider.
|
|
18
|
+
*/
|
|
19
|
+
export type CollaborationContextType = {
|
|
20
|
+
color: string;
|
|
21
|
+
isCollabActive: boolean;
|
|
22
|
+
name: string;
|
|
23
|
+
yjsDocMap: Map<string, Doc>;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const entries = [
|
|
27
|
+
['Cat', 'rgb(125, 50, 0)'],
|
|
28
|
+
['Dog', 'rgb(100, 0, 0)'],
|
|
29
|
+
['Rabbit', 'rgb(150, 0, 0)'],
|
|
30
|
+
['Frog', 'rgb(200, 0, 0)'],
|
|
31
|
+
['Fox', 'rgb(200, 75, 0)'],
|
|
32
|
+
['Hedgehog', 'rgb(0, 75, 0)'],
|
|
33
|
+
['Pigeon', 'rgb(0, 125, 0)'],
|
|
34
|
+
['Squirrel', 'rgb(75, 100, 0)'],
|
|
35
|
+
['Bear', 'rgb(125, 100, 0)'],
|
|
36
|
+
['Tiger', 'rgb(0, 0, 150)'],
|
|
37
|
+
['Leopard', 'rgb(0, 0, 200)'],
|
|
38
|
+
['Zebra', 'rgb(0, 0, 250)'],
|
|
39
|
+
['Wolf', 'rgb(0, 100, 150)'],
|
|
40
|
+
['Owl', 'rgb(0, 100, 100)'],
|
|
41
|
+
['Gull', 'rgb(100, 0, 100)'],
|
|
42
|
+
['Squid', 'rgb(150, 0, 150)'],
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
const randomEntry = entries[Math.floor(Math.random() * entries.length)];
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The React context that holds the shared {@link CollaborationContextType} for
|
|
49
|
+
* collaborative editors. Provide it with {@link LexicalCollaboration} and read
|
|
50
|
+
* it with {@link useCollaborationContext}.
|
|
51
|
+
*/
|
|
52
|
+
export const CollaborationContext =
|
|
53
|
+
createContext<CollaborationContextType | null>(null);
|
|
54
|
+
|
|
55
|
+
/** @internal */
|
|
56
|
+
export function newContext(): CollaborationContextType {
|
|
57
|
+
return {
|
|
58
|
+
color: randomEntry[1],
|
|
59
|
+
isCollabActive: false,
|
|
60
|
+
name: randomEntry[0],
|
|
61
|
+
yjsDocMap: new Map(),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// This is here to help the transition post-#7818, however should be removed in a future release as
|
|
66
|
+
// a shared context across editors is likely to lead to bugs.
|
|
67
|
+
const UNSAFE_GLOBAL_CONTEXT = newContext();
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Reads the current {@link CollaborationContextType} from the nearest
|
|
71
|
+
* {@link LexicalCollaboration} provider. Optionally pass `username` and `color`
|
|
72
|
+
* to set the local user's display name and cursor color.
|
|
73
|
+
*
|
|
74
|
+
* @returns The active collaboration context.
|
|
75
|
+
*/
|
|
76
|
+
export function useCollaborationContext(
|
|
77
|
+
username?: string,
|
|
78
|
+
color?: string,
|
|
79
|
+
): CollaborationContextType {
|
|
80
|
+
let collabContext = useContext(CollaborationContext);
|
|
81
|
+
devInvariant(
|
|
82
|
+
collabContext != null,
|
|
83
|
+
'useCollaborationContext: no context provider found',
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
collabContext = collabContext ?? UNSAFE_GLOBAL_CONTEXT;
|
|
87
|
+
|
|
88
|
+
if (username != null) {
|
|
89
|
+
// eslint-disable-next-line react-hooks/immutability
|
|
90
|
+
collabContext.name = username;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (color != null) {
|
|
94
|
+
// eslint-disable-next-line react-hooks/immutability
|
|
95
|
+
collabContext.color = color;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return collabContext;
|
|
99
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type {JSX, RefObject} from 'react';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The base class for an item shown in a {@link LexicalTypeaheadMenuPlugin} or
|
|
13
|
+
* {@link LexicalNodeMenuPlugin} menu. Each option has a unique `key` and a `ref`
|
|
14
|
+
* to its rendered element (used for scrolling and keyboard navigation).
|
|
15
|
+
* Subclass it to attach your own data such as a label or callback.
|
|
16
|
+
*
|
|
17
|
+
* Its own module, and not `shared/LexicalMenu`, because that module is not an
|
|
18
|
+
* entry point: it is inlined into every entry that reaches it, which would give
|
|
19
|
+
* each of them a different `MenuOption` class. Its own module rather than one
|
|
20
|
+
* of the plugin entry points, because those export React components — anything
|
|
21
|
+
* importing this from one of them sits behind that component's Fast Refresh
|
|
22
|
+
* boundary and is invalidated whenever the component changes.
|
|
23
|
+
*/
|
|
24
|
+
export class MenuOption {
|
|
25
|
+
key: string;
|
|
26
|
+
ref?: RefObject<HTMLElement | null>;
|
|
27
|
+
icon?: JSX.Element;
|
|
28
|
+
title?: JSX.Element | string;
|
|
29
|
+
|
|
30
|
+
constructor(key: string) {
|
|
31
|
+
this.key = key;
|
|
32
|
+
this.ref = {current: null};
|
|
33
|
+
this.setRefElement = this.setRefElement.bind(this);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
setRefElement(element: HTMLElement | null) {
|
|
37
|
+
this.ref = {current: element};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -7,17 +7,14 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
|
|
10
|
-
import {getScrollParent as getScrollParent_} from '@lexical/utils';
|
|
11
10
|
import {
|
|
12
11
|
$getSelection,
|
|
13
12
|
$isRangeSelection,
|
|
14
13
|
$isTextNode,
|
|
15
14
|
COMMAND_PRIORITY_LOW,
|
|
16
15
|
type CommandListenerPriority,
|
|
17
|
-
createCommand,
|
|
18
16
|
getDOMSelection,
|
|
19
17
|
getDOMSelectionPoints,
|
|
20
|
-
type LexicalCommand,
|
|
21
18
|
type LexicalEditor,
|
|
22
19
|
type RangeSelection,
|
|
23
20
|
type TextNode,
|
|
@@ -40,14 +37,6 @@ import {
|
|
|
40
37
|
useMenuAnchorRef,
|
|
41
38
|
} from './shared/LexicalMenu';
|
|
42
39
|
|
|
43
|
-
/**
|
|
44
|
-
* The default set of punctuation characters (as a character-class fragment)
|
|
45
|
-
* that terminate a typeahead query. Used as the default `punctuation` option of
|
|
46
|
-
* {@link useBasicTypeaheadTriggerMatch}.
|
|
47
|
-
*/
|
|
48
|
-
export const PUNCTUATION =
|
|
49
|
-
'\\.,\\+\\*\\?\\$\\@\\|#{}\\(\\)\\^\\-\\[\\]\\\\/!%\'"~=<>_:;';
|
|
50
|
-
|
|
51
40
|
function getTextUpToAnchor(selection: RangeSelection): string | null {
|
|
52
41
|
const anchor = selection.anchor;
|
|
53
42
|
if (anchor.type !== 'text') {
|
|
@@ -122,75 +111,12 @@ function isSelectionOnEntityBoundary(
|
|
|
122
111
|
}
|
|
123
112
|
|
|
124
113
|
export {useDynamicPositioning} from './shared/LexicalMenu';
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
* {@link MenuRenderFn}s can handle it to implement their own scrolling.
|
|
132
|
-
*/
|
|
133
|
-
export const SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND: LexicalCommand<{
|
|
134
|
-
index: number;
|
|
135
|
-
option: MenuOption;
|
|
136
|
-
}> = createCommand('SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND');
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Builds a {@link TriggerFn} for the common case of a single-character
|
|
140
|
-
* `trigger` (such as `@` or `#`) followed by a query. The returned function
|
|
141
|
-
* matches when the trigger is preceded by whitespace or the start of the line
|
|
142
|
-
* and is followed by between `minLength` and `maxLength` non-`punctuation`
|
|
143
|
-
* characters (optionally allowing whitespace).
|
|
144
|
-
*
|
|
145
|
-
* @returns A memoized trigger function for {@link LexicalTypeaheadMenuPlugin}.
|
|
146
|
-
*/
|
|
147
|
-
export function useBasicTypeaheadTriggerMatch(
|
|
148
|
-
trigger: string,
|
|
149
|
-
{
|
|
150
|
-
minLength = 1,
|
|
151
|
-
maxLength = 75,
|
|
152
|
-
punctuation = PUNCTUATION,
|
|
153
|
-
allowWhitespace = false,
|
|
154
|
-
}: {
|
|
155
|
-
minLength?: number;
|
|
156
|
-
maxLength?: number;
|
|
157
|
-
punctuation?: string;
|
|
158
|
-
allowWhitespace?: boolean;
|
|
159
|
-
},
|
|
160
|
-
): TriggerFn {
|
|
161
|
-
return useCallback(
|
|
162
|
-
(text: string) => {
|
|
163
|
-
const validCharsSuffix = allowWhitespace ? '' : '\\s';
|
|
164
|
-
const validChars = '[^' + trigger + punctuation + validCharsSuffix + ']';
|
|
165
|
-
const TypeaheadTriggerRegex = new RegExp(
|
|
166
|
-
'(^|\\s|\\()(' +
|
|
167
|
-
'[' +
|
|
168
|
-
trigger +
|
|
169
|
-
']' +
|
|
170
|
-
'((?:' +
|
|
171
|
-
validChars +
|
|
172
|
-
'){0,' +
|
|
173
|
-
maxLength +
|
|
174
|
-
'})' +
|
|
175
|
-
')$',
|
|
176
|
-
);
|
|
177
|
-
const match = TypeaheadTriggerRegex.exec(text);
|
|
178
|
-
if (match !== null) {
|
|
179
|
-
const maybeLeadingWhitespace = match[1];
|
|
180
|
-
const matchingString = match[3];
|
|
181
|
-
if (matchingString.length >= minLength) {
|
|
182
|
-
return {
|
|
183
|
-
leadOffset: match.index + maybeLeadingWhitespace.length,
|
|
184
|
-
matchingString,
|
|
185
|
-
replaceableString: match[2],
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
return null;
|
|
190
|
-
},
|
|
191
|
-
[allowWhitespace, trigger, punctuation, maxLength, minLength],
|
|
192
|
-
);
|
|
193
|
-
}
|
|
114
|
+
export {
|
|
115
|
+
getScrollParent,
|
|
116
|
+
PUNCTUATION,
|
|
117
|
+
SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND,
|
|
118
|
+
useBasicTypeaheadTriggerMatch,
|
|
119
|
+
} from '@lexical/react/LexicalTypeaheadMenuPluginUtils';
|
|
194
120
|
|
|
195
121
|
/**
|
|
196
122
|
* Props for the {@link LexicalTypeaheadMenuPlugin} component.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type {MenuOption, TriggerFn} from './shared/LexicalMenu';
|
|
10
|
+
|
|
11
|
+
import {getScrollParent as getScrollParent_} from '@lexical/utils';
|
|
12
|
+
import {createCommand, type LexicalCommand} from 'lexical';
|
|
13
|
+
import {useCallback} from 'react';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The default set of punctuation characters (as a character-class fragment)
|
|
17
|
+
* that terminate a typeahead query. Used as the default `punctuation` option of
|
|
18
|
+
* {@link useBasicTypeaheadTriggerMatch}.
|
|
19
|
+
*/
|
|
20
|
+
export const PUNCTUATION =
|
|
21
|
+
'\\.,\\+\\*\\?\\$\\@\\|#{}\\(\\)\\^\\-\\[\\]\\\\/!%\'"~=<>_:;';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Command dispatched while the typeahead menu is open to scroll the option at
|
|
25
|
+
* the given `index` into view. The built-in menu hook registers a default
|
|
26
|
+
* handler; custom implementations can register their own handler at a higher
|
|
27
|
+
* priority to override the scrolling behavior.
|
|
28
|
+
*/
|
|
29
|
+
export const SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND: LexicalCommand<{
|
|
30
|
+
index: number;
|
|
31
|
+
option: MenuOption;
|
|
32
|
+
}> = createCommand('SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND');
|
|
33
|
+
|
|
34
|
+
/** @deprecated Moved to `@lexical/utils`. Import `getScrollParent` from there. */
|
|
35
|
+
export const getScrollParent = getScrollParent_;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Builds a {@link TriggerFn} for the common case of a single-character
|
|
39
|
+
* `trigger` (such as `@` or `#`) followed by a query. The returned function
|
|
40
|
+
* matches when the trigger is preceded by whitespace, an open parenthesis,
|
|
41
|
+
* or the start of the line
|
|
42
|
+
* and is followed by between `minLength` and `maxLength` non-`punctuation`
|
|
43
|
+
* characters (optionally allowing whitespace).
|
|
44
|
+
*
|
|
45
|
+
* @returns A memoized trigger function for {@link LexicalTypeaheadMenuPlugin}.
|
|
46
|
+
*/
|
|
47
|
+
export function useBasicTypeaheadTriggerMatch(
|
|
48
|
+
trigger: string,
|
|
49
|
+
{
|
|
50
|
+
minLength = 1,
|
|
51
|
+
maxLength = 75,
|
|
52
|
+
punctuation = PUNCTUATION,
|
|
53
|
+
allowWhitespace = false,
|
|
54
|
+
}: {
|
|
55
|
+
minLength?: number;
|
|
56
|
+
maxLength?: number;
|
|
57
|
+
punctuation?: string;
|
|
58
|
+
allowWhitespace?: boolean;
|
|
59
|
+
},
|
|
60
|
+
): TriggerFn {
|
|
61
|
+
return useCallback(
|
|
62
|
+
(text: string) => {
|
|
63
|
+
const validCharsSuffix = allowWhitespace ? '' : '\\s';
|
|
64
|
+
const validChars = '[^' + trigger + punctuation + validCharsSuffix + ']';
|
|
65
|
+
const TypeaheadTriggerRegex = new RegExp(
|
|
66
|
+
'(^|\\s|\\()(' +
|
|
67
|
+
'[' +
|
|
68
|
+
trigger +
|
|
69
|
+
']' +
|
|
70
|
+
'((?:' +
|
|
71
|
+
validChars +
|
|
72
|
+
'){0,' +
|
|
73
|
+
maxLength +
|
|
74
|
+
'})' +
|
|
75
|
+
')$',
|
|
76
|
+
);
|
|
77
|
+
const match = TypeaheadTriggerRegex.exec(text);
|
|
78
|
+
if (match !== null) {
|
|
79
|
+
const maybeLeadingWhitespace = match[1];
|
|
80
|
+
const matchingString = match[3];
|
|
81
|
+
if (matchingString.length >= minLength) {
|
|
82
|
+
return {
|
|
83
|
+
leadOffset: match.index + maybeLeadingWhitespace.length,
|
|
84
|
+
matchingString,
|
|
85
|
+
replaceableString: match[2],
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
},
|
|
91
|
+
[allowWhitespace, trigger, punctuation, maxLength, minLength],
|
|
92
|
+
);
|
|
93
|
+
}
|