mirror of
https://github.com/facebook/lexical.git
synced 2025-05-21 09:07:44 +08:00
Add get{Current/Latest}EditorContent() (#682)
This commit is contained in:
@ -43,7 +43,7 @@ export default function useTypeahead(editor: OutlineEditor): void {
|
|||||||
// Monitor entered text
|
// Monitor entered text
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return editor.addListener('update', (viewModel) => {
|
return editor.addListener('update', (viewModel) => {
|
||||||
const text = editor.getTextContent();
|
const text = editor.getCurrentTextContent();
|
||||||
setText(text);
|
setText(text);
|
||||||
});
|
});
|
||||||
}, [editor]);
|
}, [editor]);
|
||||||
|
@ -41,7 +41,7 @@ export function useCharacterLimit(
|
|||||||
const Segmenter = Intl.Segmenter;
|
const Segmenter = Intl.Segmenter;
|
||||||
let offsetUtf16 = 0;
|
let offsetUtf16 = 0;
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
const text = editor.getTextContent();
|
const text = editor.getCurrentTextContent();
|
||||||
if (typeof Segmenter === 'function') {
|
if (typeof Segmenter === 'function') {
|
||||||
const segmenter = new Segmenter();
|
const segmenter = new Segmenter();
|
||||||
const graphemes = segmenter.segment(text);
|
const graphemes = segmenter.segment(text);
|
||||||
@ -79,7 +79,7 @@ export function useCharacterLimit(
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
editor.registerNodeType('overflow', OverflowNode);
|
editor.registerNodeType('overflow', OverflowNode);
|
||||||
(() => {
|
(() => {
|
||||||
const textLength = strlen(editor.getTextContent());
|
const textLength = strlen(editor.getCurrentTextContent());
|
||||||
const diff = maxCharacters - textLength;
|
const diff = maxCharacters - textLength;
|
||||||
remainingCharacters(diff);
|
remainingCharacters(diff);
|
||||||
execute();
|
execute();
|
||||||
@ -90,7 +90,7 @@ export function useCharacterLimit(
|
|||||||
'update',
|
'update',
|
||||||
(viewModel: ViewModel, dirtyNodes: Set<NodeKey> | null) => {
|
(viewModel: ViewModel, dirtyNodes: Set<NodeKey> | null) => {
|
||||||
const isComposing = editor.isComposing();
|
const isComposing = editor.isComposing();
|
||||||
const text = editor.getTextContent();
|
const text = editor.getCurrentTextContent();
|
||||||
const utf16TextLength = text.length;
|
const utf16TextLength = text.length;
|
||||||
const hasDirtyNodes = dirtyNodes !== null && dirtyNodes.size > 0;
|
const hasDirtyNodes = dirtyNodes !== null && dirtyNodes.size > 0;
|
||||||
if (
|
if (
|
||||||
@ -99,7 +99,7 @@ export function useCharacterLimit(
|
|||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const textLength = strlen(editor.getTextContent());
|
const textLength = strlen(editor.getCurrentTextContent());
|
||||||
const textLengthAboveThreshold =
|
const textLengthAboveThreshold =
|
||||||
textLength > maxCharacters ||
|
textLength > maxCharacters ||
|
||||||
(lastTextLength !== null && lastTextLength > maxCharacters);
|
(lastTextLength !== null && lastTextLength > maxCharacters);
|
||||||
|
@ -507,6 +507,32 @@ describe('OutlineEditor tests', () => {
|
|||||||
expect(parsedSelection.focus.key).toEqual(parsedText.__key);
|
expect(parsedSelection.focus.key).toEqual(parsedText.__key);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('getCurrentTextContent() / getLatestTextContent()', async () => {
|
||||||
|
editor.update((view: View) => {
|
||||||
|
const root = view.getRoot();
|
||||||
|
const paragraph = createParagraphNode();
|
||||||
|
const text1 = createTextNode('1');
|
||||||
|
root.append(paragraph);
|
||||||
|
paragraph.append(text1);
|
||||||
|
});
|
||||||
|
editor.update((view: View) => {
|
||||||
|
const root = view.getRoot();
|
||||||
|
const paragraph = root.getFirstChild();
|
||||||
|
const text2 = createTextNode('2');
|
||||||
|
paragraph.append(text2);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(editor.getCurrentTextContent()).toBe('');
|
||||||
|
expect(
|
||||||
|
editor.getLatestTextContent((text) => {
|
||||||
|
expect(text).toBe('12');
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await Promise.resolve();
|
||||||
|
expect(editor.getCurrentTextContent()).toBe('12');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Node children', () => {
|
describe('Node children', () => {
|
||||||
@ -575,7 +601,7 @@ describe('OutlineEditor tests', () => {
|
|||||||
textToKey.set(previousText, textNode.__key);
|
textToKey.set(previousText, textNode.__key);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
expect(editor.getTextContent()).toBe(previous.join(''));
|
expect(editor.getCurrentTextContent()).toBe(previous.join(''));
|
||||||
|
|
||||||
// Next editor state
|
// Next editor state
|
||||||
const previousSet = new Set(previous);
|
const previousSet = new Set(previous);
|
||||||
@ -609,7 +635,7 @@ describe('OutlineEditor tests', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
// Expect text content + HTML to be correct
|
// Expect text content + HTML to be correct
|
||||||
expect(editor.getTextContent()).toBe(next.join(''));
|
expect(editor.getCurrentTextContent()).toBe(next.join(''));
|
||||||
expect(container.innerHTML).toBe(
|
expect(container.innerHTML).toBe(
|
||||||
`<div contenteditable="true" data-outline-editor="true"><p>${
|
`<div contenteditable="true" data-outline-editor="true"><p>${
|
||||||
next.length > 0
|
next.length > 0
|
||||||
|
@ -118,13 +118,13 @@ describe('OutlineTextNode tests', () => {
|
|||||||
view.getRoot().getFirstChild().append(textNode);
|
view.getRoot().getFirstChild().append(textNode);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(editor.getTextContent()).toBe('Text');
|
expect(editor.getCurrentTextContent()).toBe('Text');
|
||||||
|
|
||||||
// Make sure that the editor content is still set after further reconciliations
|
// Make sure that the editor content is still set after further reconciliations
|
||||||
await update((view) => {
|
await update((view) => {
|
||||||
view.markNodeAsDirty(view.getNodeByKey(nodeKey));
|
view.markNodeAsDirty(view.getNodeByKey(nodeKey));
|
||||||
});
|
});
|
||||||
expect(editor.getTextContent()).toBe('Text');
|
expect(editor.getCurrentTextContent()).toBe('Text');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('inert nodes', async () => {
|
test('inert nodes', async () => {
|
||||||
@ -140,13 +140,13 @@ describe('OutlineTextNode tests', () => {
|
|||||||
view.getRoot().getFirstChild().append(textNode);
|
view.getRoot().getFirstChild().append(textNode);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(editor.getTextContent()).toBe('');
|
expect(editor.getCurrentTextContent()).toBe('');
|
||||||
|
|
||||||
// Make sure that the editor content is still empty after further reconciliations
|
// Make sure that the editor content is still empty after further reconciliations
|
||||||
await update((view) => {
|
await update((view) => {
|
||||||
view.markNodeAsDirty(view.getNodeByKey(nodeKey));
|
view.markNodeAsDirty(view.getNodeByKey(nodeKey));
|
||||||
});
|
});
|
||||||
expect(editor.getTextContent()).toBe('');
|
expect(editor.getCurrentTextContent()).toBe('');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('prepend node', async () => {
|
test('prepend node', async () => {
|
||||||
@ -161,7 +161,7 @@ describe('OutlineTextNode tests', () => {
|
|||||||
previousTextNode.insertBefore(textNode);
|
previousTextNode.insertBefore(textNode);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(editor.getTextContent()).toBe('Hello World');
|
expect(editor.getCurrentTextContent()).toBe('Hello World');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -107,6 +107,22 @@ export type ListenerType =
|
|||||||
| 'root'
|
| 'root'
|
||||||
| 'decorator';
|
| 'decorator';
|
||||||
|
|
||||||
|
let isPreparingPendingViewUpdate = false;
|
||||||
|
|
||||||
|
export function asyncErrorOnPreparingPendingViewUpdate(
|
||||||
|
fnName: 'Editor.getLatestTextContent()',
|
||||||
|
): void {
|
||||||
|
if (
|
||||||
|
isPreparingPendingViewUpdate &&
|
||||||
|
fnName === 'Editor.getLatestTextContent()'
|
||||||
|
) {
|
||||||
|
invariant(
|
||||||
|
false,
|
||||||
|
'Editor.getLatestTextContent() can be asynchronous and cannot be used within Editor.update()',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function resetEditor(
|
export function resetEditor(
|
||||||
editor: OutlineEditor,
|
editor: OutlineEditor,
|
||||||
prevRootElement: null | HTMLElement,
|
prevRootElement: null | HTMLElement,
|
||||||
@ -167,6 +183,7 @@ function updateEditor(
|
|||||||
viewModelWasCloned = true;
|
viewModelWasCloned = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isPreparingPendingViewUpdate = true;
|
||||||
const error = preparePendingViewUpdate(
|
const error = preparePendingViewUpdate(
|
||||||
pendingViewModel,
|
pendingViewModel,
|
||||||
updateFn,
|
updateFn,
|
||||||
@ -174,6 +191,7 @@ function updateEditor(
|
|||||||
markAllTextNodesAsDirty,
|
markAllTextNodesAsDirty,
|
||||||
editor,
|
editor,
|
||||||
);
|
);
|
||||||
|
isPreparingPendingViewUpdate = false;
|
||||||
|
|
||||||
if (error !== null) {
|
if (error !== null) {
|
||||||
// Report errors
|
// Report errors
|
||||||
@ -326,9 +344,17 @@ class BaseOutlineEditor {
|
|||||||
getRootElement(): null | HTMLElement {
|
getRootElement(): null | HTMLElement {
|
||||||
return this._rootElement;
|
return this._rootElement;
|
||||||
}
|
}
|
||||||
getTextContent(): string {
|
getCurrentTextContent(): string {
|
||||||
return this._textContent;
|
return this._textContent;
|
||||||
}
|
}
|
||||||
|
getLatestTextContent(callback: (text: string) => void): void {
|
||||||
|
asyncErrorOnPreparingPendingViewUpdate('Editor.getLatestTextContent()');
|
||||||
|
if (this._pendingViewModel === null) {
|
||||||
|
callback(this._textContent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._deferred.push(() => callback(this._textContent));
|
||||||
|
}
|
||||||
setRootElement(nextRootElement: null | HTMLElement): void {
|
setRootElement(nextRootElement: null | HTMLElement): void {
|
||||||
const prevRootElement = this._rootElement;
|
const prevRootElement = this._rootElement;
|
||||||
if (nextRootElement !== prevRootElement) {
|
if (nextRootElement !== prevRootElement) {
|
||||||
@ -478,7 +504,8 @@ declare export class OutlineEditor {
|
|||||||
getDecorators(): {[NodeKey]: ReactNode};
|
getDecorators(): {[NodeKey]: ReactNode};
|
||||||
getRootElement(): null | HTMLElement;
|
getRootElement(): null | HTMLElement;
|
||||||
setRootElement(rootElement: null | HTMLElement): void;
|
setRootElement(rootElement: null | HTMLElement): void;
|
||||||
getTextContent(): string;
|
getCurrentTextContent(): string;
|
||||||
|
getLatestTextContent((text: string) => void): () => void;
|
||||||
getElementByKey(key: NodeKey): null | HTMLElement;
|
getElementByKey(key: NodeKey): null | HTMLElement;
|
||||||
getViewModel(): ViewModel;
|
getViewModel(): ViewModel;
|
||||||
setViewModel(viewModel: ViewModel): void;
|
setViewModel(viewModel: ViewModel): void;
|
||||||
|
@ -46,5 +46,6 @@
|
|||||||
"44": "reconcileNode: prevNode or nextNode does not exist in nodeMap",
|
"44": "reconcileNode: prevNode or nextNode does not exist in nodeMap",
|
||||||
"45": "reconcileNode: parentDOM is null",
|
"45": "reconcileNode: parentDOM is null",
|
||||||
"46": "Reconciliation: could not find DOM element for node key \"${key}\"",
|
"46": "Reconciliation: could not find DOM element for node key \"${key}\"",
|
||||||
"47": "clearEditor expected plain text root first child to be a ParagraphNode"
|
"47": "clearEditor expected plain text root first child to be a ParagraphNode",
|
||||||
|
"48": "Editor.getLatestTextContent() can be asynchronous and cannot be used within Editor.update()"
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user