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/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/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/root' -> '<PROJECT_ROOT>/packages/lexical/src/helpers/LexicalRootHelpers.js'
|
||||
|
||||
|
@ -57,6 +57,8 @@ module.exports = {
|
||||
'<rootDir>/packages/lexical/src/helpers/LexicalElementHelpers.js',
|
||||
'^lexical/events$':
|
||||
'<rootDir>/packages/lexical/src/helpers/LexicalEventHelpers.js',
|
||||
'^lexical/file$':
|
||||
'<rootDir>/packages/lexical/src/helpers/LexicalFileHelpers.js',
|
||||
'^lexical/offsets$':
|
||||
'<rootDir>/packages/lexical/src/helpers/LexicalOffsetHelpers.js',
|
||||
'^lexical/root$':
|
||||
|
@ -22,6 +22,7 @@ module.exports = {
|
||||
'lexical/nodes': 'lexical/dist/LexicalNodeHelpers',
|
||||
'lexical/elements': 'lexical/dist/LexicalElementHelpers',
|
||||
'lexical/events': 'lexical/dist/LexicalEventHelpers',
|
||||
'lexical/file': 'lexical/dist/LexicalFileHelpers',
|
||||
'lexical/offsets': 'lexical/dist/LexicalOffsetHelpers',
|
||||
'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);
|
||||
}
|
||||
|
||||
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 {
|
||||
background-color: rgb(223, 232, 250);
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import {useCallback, useEffect, useState} from 'react';
|
||||
import {$createStickyNode} from '../nodes/StickyNode';
|
||||
import {$log, $getRoot, createEditorStateRef} from 'lexical';
|
||||
import useLexicalList from 'lexical-react/useLexicalList';
|
||||
import {importFile, exportFile} from 'lexical/file';
|
||||
|
||||
const EditorPriority: CommandListenerEditorPriority = 0;
|
||||
|
||||
@ -67,6 +68,21 @@ export default function ActionsPlugins({
|
||||
|
||||
return (
|
||||
<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}>
|
||||
<i className="sticky" />
|
||||
</button>
|
||||
|
@ -257,6 +257,12 @@ export function useLexicalHistory(
|
||||
[externalHistoryState],
|
||||
);
|
||||
|
||||
const clearHistory = useCallback(() => {
|
||||
historyState.undoStack = [];
|
||||
historyState.redoStack = [];
|
||||
historyState.current = null;
|
||||
}, [historyState]);
|
||||
|
||||
useEffect(() => {
|
||||
const getMergeAction = createMergeActionGetter(editor, delay);
|
||||
const applyChange = ({
|
||||
@ -356,28 +362,26 @@ export function useLexicalHistory(
|
||||
};
|
||||
|
||||
const applyCommand = (type) => {
|
||||
if (type === 'undo') {
|
||||
undo();
|
||||
return true;
|
||||
switch (type) {
|
||||
case 'undo':
|
||||
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(
|
||||
editor.addListener('command', applyCommand, EditorPriority),
|
||||
editor.addListener('update', applyChange),
|
||||
);
|
||||
}, [delay, editor, historyState]);
|
||||
|
||||
const clearHistory = useCallback(() => {
|
||||
historyState.undoStack = [];
|
||||
historyState.redoStack = [];
|
||||
historyState.current = null;
|
||||
}, [historyState]);
|
||||
}, [clearHistory, delay, editor, historyState]);
|
||||
|
||||
return clearHistory;
|
||||
}
|
||||
|
@ -10,6 +10,8 @@
|
||||
import type {TextFormatType, TextModeType} from './LexicalTextNode';
|
||||
import type {ElementFormatType} from './LexicalElementNode';
|
||||
|
||||
export const VERSION = '0.1.1';
|
||||
|
||||
// Reconciling
|
||||
export const NO_DIRTY_NODES = 0;
|
||||
export const HAS_DIRTY_NODES = 1;
|
||||
|
@ -34,6 +34,7 @@ export type {LineBreakNode} from './LexicalLineBreakNode';
|
||||
export type {RootNode} from './LexicalRootNode';
|
||||
export type {ElementFormatType} from './LexicalElementNode';
|
||||
|
||||
import {VERSION} from './LexicalConstants';
|
||||
import {createEditor} from './LexicalEditor';
|
||||
import {$createTextNode, $isTextNode, TextNode} from './LexicalTextNode';
|
||||
import {$isElementNode, ElementNode} from './LexicalElementNode';
|
||||
@ -60,6 +61,7 @@ import {$createNodeFromParse} from './LexicalParsing';
|
||||
import {createEditorStateRef, isEditorStateRef} from './LexicalReference';
|
||||
|
||||
export {
|
||||
VERSION,
|
||||
createEditor,
|
||||
ElementNode,
|
||||
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',
|
||||
),
|
||||
},
|
||||
{
|
||||
find: isWWW ? 'Lexical/file' : 'lexical/file',
|
||||
replacement: path.resolve(
|
||||
'packages/lexical/src/helpers/LexicalFileHelpers',
|
||||
),
|
||||
},
|
||||
{
|
||||
find: isWWW ? 'Lexical/offsets' : 'lexical/offsets',
|
||||
replacement: path.resolve(
|
||||
|
@ -20,6 +20,9 @@ async function prepareLexicalPackage() {
|
||||
await exec(
|
||||
`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(
|
||||
`mv ./packages/lexical/npm/LexicalOffsetHelpers.js ./packages/lexical/npm/offsets.js`,
|
||||
);
|
||||
|
Reference in New Issue
Block a user