mirror of
https://github.com/facebook/lexical.git
synced 2025-08-06 16:39:33 +08:00
@ -34,6 +34,7 @@ module.name_mapper='^lexical/text' -> '<PROJECT_ROOT>/packages/lexical/src/helpe
|
|||||||
module.name_mapper='^lexical/nodes' -> '<PROJECT_ROOT>/packages/lexical/src/helpers/LexicalNodeHelpers.js'
|
module.name_mapper='^lexical/nodes' -> '<PROJECT_ROOT>/packages/lexical/src/helpers/LexicalNodeHelpers.js'
|
||||||
module.name_mapper='^lexical/elements' -> '<PROJECT_ROOT>/packages/lexical/src/helpers/LexicalElementHelpers.js'
|
module.name_mapper='^lexical/elements' -> '<PROJECT_ROOT>/packages/lexical/src/helpers/LexicalElementHelpers.js'
|
||||||
module.name_mapper='^lexical/events' -> '<PROJECT_ROOT>/packages/lexical/src/helpers/LexicalEventHelpers.js'
|
module.name_mapper='^lexical/events' -> '<PROJECT_ROOT>/packages/lexical/src/helpers/LexicalEventHelpers.js'
|
||||||
|
module.name_mapper='^lexical/file' -> '<PROJECT_ROOT>/packages/lexical/src/helpers/LexicalFileHelpers.js'
|
||||||
module.name_mapper='^lexical/offsets' -> '<PROJECT_ROOT>/packages/lexical/src/helpers/LexicalOffsetHelpers.js'
|
module.name_mapper='^lexical/offsets' -> '<PROJECT_ROOT>/packages/lexical/src/helpers/LexicalOffsetHelpers.js'
|
||||||
module.name_mapper='^lexical/root' -> '<PROJECT_ROOT>/packages/lexical/src/helpers/LexicalRootHelpers.js'
|
module.name_mapper='^lexical/root' -> '<PROJECT_ROOT>/packages/lexical/src/helpers/LexicalRootHelpers.js'
|
||||||
|
|
||||||
|
@ -57,6 +57,8 @@ module.exports = {
|
|||||||
'<rootDir>/packages/lexical/src/helpers/LexicalElementHelpers.js',
|
'<rootDir>/packages/lexical/src/helpers/LexicalElementHelpers.js',
|
||||||
'^lexical/events$':
|
'^lexical/events$':
|
||||||
'<rootDir>/packages/lexical/src/helpers/LexicalEventHelpers.js',
|
'<rootDir>/packages/lexical/src/helpers/LexicalEventHelpers.js',
|
||||||
|
'^lexical/file$':
|
||||||
|
'<rootDir>/packages/lexical/src/helpers/LexicalFileHelpers.js',
|
||||||
'^lexical/offsets$':
|
'^lexical/offsets$':
|
||||||
'<rootDir>/packages/lexical/src/helpers/LexicalOffsetHelpers.js',
|
'<rootDir>/packages/lexical/src/helpers/LexicalOffsetHelpers.js',
|
||||||
'^lexical/root$':
|
'^lexical/root$':
|
||||||
|
@ -22,6 +22,7 @@ module.exports = {
|
|||||||
'lexical/nodes': 'lexical/dist/LexicalNodeHelpers',
|
'lexical/nodes': 'lexical/dist/LexicalNodeHelpers',
|
||||||
'lexical/elements': 'lexical/dist/LexicalElementHelpers',
|
'lexical/elements': 'lexical/dist/LexicalElementHelpers',
|
||||||
'lexical/events': 'lexical/dist/LexicalEventHelpers',
|
'lexical/events': 'lexical/dist/LexicalEventHelpers',
|
||||||
|
'lexical/file': 'lexical/dist/LexicalFileHelpers',
|
||||||
'lexical/offsets': 'lexical/dist/LexicalOffsetHelpers',
|
'lexical/offsets': 'lexical/dist/LexicalOffsetHelpers',
|
||||||
'lexical/root': 'lexical/dist/LexicalRootHelpers',
|
'lexical/root': 'lexical/dist/LexicalRootHelpers',
|
||||||
|
|
||||||
|
4
packages/lexical-playground/src/images/icons/upload.svg
Normal file
4
packages/lexical-playground/src/images/icons/upload.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-upload" viewBox="0 0 16 16">
|
||||||
|
<path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/>
|
||||||
|
<path d="M7.646 1.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708L8.5 2.707V11.5a.5.5 0 0 1-1 0V2.707L5.354 4.854a.5.5 0 1 1-.708-.708l3-3z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 424 B |
@ -397,6 +397,14 @@ i.sticky {
|
|||||||
background-image: url(images/icons/sticky.svg);
|
background-image: url(images/icons/sticky.svg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i.import {
|
||||||
|
background-image: url(images/icons/upload.svg);
|
||||||
|
}
|
||||||
|
|
||||||
|
i.export {
|
||||||
|
background-image: url(images/icons/download.svg);
|
||||||
|
}
|
||||||
|
|
||||||
.link-editor .button.active, .toolbar .button.active {
|
.link-editor .button.active, .toolbar .button.active {
|
||||||
background-color: rgb(223, 232, 250);
|
background-color: rgb(223, 232, 250);
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ import {useCallback, useEffect, useState} from 'react';
|
|||||||
import {$createStickyNode} from '../nodes/StickyNode';
|
import {$createStickyNode} from '../nodes/StickyNode';
|
||||||
import {$log, $getRoot, createEditorStateRef} from 'lexical';
|
import {$log, $getRoot, createEditorStateRef} from 'lexical';
|
||||||
import useLexicalList from 'lexical-react/useLexicalList';
|
import useLexicalList from 'lexical-react/useLexicalList';
|
||||||
|
import {importFile, exportFile} from 'lexical/file';
|
||||||
|
|
||||||
const EditorPriority: CommandListenerEditorPriority = 0;
|
const EditorPriority: CommandListenerEditorPriority = 0;
|
||||||
|
|
||||||
@ -67,6 +68,21 @@ export default function ActionsPlugins({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="actions">
|
<div className="actions">
|
||||||
|
<button
|
||||||
|
className="action-button import"
|
||||||
|
onClick={() => importFile(editor)}>
|
||||||
|
<i className="import" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="action-button export"
|
||||||
|
onClick={() =>
|
||||||
|
exportFile(editor, {
|
||||||
|
fileName: `Playground ${new Date().toISOString()}`,
|
||||||
|
source: 'Playground',
|
||||||
|
})
|
||||||
|
}>
|
||||||
|
<i className="export" />
|
||||||
|
</button>
|
||||||
<button className="action-button sticky" onClick={insertSticky}>
|
<button className="action-button sticky" onClick={insertSticky}>
|
||||||
<i className="sticky" />
|
<i className="sticky" />
|
||||||
</button>
|
</button>
|
||||||
|
@ -257,6 +257,12 @@ export function useLexicalHistory(
|
|||||||
[externalHistoryState],
|
[externalHistoryState],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const clearHistory = useCallback(() => {
|
||||||
|
historyState.undoStack = [];
|
||||||
|
historyState.redoStack = [];
|
||||||
|
historyState.current = null;
|
||||||
|
}, [historyState]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getMergeAction = createMergeActionGetter(editor, delay);
|
const getMergeAction = createMergeActionGetter(editor, delay);
|
||||||
const applyChange = ({
|
const applyChange = ({
|
||||||
@ -356,28 +362,26 @@ export function useLexicalHistory(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const applyCommand = (type) => {
|
const applyCommand = (type) => {
|
||||||
if (type === 'undo') {
|
switch (type) {
|
||||||
undo();
|
case 'undo':
|
||||||
return true;
|
undo();
|
||||||
|
return true;
|
||||||
|
case 'redo':
|
||||||
|
redo();
|
||||||
|
return true;
|
||||||
|
case 'clear-history':
|
||||||
|
clearHistory();
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
if (type === 'redo') {
|
|
||||||
redo();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return withSubscriptions(
|
return withSubscriptions(
|
||||||
editor.addListener('command', applyCommand, EditorPriority),
|
editor.addListener('command', applyCommand, EditorPriority),
|
||||||
editor.addListener('update', applyChange),
|
editor.addListener('update', applyChange),
|
||||||
);
|
);
|
||||||
}, [delay, editor, historyState]);
|
}, [clearHistory, delay, editor, historyState]);
|
||||||
|
|
||||||
const clearHistory = useCallback(() => {
|
|
||||||
historyState.undoStack = [];
|
|
||||||
historyState.redoStack = [];
|
|
||||||
historyState.current = null;
|
|
||||||
}, [historyState]);
|
|
||||||
|
|
||||||
return clearHistory;
|
return clearHistory;
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,8 @@
|
|||||||
import type {TextFormatType, TextModeType} from './LexicalTextNode';
|
import type {TextFormatType, TextModeType} from './LexicalTextNode';
|
||||||
import type {ElementFormatType} from './LexicalElementNode';
|
import type {ElementFormatType} from './LexicalElementNode';
|
||||||
|
|
||||||
|
export const VERSION = '0.1.1';
|
||||||
|
|
||||||
// Reconciling
|
// Reconciling
|
||||||
export const NO_DIRTY_NODES = 0;
|
export const NO_DIRTY_NODES = 0;
|
||||||
export const HAS_DIRTY_NODES = 1;
|
export const HAS_DIRTY_NODES = 1;
|
||||||
|
@ -34,6 +34,7 @@ export type {LineBreakNode} from './LexicalLineBreakNode';
|
|||||||
export type {RootNode} from './LexicalRootNode';
|
export type {RootNode} from './LexicalRootNode';
|
||||||
export type {ElementFormatType} from './LexicalElementNode';
|
export type {ElementFormatType} from './LexicalElementNode';
|
||||||
|
|
||||||
|
import {VERSION} from './LexicalConstants';
|
||||||
import {createEditor} from './LexicalEditor';
|
import {createEditor} from './LexicalEditor';
|
||||||
import {$createTextNode, $isTextNode, TextNode} from './LexicalTextNode';
|
import {$createTextNode, $isTextNode, TextNode} from './LexicalTextNode';
|
||||||
import {$isElementNode, ElementNode} from './LexicalElementNode';
|
import {$isElementNode, ElementNode} from './LexicalElementNode';
|
||||||
@ -60,6 +61,7 @@ import {$createNodeFromParse} from './LexicalParsing';
|
|||||||
import {createEditorStateRef, isEditorStateRef} from './LexicalReference';
|
import {createEditorStateRef, isEditorStateRef} from './LexicalReference';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
VERSION,
|
||||||
createEditor,
|
createEditor,
|
||||||
ElementNode,
|
ElementNode,
|
||||||
DecoratorNode,
|
DecoratorNode,
|
||||||
|
76
packages/lexical/src/helpers/LexicalFileHelpers.js
Normal file
76
packages/lexical/src/helpers/LexicalFileHelpers.js
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its 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 {LexicalEditor} from 'lexical';
|
||||||
|
|
||||||
|
import {VERSION} from 'lexical';
|
||||||
|
|
||||||
|
export function importFile(editor: LexicalEditor) {
|
||||||
|
readTextFileFromSystem((text) => {
|
||||||
|
const json = JSON.parse(text);
|
||||||
|
const editorState = editor.parseEditorState(
|
||||||
|
JSON.stringify(json.editorState),
|
||||||
|
);
|
||||||
|
editor.setEditorState(editorState);
|
||||||
|
editor.execCommand('clear-history');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function readTextFileFromSystem(callback: (text: string) => void) {
|
||||||
|
const input = document.createElement('input');
|
||||||
|
input.type = 'file';
|
||||||
|
input.accept = '.lexical';
|
||||||
|
input.addEventListener('change', (e: Event) => {
|
||||||
|
// $FlowFixMe
|
||||||
|
const file = e.target.files[0];
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.readAsText(file, 'UTF-8');
|
||||||
|
reader.onload = (readerEvent) => {
|
||||||
|
// $FlowFixMe
|
||||||
|
const content = readerEvent.target.result;
|
||||||
|
callback(content);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
input.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function exportFile(
|
||||||
|
editor: LexicalEditor,
|
||||||
|
config?: $ReadOnly<{source?: string, fileName?: string}> = {},
|
||||||
|
) {
|
||||||
|
const now = new Date();
|
||||||
|
const editorState = editor.getEditorState();
|
||||||
|
const documentJSON = {
|
||||||
|
source: config.source || 'Lexical',
|
||||||
|
version: VERSION,
|
||||||
|
lastSaved: now.getTime(),
|
||||||
|
editorState: editorState,
|
||||||
|
};
|
||||||
|
const fileName = config.fileName || now.toISOString();
|
||||||
|
exportBlob(documentJSON, `${fileName}.lexical`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adapted from https://stackoverflow.com/a/19328891/2013580
|
||||||
|
function exportBlob(data, fileName: string) {
|
||||||
|
const a = document.createElement('a');
|
||||||
|
const body = document.body;
|
||||||
|
if (body === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
body.appendChild(a);
|
||||||
|
a.style.display = 'none';
|
||||||
|
const json = JSON.stringify(data);
|
||||||
|
const blob = new Blob([json], {type: 'octet/stream'});
|
||||||
|
const url = window.URL.createObjectURL(blob);
|
||||||
|
a.href = url;
|
||||||
|
a.download = fileName;
|
||||||
|
a.click();
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
a.remove();
|
||||||
|
}
|
@ -167,6 +167,12 @@ async function build(name, inputFile, outputFile) {
|
|||||||
'packages/lexical/src/helpers/LexicalEventHelpers',
|
'packages/lexical/src/helpers/LexicalEventHelpers',
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
find: isWWW ? 'Lexical/file' : 'lexical/file',
|
||||||
|
replacement: path.resolve(
|
||||||
|
'packages/lexical/src/helpers/LexicalFileHelpers',
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
find: isWWW ? 'Lexical/offsets' : 'lexical/offsets',
|
find: isWWW ? 'Lexical/offsets' : 'lexical/offsets',
|
||||||
replacement: path.resolve(
|
replacement: path.resolve(
|
||||||
|
@ -20,6 +20,9 @@ async function prepareLexicalPackage() {
|
|||||||
await exec(
|
await exec(
|
||||||
`mv ./packages/lexical/npm/LexicalEventHelpers.js ./packages/lexical/npm/events.js`,
|
`mv ./packages/lexical/npm/LexicalEventHelpers.js ./packages/lexical/npm/events.js`,
|
||||||
);
|
);
|
||||||
|
await exec(
|
||||||
|
`mv ./packages/lexical/npm/LexicalFileHelpers.js ./packages/lexical/npm/file.js`,
|
||||||
|
);
|
||||||
await exec(
|
await exec(
|
||||||
`mv ./packages/lexical/npm/LexicalOffsetHelpers.js ./packages/lexical/npm/offsets.js`,
|
`mv ./packages/lexical/npm/LexicalOffsetHelpers.js ./packages/lexical/npm/offsets.js`,
|
||||||
);
|
);
|
||||||
|
Reference in New Issue
Block a user