Lexical: Hashtag, emoji autocompletion
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
parent
46ffd053bb
commit
9c49fc3d8a
10 changed files with 184 additions and 143 deletions
|
@ -6,6 +6,7 @@ import { defineMessages, IntlShape } from 'react-intl';
|
|||
import api from 'soapbox/api';
|
||||
import { isNativeEmoji } from 'soapbox/features/emoji';
|
||||
import emojiSearch from 'soapbox/features/emoji/search';
|
||||
import { normalizeTag } from 'soapbox/normalizers';
|
||||
import { tagHistory } from 'soapbox/settings';
|
||||
import toast from 'soapbox/toast';
|
||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||
|
@ -29,7 +30,7 @@ import type { History } from 'soapbox/types/history';
|
|||
|
||||
const { CancelToken, isCancel } = axios;
|
||||
|
||||
let cancelFetchComposeSuggestionsAccounts: Canceler;
|
||||
let cancelFetchComposeSuggestions: Canceler;
|
||||
|
||||
const COMPOSE_CHANGE = 'COMPOSE_CHANGE';
|
||||
const COMPOSE_SUBMIT_REQUEST = 'COMPOSE_SUBMIT_REQUEST';
|
||||
|
@ -500,8 +501,8 @@ const setGroupTimelineVisible = (composeId: string, groupTimelineVisible: boolea
|
|||
});
|
||||
|
||||
const clearComposeSuggestions = (composeId: string) => {
|
||||
if (cancelFetchComposeSuggestionsAccounts) {
|
||||
cancelFetchComposeSuggestionsAccounts();
|
||||
if (cancelFetchComposeSuggestions) {
|
||||
cancelFetchComposeSuggestions();
|
||||
}
|
||||
return {
|
||||
type: COMPOSE_SUGGESTIONS_CLEAR,
|
||||
|
@ -510,12 +511,12 @@ const clearComposeSuggestions = (composeId: string) => {
|
|||
};
|
||||
|
||||
const fetchComposeSuggestionsAccounts = throttle((dispatch, getState, composeId, token) => {
|
||||
if (cancelFetchComposeSuggestionsAccounts) {
|
||||
cancelFetchComposeSuggestionsAccounts(composeId);
|
||||
if (cancelFetchComposeSuggestions) {
|
||||
cancelFetchComposeSuggestions(composeId);
|
||||
}
|
||||
api(getState).get('/api/v1/accounts/search', {
|
||||
cancelToken: new CancelToken(cancel => {
|
||||
cancelFetchComposeSuggestionsAccounts = cancel;
|
||||
cancelFetchComposeSuggestions = cancel;
|
||||
}),
|
||||
params: {
|
||||
q: token.slice(1),
|
||||
|
@ -540,10 +541,37 @@ const fetchComposeSuggestionsEmojis = (dispatch: AppDispatch, getState: () => Ro
|
|||
};
|
||||
|
||||
const fetchComposeSuggestionsTags = (dispatch: AppDispatch, getState: () => RootState, composeId: string, token: string) => {
|
||||
if (cancelFetchComposeSuggestions) {
|
||||
cancelFetchComposeSuggestions(composeId);
|
||||
}
|
||||
|
||||
const state = getState();
|
||||
|
||||
const instance = state.instance;
|
||||
const { trends } = getFeatures(instance);
|
||||
|
||||
if (trends) {
|
||||
const currentTrends = state.trends.items;
|
||||
|
||||
dispatch(updateSuggestionTags(composeId, token, currentTrends));
|
||||
return dispatch(updateSuggestionTags(composeId, token, currentTrends));
|
||||
}
|
||||
|
||||
api(getState).get('/api/v2/search', {
|
||||
cancelToken: new CancelToken(cancel => {
|
||||
cancelFetchComposeSuggestions = cancel;
|
||||
}),
|
||||
params: {
|
||||
q: token.slice(1),
|
||||
limit: 4,
|
||||
type: 'hashtags',
|
||||
},
|
||||
}).then(response => {
|
||||
dispatch(updateSuggestionTags(composeId, token, response.data?.hashtags.map(normalizeTag)));
|
||||
}).catch(error => {
|
||||
if (!isCancel(error)) {
|
||||
toast.showAlertForError(error);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const fetchComposeSuggestions = (composeId: string, token: string) =>
|
||||
|
@ -602,11 +630,11 @@ const selectComposeSuggestion = (composeId: string, position: number, token: str
|
|||
});
|
||||
};
|
||||
|
||||
const updateSuggestionTags = (composeId: string, token: string, currentTrends: ImmutableList<Tag>) => ({
|
||||
const updateSuggestionTags = (composeId: string, token: string, tags: ImmutableList<Tag>) => ({
|
||||
type: COMPOSE_SUGGESTION_TAGS_UPDATE,
|
||||
id: composeId,
|
||||
token,
|
||||
currentTrends,
|
||||
tags,
|
||||
});
|
||||
|
||||
const updateTagHistory = (composeId: string, tags: string[]) => ({
|
||||
|
|
|
@ -5,16 +5,21 @@ import { joinPublicPath } from 'soapbox/utils/static';
|
|||
|
||||
interface IEmoji extends React.ImgHTMLAttributes<HTMLImageElement> {
|
||||
/** Unicode emoji character. */
|
||||
emoji: string
|
||||
emoji?: string
|
||||
}
|
||||
|
||||
/** A single emoji image. */
|
||||
const Emoji: React.FC<IEmoji> = (props): JSX.Element | null => {
|
||||
const { emoji, alt, src, ...rest } = props;
|
||||
const codepoints = toCodePoints(removeVS16s(emoji));
|
||||
const filename = codepoints.join('-');
|
||||
|
||||
if (!filename) return null;
|
||||
let filename;
|
||||
|
||||
if (emoji) {
|
||||
const codepoints = toCodePoints(removeVS16s(emoji));
|
||||
filename = codepoints.join('-');
|
||||
}
|
||||
|
||||
if (!filename && !src) return null;
|
||||
|
||||
return (
|
||||
<img
|
||||
|
|
|
@ -29,7 +29,6 @@ import { useAppDispatch, useFeatures } from 'soapbox/hooks';
|
|||
import nodes from './nodes';
|
||||
import { AutosuggestPlugin } from './plugins/autosuggest-plugin';
|
||||
import DraggableBlockPlugin from './plugins/draggable-block-plugin';
|
||||
import { EmojiPlugin } from './plugins/emoji-plugin';
|
||||
import FloatingLinkEditorPlugin from './plugins/floating-link-editor-plugin';
|
||||
import FloatingTextFormatToolbarPlugin from './plugins/floating-text-format-toolbar-plugin';
|
||||
import { MentionPlugin } from './plugins/mention-plugin';
|
||||
|
@ -143,7 +142,6 @@ const ComposeEditor = React.forwardRef<string, any>(({ composeId, condensed, onF
|
|||
/>
|
||||
<HistoryPlugin />
|
||||
<HashtagPlugin />
|
||||
<EmojiPlugin />
|
||||
<MentionPlugin />
|
||||
<AutosuggestPlugin composeId={composeId} suggestionsHidden={suggestionsHidden} setSuggestionsHidden={setSuggestionsHidden} />
|
||||
{features.richText && <LinkPlugin />}
|
||||
|
|
|
@ -1,54 +1,74 @@
|
|||
/**
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
import { $applyNodeReplacement, DecoratorNode } from 'lexical';
|
||||
import React from 'react';
|
||||
|
||||
import { addClassNamesToElement } from '@lexical/utils';
|
||||
import { $applyNodeReplacement, TextNode } from 'lexical';
|
||||
import { Emoji } from 'soapbox/components/ui';
|
||||
|
||||
import type {
|
||||
DOMExportOutput,
|
||||
EditorConfig,
|
||||
LexicalNode,
|
||||
NodeKey,
|
||||
SerializedTextNode,
|
||||
SerializedLexicalNode,
|
||||
Spread,
|
||||
} from 'lexical';
|
||||
|
||||
class EmojiNode extends TextNode {
|
||||
type SerializedEmojiNode = Spread<{
|
||||
name: string
|
||||
src: string
|
||||
type: 'emoji'
|
||||
version: 1
|
||||
}, SerializedLexicalNode>;
|
||||
|
||||
static getType(): string {
|
||||
class EmojiNode extends DecoratorNode<JSX.Element> {
|
||||
|
||||
__name: string;
|
||||
__src: string;
|
||||
|
||||
static getType(): 'emoji' {
|
||||
return 'emoji';
|
||||
}
|
||||
|
||||
static clone(node: EmojiNode): EmojiNode {
|
||||
return new EmojiNode(node.__text, node.__key);
|
||||
return new EmojiNode(node.__name, node.__key);
|
||||
}
|
||||
|
||||
constructor(text: string, key?: NodeKey) {
|
||||
super(text, key);
|
||||
constructor(name: string, src: string, key?: NodeKey) {
|
||||
super(key);
|
||||
this.__name = name;
|
||||
this.__src = src;
|
||||
}
|
||||
|
||||
createDOM(config: EditorConfig): HTMLElement {
|
||||
const element = super.createDOM(config);
|
||||
addClassNamesToElement(element, config.theme.emoji);
|
||||
return element;
|
||||
const span = document.createElement('span');
|
||||
const theme = config.theme;
|
||||
const className = theme.emoji;
|
||||
if (className !== undefined) {
|
||||
span.className = className;
|
||||
}
|
||||
return span;
|
||||
}
|
||||
|
||||
static importJSON(serializedNode: SerializedTextNode): EmojiNode {
|
||||
const node = $createEmojiNode(serializedNode.text);
|
||||
node.setFormat(serializedNode.format);
|
||||
node.setDetail(serializedNode.detail);
|
||||
node.setMode(serializedNode.mode);
|
||||
node.setStyle(serializedNode.style);
|
||||
exportDOM(): DOMExportOutput {
|
||||
const element = document.createElement('img');
|
||||
element.setAttribute('src', this.__src);
|
||||
element.setAttribute('alt', this.__name);
|
||||
element.classList.add('h-4', 'w-4');
|
||||
return { element };
|
||||
}
|
||||
|
||||
static importJSON(serializedNode: SerializedEmojiNode): EmojiNode {
|
||||
const { name, src } =
|
||||
serializedNode;
|
||||
const node = $createEmojiNode(name, src);
|
||||
return node;
|
||||
}
|
||||
|
||||
exportJSON(): SerializedTextNode {
|
||||
exportJSON(): SerializedEmojiNode {
|
||||
return {
|
||||
...super.exportJSON(),
|
||||
name: this.__name,
|
||||
src: this.__src,
|
||||
type: 'emoji',
|
||||
version: 1,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -60,9 +80,15 @@ class EmojiNode extends TextNode {
|
|||
return true;
|
||||
}
|
||||
|
||||
decorate(): JSX.Element {
|
||||
return (
|
||||
<Emoji src={this.__src} alt={this.__name} className='emojione h-4 w-4' />
|
||||
);
|
||||
}
|
||||
|
||||
const $createEmojiNode = (text = ''): EmojiNode => $applyNodeReplacement(new EmojiNode(text).setMode('token'));
|
||||
}
|
||||
|
||||
const $createEmojiNode = (name = '', src: string): EmojiNode => $applyNodeReplacement(new EmojiNode(name, src));
|
||||
|
||||
const $isEmojiNode = (
|
||||
node: LexicalNode | null | undefined,
|
||||
|
|
|
@ -26,18 +26,16 @@ import React, {
|
|||
import ReactDOM from 'react-dom';
|
||||
|
||||
import { fetchComposeSuggestions } from 'soapbox/actions/compose';
|
||||
import { useEmoji } from 'soapbox/actions/emojis';
|
||||
import AutosuggestEmoji from 'soapbox/components/autosuggest-emoji';
|
||||
import { isNativeEmoji } from 'soapbox/features/emoji';
|
||||
import { useAppDispatch, useCompose } from 'soapbox/hooks';
|
||||
import { textAtCursorMatchesToken } from 'soapbox/utils/suggestions';
|
||||
|
||||
import AutosuggestAccount from '../../components/autosuggest-account';
|
||||
|
||||
import { MENTION_REGEX } from './mention-plugin';
|
||||
|
||||
import type { AutoSuggestion } from 'soapbox/components/autosuggest-input';
|
||||
|
||||
|
||||
const EMOJI_REGEX = new RegExp('(^|$|(?:^|\\s))([:])([a-z\\d_-]+([:]?))', 'i');
|
||||
|
||||
export type QueryMatch = {
|
||||
leadOffset: number
|
||||
matchingString: string
|
||||
|
@ -75,15 +73,6 @@ function tryToPositionRange(leadOffset: number, range: Range): boolean {
|
|||
return true;
|
||||
}
|
||||
|
||||
function getQueryTextForSearch(editor: LexicalEditor): string | null {
|
||||
const state = editor.getEditorState();
|
||||
const node = (state._selection as RangeSelection)?.anchor?.getNode();
|
||||
|
||||
if (node && (node.getType() === 'mention' || node.getType() === 'text')) return node.getTextContent();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function isSelectionOnEntityBoundary(
|
||||
editor: LexicalEditor,
|
||||
offset: number,
|
||||
|
@ -309,34 +298,60 @@ export function AutosuggestPlugin({
|
|||
const onSelectSuggestion: React.MouseEventHandler<HTMLDivElement> = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const suggestion = suggestions.get(e.currentTarget.getAttribute('data-index') as any);
|
||||
const suggestion = suggestions.get(e.currentTarget.getAttribute('data-index') as any) as AutoSuggestion;
|
||||
|
||||
editor.update(() => {
|
||||
|
||||
dispatch((_, getState) => {
|
||||
dispatch((dispatch, getState) => {
|
||||
const state = editor.getEditorState();
|
||||
const node = (state._selection as RangeSelection)?.anchor?.getNode();
|
||||
|
||||
if (typeof suggestion === 'object' && suggestion.id) {
|
||||
dispatch(useEmoji(suggestion)); // eslint-disable-line react-hooks/rules-of-hooks
|
||||
|
||||
const { leadOffset, matchingString } = resolution!.match;
|
||||
|
||||
if (isNativeEmoji(suggestion)) {
|
||||
node.spliceText(leadOffset - 1, matchingString.length, `${suggestion.native} `, true);
|
||||
} else {
|
||||
// const completion = suggestion.colons;
|
||||
// node.replace($createEmojiNode(completion, suggestion.imageUrl));
|
||||
}
|
||||
} else if ((suggestion as string)[0] === '#') {
|
||||
node.setTextContent(`${suggestion} `);
|
||||
node.select();
|
||||
} else {
|
||||
const content = getState().accounts.get(suggestion)!.acct;
|
||||
|
||||
node.setTextContent(`@${content} `);
|
||||
node.select();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const checkForMatch = useCallback((text: string) => {
|
||||
const matchArr = MENTION_REGEX.exec(text) || EMOJI_REGEX.exec(text);
|
||||
const getQueryTextForSearch = (editor: LexicalEditor) => {
|
||||
const state = editor.getEditorState();
|
||||
const node = (state._selection as RangeSelection)?.anchor?.getNode();
|
||||
|
||||
if (!matchArr) return null;
|
||||
if (!node) return null;
|
||||
|
||||
dispatch(fetchComposeSuggestions(composeId, matchArr[0]?.trim()));
|
||||
if (['mention', 'hashtag'].includes(node.getType())) {
|
||||
const matchingString = node.getTextContent();
|
||||
|
||||
return {
|
||||
leadOffset: matchArr.index,
|
||||
matchingString: matchArr[0],
|
||||
return { leadOffset: 0, matchingString };
|
||||
}
|
||||
|
||||
if (node.getType() === 'text') {
|
||||
const [leadOffset, matchingString] = textAtCursorMatchesToken(node.getTextContent(), (state._selection as RangeSelection)?.anchor?.offset, [':']);
|
||||
|
||||
if (!leadOffset || !matchingString) return null;
|
||||
|
||||
return { leadOffset, matchingString };
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
}, []);
|
||||
|
||||
const renderSuggestion = (suggestion: AutoSuggestion, i: number) => {
|
||||
let inner;
|
||||
|
@ -345,6 +360,9 @@ export function AutosuggestPlugin({
|
|||
if (typeof suggestion === 'object') {
|
||||
inner = <AutosuggestEmoji emoji={suggestion} />;
|
||||
key = suggestion.id;
|
||||
} else if (suggestion[0] === '#') {
|
||||
inner = suggestion;
|
||||
key = suggestion;
|
||||
} else {
|
||||
inner = <AutosuggestAccount id={suggestion} />;
|
||||
key = suggestion;
|
||||
|
@ -382,17 +400,16 @@ export function AutosuggestPlugin({
|
|||
const updateListener = () => {
|
||||
editor.getEditorState().read(() => {
|
||||
const range = document.createRange();
|
||||
const text = getQueryTextForSearch(editor);
|
||||
const match = getQueryTextForSearch(editor);
|
||||
|
||||
if (!text) {
|
||||
if (!match) {
|
||||
closeTypeahead();
|
||||
return;
|
||||
}
|
||||
|
||||
const match = checkForMatch(text);
|
||||
dispatch(fetchComposeSuggestions(composeId, match.matchingString.trim()));
|
||||
|
||||
if (
|
||||
match !== null &&
|
||||
!isSelectionOnEntityBoundary(editor, match.leadOffset)
|
||||
) {
|
||||
const isRangePositioned = tryToPositionRange(match.leadOffset, range);
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
/**
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
||||
import { useLexicalTextEntity } from '@lexical/react/useLexicalTextEntity';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
|
||||
import { $createEmojiNode, EmojiNode } from '../nodes/emoji-node';
|
||||
|
||||
|
||||
import type { TextNode } from 'lexical';
|
||||
|
||||
const REGEX = new RegExp('ggfafsdasdf(^|$|(?:^|\\s))([:])([a-z\\d_-]+([:]))', 'i');
|
||||
|
||||
export const getEmojiMatch = (text: string) => {
|
||||
const matchArr = REGEX.exec(text);
|
||||
|
||||
if (!matchArr) return null;
|
||||
return matchArr;
|
||||
};
|
||||
|
||||
export const EmojiPlugin = (): JSX.Element | null => {
|
||||
const [editor] = useLexicalComposerContext();
|
||||
|
||||
useEffect(() => {
|
||||
if (!editor.hasNodes([EmojiNode])) {
|
||||
throw new Error('EmojiPlugin: EmojiNode not registered on editor');
|
||||
}
|
||||
}, [editor]);
|
||||
|
||||
const createEmojiNode = useCallback((textNode: TextNode): EmojiNode => {
|
||||
return $createEmojiNode(textNode.getTextContent());
|
||||
}, []);
|
||||
|
||||
const getEntityMatch = useCallback((text: string) => {
|
||||
const matchArr = getEmojiMatch(text);
|
||||
|
||||
if (!matchArr) return null;
|
||||
|
||||
const emojiLength = matchArr[3].length + 1;
|
||||
const startOffset = matchArr.index + matchArr[1].length;
|
||||
const endOffset = startOffset + emojiLength;
|
||||
return {
|
||||
end: endOffset,
|
||||
start: startOffset,
|
||||
};
|
||||
}, []);
|
||||
|
||||
useLexicalTextEntity<EmojiNode>(
|
||||
getEntityMatch,
|
||||
EmojiNode,
|
||||
createEmojiNode,
|
||||
);
|
||||
|
||||
return null;
|
||||
};
|
|
@ -183,11 +183,11 @@ const insertSuggestion = (compose: Compose, position: number, token: string, com
|
|||
});
|
||||
};
|
||||
|
||||
const updateSuggestionTags = (compose: Compose, token: string, currentTrends: ImmutableList<Tag>) => {
|
||||
const updateSuggestionTags = (compose: Compose, token: string, tags: ImmutableList<Tag>) => {
|
||||
const prefix = token.slice(1);
|
||||
|
||||
return compose.merge({
|
||||
suggestions: ImmutableList(currentTrends
|
||||
suggestions: ImmutableList(tags
|
||||
.filter((tag) => tag.get('name').toLowerCase().startsWith(prefix.toLowerCase()))
|
||||
.slice(0, 4)
|
||||
.map((tag) => '#' + tag.name)),
|
||||
|
@ -412,7 +412,7 @@ export default function compose(state = initialState, action: AnyAction) {
|
|||
case COMPOSE_SUGGESTION_SELECT:
|
||||
return updateCompose(state, action.id, compose => insertSuggestion(compose, action.position, action.token, action.completion, action.path));
|
||||
case COMPOSE_SUGGESTION_TAGS_UPDATE:
|
||||
return updateCompose(state, action.id, compose => updateSuggestionTags(compose, action.token, action.currentTrends));
|
||||
return updateCompose(state, action.id, compose => updateSuggestionTags(compose, action.token, action.tags));
|
||||
case COMPOSE_TAG_HISTORY_UPDATE:
|
||||
return updateCompose(state, action.id, compose => compose.set('tagHistory', ImmutableList(fromJS(action.tags)) as ImmutableList<string>));
|
||||
case TIMELINE_DELETE:
|
||||
|
|
|
@ -62,7 +62,7 @@
|
|||
"@lexical/react": "^0.9.0",
|
||||
"@lexical/rich-text": "^0.9.0",
|
||||
"@lexical/selection": "^0.9.0",
|
||||
"@lexical/utils": "^0.9.0",
|
||||
"@lexical/utils": "^0.9.1",
|
||||
"@metamask/providers": "^10.0.0",
|
||||
"@popperjs/core": "^2.11.5",
|
||||
"@reach/combobox": "^0.18.0",
|
||||
|
|
30
yarn.lock
30
yarn.lock
|
@ -2440,6 +2440,13 @@
|
|||
dependencies:
|
||||
"@lexical/utils" "0.9.0"
|
||||
|
||||
"@lexical/list@0.9.1":
|
||||
version "0.9.1"
|
||||
resolved "https://registry.yarnpkg.com/@lexical/list/-/list-0.9.1.tgz#18eba1e28d818a53661b7381f32b4461f024981e"
|
||||
integrity sha512-z3wJfDjStesqjvamUgMez+CVvfGrmVqyGgMnCkYBrlfSZ8zlHvz8sJ/6iZkTh7TXIEkCTQpusCrkjUeYccmTxQ==
|
||||
dependencies:
|
||||
"@lexical/utils" "0.9.1"
|
||||
|
||||
"@lexical/mark@0.9.0":
|
||||
version "0.9.0"
|
||||
resolved "https://registry.yarnpkg.com/@lexical/mark/-/mark-0.9.0.tgz#040d3e8d3e2f46160bd4e5b1e6a5489df0c6aa46"
|
||||
|
@ -2508,6 +2515,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@lexical/selection/-/selection-0.9.0.tgz#2c11085f94435c1c71344654a1f2b9a0c3549a22"
|
||||
integrity sha512-jPLeaqNujER1t61OWmSuUMS010A6Nz49efO8rEJFjMbUK7GB9IDsSDgvE4WnT/yBrXLAickjSiXgUq51LQTYYw==
|
||||
|
||||
"@lexical/selection@0.9.1":
|
||||
version "0.9.1"
|
||||
resolved "https://registry.yarnpkg.com/@lexical/selection/-/selection-0.9.1.tgz#6d809d9b8c3673992ba6fc6ccd6af5f402f57ba0"
|
||||
integrity sha512-4eZcH8d4Kq/GswobVNIkM5fKk8YXPNFLCOHIyi0FmHymW/Ku7HLz2+mcOR6EQmk1Ce/4VJ4Oq+d1XlZw4PPTNA==
|
||||
|
||||
"@lexical/table@0.9.0":
|
||||
version "0.9.0"
|
||||
resolved "https://registry.yarnpkg.com/@lexical/table/-/table-0.9.0.tgz#8f4e0d797c15141e26667cc54d7b640c90f5a9b8"
|
||||
|
@ -2515,12 +2527,19 @@
|
|||
dependencies:
|
||||
"@lexical/utils" "0.9.0"
|
||||
|
||||
"@lexical/table@0.9.1":
|
||||
version "0.9.1"
|
||||
resolved "https://registry.yarnpkg.com/@lexical/table/-/table-0.9.1.tgz#06978da97425e46399d48b5d75d19b389c5b6a38"
|
||||
integrity sha512-BPKmpToQRxv87OtA6W22XwfOBtZf+vG95jp/bj2ecJAW5BVMjHf5ViI3vjXf3Z701giVh1OVXNct8EPkcfvg3w==
|
||||
dependencies:
|
||||
"@lexical/utils" "0.9.1"
|
||||
|
||||
"@lexical/text@0.9.0":
|
||||
version "0.9.0"
|
||||
resolved "https://registry.yarnpkg.com/@lexical/text/-/text-0.9.0.tgz#ee90b42d8558123c917f561cfbfb804dac25e0a4"
|
||||
integrity sha512-+OHoB1Qb2SajGTEm9K4I3hDtCmeLr0Bs62psra6qYE/lil0Y8brrEXB5egppDpm6mKtiMzGrkBDMpcsKw/kiWA==
|
||||
|
||||
"@lexical/utils@0.9.0", "@lexical/utils@^0.9.0":
|
||||
"@lexical/utils@0.9.0":
|
||||
version "0.9.0"
|
||||
resolved "https://registry.yarnpkg.com/@lexical/utils/-/utils-0.9.0.tgz#923e79af94566844442bc8699aba89f7d1cf5779"
|
||||
integrity sha512-s4BrBKrd7VHexLSYdSKrRAVmuxmfVV49MYx/khILGnNQKYS2O8PuERCXi+IRi8Ac5ASELO6d28Y2my8NMM1CYA==
|
||||
|
@ -2529,6 +2548,15 @@
|
|||
"@lexical/selection" "0.9.0"
|
||||
"@lexical/table" "0.9.0"
|
||||
|
||||
"@lexical/utils@0.9.1", "@lexical/utils@^0.9.1":
|
||||
version "0.9.1"
|
||||
resolved "https://registry.yarnpkg.com/@lexical/utils/-/utils-0.9.1.tgz#5fd58468da890e7ed5260c502f66fd19ec64e9b3"
|
||||
integrity sha512-cszNQufH6UZxOVew7DTELM63wjj8qOiGiOFklMmM6QmkirHb2fK4LxeLjaKiwP5DkA0A1NDNz8yuuMXqA/mWPQ==
|
||||
dependencies:
|
||||
"@lexical/list" "0.9.1"
|
||||
"@lexical/selection" "0.9.1"
|
||||
"@lexical/table" "0.9.1"
|
||||
|
||||
"@lexical/yjs@0.9.0":
|
||||
version "0.9.0"
|
||||
resolved "https://registry.yarnpkg.com/@lexical/yjs/-/yjs-0.9.0.tgz#88d90c346102bf46f5ee2ef6a6c763bef1f12bf6"
|
||||
|
|
Loading…
Reference in a new issue