feat:添加分割线

This commit is contained in:
shixiaohua 2024-02-27 09:47:34 +08:00
parent 4a995d69a5
commit 36f2cb43e0
8 changed files with 398 additions and 252 deletions

View File

@ -1,13 +1,13 @@
import {useCallback, useMemo, useState} from 'react'; import {useCallback, useMemo, useState} from 'react';
import * as React from 'react'; import * as React from 'react';
import {Modal} from "antd";
import Modal from '../plugins/Input/Modal';
export default function useModal(){ export default function useModal(){
const [modalContent, setModalContent] = useState(null); const [modalContent, setModalContent] = useState(null);
const [isOpen,setIsOpen]=useState(true)
const onClose = useCallback(() => { const onClose = useCallback(() => {
setModalContent(null); setModalContent(null);
setIsOpen(false)
}, []); }, []);
const modal = useMemo(() => { const modal = useMemo(() => {
@ -15,32 +15,30 @@ export default function useModal(){
return null; return null;
} }
const {title, content, closeOnClickOutside} = modalContent; const {title, content, closeOnClickOutside} = modalContent;
return (<div> console.log("modalContent !== null",modalContent)
<Modal return (
open={isOpen} <Modal
//onOk={handleOk} onClose={onClose}
onCancel={onClose} title={title}
title={title} closeOnClickOutside={closeOnClickOutside}>
// closeOnClickOutside={closeOnClickOutside} {content}
> </Modal>
{content}
</Modal></div>
); );
}, [modalContent, onClose,isOpen]); }, [modalContent, onClose]);
const showModal = useCallback(( const showModal = useCallback(
title, (
getContent, title,
closeOnClickOutside = false, getContent,
) => { closeOnClickOutside = false,
setModalContent({ ) => {
closeOnClickOutside, setModalContent({
content: getContent(onClose), closeOnClickOutside,
title, content: getContent(onClose),
}); title,
}, });
[onClose], },
[onClose],
); );
return [modal, showModal]; return [modal, showModal];

View File

@ -20,11 +20,13 @@ import AutoLinkPlugin from "./plugins/AutoLinkPlugin";
import ListMaxIndentLevelPlugin from "./plugins/ListMaxIndentLevelPlugin"; import ListMaxIndentLevelPlugin from "./plugins/ListMaxIndentLevelPlugin";
import CodeHighlightPlugin from "./plugins/CodeHighlightPlugin"; import CodeHighlightPlugin from "./plugins/CodeHighlightPlugin";
import ImportFilePlugin from "./plugins/ImportFilePlugin"; import ImportFilePlugin from "./plugins/ImportFilePlugin";
import {TablePlugin} from "@lexical/react/LexicalTablePlugin";
import SaveFilePlugin from "./plugins/SaveFilePlugin"; import SaveFilePlugin from "./plugins/SaveFilePlugin";
import {TabIndentationPlugin} from "@lexical/react/LexicalTabIndentationPlugin"; import {TabIndentationPlugin} from "@lexical/react/LexicalTabIndentationPlugin";
import UsefulNodes from "./nodes/UsefulNodes"; import UsefulNodes from "./nodes/UsefulNodes";
import ImagesPlugin from "./plugins/ImagesPlugin"; import ImagesPlugin from "./plugins/ImagesPlugin";
import {TablePlugin} from "./plugins/TablePlugin";
import {HorizontalRulePlugin} from "@lexical/react/LexicalHorizontalRulePlugin"
function Placeholder() { function Placeholder() {
return <div className="editor-placeholder">Enter some rich text...</div>; return <div className="editor-placeholder">Enter some rich text...</div>;
} }
@ -74,9 +76,12 @@ export default function Hlexical(props) {
<MarkdownShortcutPlugin transformers={TRANSFORMERS}/> <MarkdownShortcutPlugin transformers={TRANSFORMERS}/>
{/*图片加载*/} {/*图片加载*/}
<ImagesPlugin/> <ImagesPlugin/>
{/*分割线 */}
<HorizontalRulePlugin />
{/*页分割线*/}
{/*目录加载*/} {/*目录加载*/}
{/* 表格加载 */} {/* 表格加载 */}
{/*<TableOfContentsPlugin />*/} {/*<TableOfContentsPlugin />*/}
{/*<LexicalTableOfContents>*/} {/*<LexicalTableOfContents>*/}
{/* {(tableOfContentsArray) => {*/} {/* {(tableOfContentsArray) => {*/}

View File

@ -71,7 +71,7 @@ export function extractRowsFromHTML(tableElem) {
return rows; return rows;
} }
function convertTableElement(domNode){ function convertTableElement(domNode) {
const rowElems = domNode.querySelectorAll('tr'); const rowElems = domNode.querySelectorAll('tr');
if (!rowElems || rowElems.length === 0) { if (!rowElems || rowElems.length === 0) {
return null; return null;
@ -184,177 +184,177 @@ export class TableNode extends DecoratorNode {
} }
constructor(rows, key) { constructor(rows, key) {
super(key); super(key);
this.__rows = rows || []; this.__rows = rows || [];
} }
createDOM() { createDOM() {
return document.createElement('div'); return document.createElement('div');
} }
updateDOM() { updateDOM() {
return false; return false;
} }
mergeRows(startX, startY, mergeRows) { mergeRows(startX, startY, mergeRows) {
const self = this.getWritable(); const self = this.getWritable();
const rows = self.__rows; const rows = self.__rows;
const endY = Math.min(rows.length, startY + mergeRows.length); const endY = Math.min(rows.length, startY + mergeRows.length);
for (let y = startY; y < endY; y++) { for (let y = startY; y < endY; y++) {
const row = rows[y]; const row = rows[y];
const mergeRow = mergeRows[y - startY]; const mergeRow = mergeRows[y - startY];
const cells = row.cells; const cells = row.cells;
const cellsClone = Array.from(cells); const cellsClone = Array.from(cells);
const rowClone = {...row, cells: cellsClone}; const rowClone = {...row, cells: cellsClone};
const mergeCells = mergeRow.cells; const mergeCells = mergeRow.cells;
const endX = Math.min(cells.length, startX + mergeCells.length); const endX = Math.min(cells.length, startX + mergeCells.length);
for (let x = startX; x < endX; x++) { for (let x = startX; x < endX; x++) {
const cell = cells[x];
const mergeCell = mergeCells[x - startX];
const cellClone = {...cell, json: mergeCell.json, type: mergeCell.type};
cellsClone[x] = cellClone;
}
rows[y] = rowClone;
}
}
updateCellJSON(x, y, json) {
const self = this.getWritable();
const rows = self.__rows;
const row = rows[y];
const cells = row.cells;
const cell = cells[x]; const cell = cells[x];
const mergeCell = mergeCells[x - startX]; const cellsClone = Array.from(cells);
const cellClone = {...cell, json: mergeCell.json, type: mergeCell.type}; const cellClone = {...cell, json};
const rowClone = {...row, cells: cellsClone};
cellsClone[x] = cellClone; cellsClone[x] = cellClone;
rows[y] = rowClone;
} }
rows[y] = rowClone;
}
}
updateCellJSON(x, y, json) { updateCellType(x, y, type) {
const self = this.getWritable(); const self = this.getWritable();
const rows = self.__rows; const rows = self.__rows;
const row = rows[y]; const row = rows[y];
const cells = row.cells; const cells = row.cells;
const cell = cells[x]; const cell = cells[x];
const cellsClone = Array.from(cells); const cellsClone = Array.from(cells);
const cellClone = {...cell, json}; const cellClone = {...cell, type};
const rowClone = {...row, cells: cellsClone}; const rowClone = {...row, cells: cellsClone};
cellsClone[x] = cellClone; cellsClone[x] = cellClone;
rows[y] = rowClone; rows[y] = rowClone;
}
updateCellType(x, y, type) {
const self = this.getWritable();
const rows = self.__rows;
const row = rows[y];
const cells = row.cells;
const cell = cells[x];
const cellsClone = Array.from(cells);
const cellClone = {...cell, type};
const rowClone = {...row, cells: cellsClone};
cellsClone[x] = cellClone;
rows[y] = rowClone;
}
insertColumnAt(x) {
const self = this.getWritable();
const rows = self.__rows;
for (let y = 0; y < rows.length; y++) {
const row = rows[y];
const cells = row.cells;
const cellsClone = Array.from(cells);
const rowClone = {...row, cells: cellsClone};
const type = (cells[x] || cells[x - 1]).type;
cellsClone.splice(x, 0, createCell(type));
rows[y] = rowClone;
}
}
deleteColumnAt(x) {
const self = this.getWritable();
const rows = self.__rows;
for (let y = 0; y < rows.length; y++) {
const row = rows[y];
const cells = row.cells;
const cellsClone = Array.from(cells);
const rowClone = {...row, cells: cellsClone};
cellsClone.splice(x, 1);
rows[y] = rowClone;
}
}
addColumns(count) {
const self = this.getWritable();
const rows = self.__rows;
for (let y = 0; y < rows.length; y++) {
const row = rows[y];
const cells = row.cells;
const cellsClone = Array.from(cells);
const rowClone = {...row, cells: cellsClone};
const type = cells[cells.length - 1].type;
for (let x = 0; x < count; x++) {
cellsClone.push(createCell(type));
} }
rows[y] = rowClone;
}
}
insertRowAt(y) { insertColumnAt(x) {
const self = this.getWritable(); const self = this.getWritable();
const rows = self.__rows; const rows = self.__rows;
const prevRow = rows[y] || rows[y - 1]; for (let y = 0; y < rows.length; y++) {
const cellCount = prevRow.cells.length; const row = rows[y];
const row = createRow(); const cells = row.cells;
for (let x = 0; x < cellCount; x++) { const cellsClone = Array.from(cells);
const cell = createCell(prevRow.cells[x].type); const rowClone = {...row, cells: cellsClone};
row.cells.push(cell); const type = (cells[x] || cells[x - 1]).type;
} cellsClone.splice(x, 0, createCell(type));
rows.splice(y, 0, row); rows[y] = rowClone;
} }
deleteRowAt(y) {
const self = this.getWritable();
const rows = self.__rows;
rows.splice(y, 1);
}
addRows(count) {
const self = this.getWritable();
const rows = self.__rows;
const prevRow = rows[rows.length - 1];
const cellCount = prevRow.cells.length;
for (let y = 0; y < count; y++) {
const row = createRow();
for (let x = 0; x < cellCount; x++) {
const cell = createCell(prevRow.cells[x].type);
row.cells.push(cell);
} }
rows.push(row);
}
}
updateColumnWidth(x, width) { deleteColumnAt(x) {
const self = this.getWritable(); const self = this.getWritable();
const rows = self.__rows; const rows = self.__rows;
for (let y = 0; y < rows.length; y++) { for (let y = 0; y < rows.length; y++) {
const row = rows[y]; const row = rows[y];
const cells = row.cells; const cells = row.cells;
const cellsClone = Array.from(cells); const cellsClone = Array.from(cells);
const rowClone = {...row, cells: cellsClone}; const rowClone = {...row, cells: cellsClone};
cellsClone[x].width = width; cellsClone.splice(x, 1);
rows[y] = rowClone; rows[y] = rowClone;
} }
} }
decorate(_, config) { addColumns(count) {
return ( const self = this.getWritable();
<Suspense> const rows = self.__rows;
<TableComponent for (let y = 0; y < rows.length; y++) {
nodeKey={this.__key} const row = rows[y];
theme={config.theme} const cells = row.cells;
rows={this.__rows} const cellsClone = Array.from(cells);
/> const rowClone = {...row, cells: cellsClone};
</Suspense> const type = cells[cells.length - 1].type;
); for (let x = 0; x < count; x++) {
} cellsClone.push(createCell(type));
}
rows[y] = rowClone;
}
}
isInline() { insertRowAt(y) {
return false; const self = this.getWritable();
} const rows = self.__rows;
const prevRow = rows[y] || rows[y - 1];
const cellCount = prevRow.cells.length;
const row = createRow();
for (let x = 0; x < cellCount; x++) {
const cell = createCell(prevRow.cells[x].type);
row.cells.push(cell);
}
rows.splice(y, 0, row);
}
deleteRowAt(y) {
const self = this.getWritable();
const rows = self.__rows;
rows.splice(y, 1);
}
addRows(count) {
const self = this.getWritable();
const rows = self.__rows;
const prevRow = rows[rows.length - 1];
const cellCount = prevRow.cells.length;
for (let y = 0; y < count; y++) {
const row = createRow();
for (let x = 0; x < cellCount; x++) {
const cell = createCell(prevRow.cells[x].type);
row.cells.push(cell);
}
rows.push(row);
}
}
updateColumnWidth(x, width) {
const self = this.getWritable();
const rows = self.__rows;
for (let y = 0; y < rows.length; y++) {
const row = rows[y];
const cells = row.cells;
const cellsClone = Array.from(cells);
const rowClone = {...row, cells: cellsClone};
cellsClone[x].width = width;
rows[y] = rowClone;
}
}
decorate(_, config) {
return (
<Suspense>
<TableComponent
nodeKey={this.__key}
theme={config.theme}
rows={this.__rows}
/>
</Suspense>
);
}
isInline() {
return false;
}
} }
export function $isTableNode( export function $isTableNode(
node , node,
){ ) {
return node instanceof TableNode; return node instanceof TableNode;
} }

View File

@ -4,6 +4,7 @@ import {ListItemNode, ListNode} from "@lexical/list";
import {CodeHighlightNode, CodeNode, $createCodeNode, $isCodeNode} from "@lexical/code"; import {CodeHighlightNode, CodeNode, $createCodeNode, $isCodeNode} from "@lexical/code";
import {AutoLinkNode, LinkNode} from "@lexical/link"; import {AutoLinkNode, LinkNode} from "@lexical/link";
import {ImageNode} from "./ImageNode"; import {ImageNode} from "./ImageNode";
import {HorizontalRuleNode} from "@lexical/react/LexicalHorizontalRuleNode";
const UsefulNodes=[ const UsefulNodes=[
HeadingNode, HeadingNode,
@ -17,6 +18,7 @@ const UsefulNodes=[
TableRowNode, TableRowNode,
AutoLinkNode, AutoLinkNode,
ImageNode, ImageNode,
LinkNode LinkNode,
HorizontalRuleNode,
] ]
export default UsefulNodes; export default UsefulNodes;

View File

@ -0,0 +1,89 @@
import './index.less';
import * as React from 'react';
import {useEffect, useRef} from 'react';
import {createPortal} from 'react-dom';
function PortalImpl({ onClose,
children,
title,
closeOnClickOutside,
}) {
const modalRef = useRef(null);
useEffect(() => {
console.log("PortalImpl")
if (modalRef.current !== null) {
modalRef.current.focus();
}
}, []);
useEffect(() => {
let modalOverlayElement= null;
const handler = (event) => {
if (event.keyCode === 27) {
onClose();
}
};
const clickOutsideHandler = (event) => {
const target = event.target;
if (
modalRef.current !== null &&
!modalRef.current.contains(target) &&
closeOnClickOutside
) {
onClose();
}
};
const modelElement = modalRef.current;
if (modelElement !== null) {
modalOverlayElement = modelElement.parentElement;
if (modalOverlayElement !== null) {
modalOverlayElement.addEventListener('click', clickOutsideHandler);
}
}
window.addEventListener('keydown', handler);
return () => {
window.removeEventListener('keydown', handler);
if (modalOverlayElement !== null) {
modalOverlayElement?.removeEventListener('click', clickOutsideHandler);
}
};
}, [closeOnClickOutside, onClose]);
return (
<div className="Modal__overlay" role="dialog">
<div className="Modal__modal" tabIndex={-1} ref={modalRef}>
<h2 className="Modal__title">{title}</h2>
<button
className="Modal__closeButton"
aria-label="Close modal"
type="button"
onClick={onClose}>
X
</button>
<div className="Modal__content">{children}</div>
</div>
</div>
);
}
export default function Modal({
onClose,
children,
title,
closeOnClickOutside = false,
}){
console.log("createPortal")
return createPortal(
<PortalImpl
onClose={onClose}
title={title}
closeOnClickOutside={closeOnClickOutside}>
{children}
</PortalImpl>,
document.body,
);
}

View File

@ -0,0 +1,53 @@
.Modal__overlay {
display: flex;
justify-content: center;
align-items: center;
position: fixed;
flex-direction: column;
top: 0px;
bottom: 0px;
left: 0px;
right: 0px;
background-color: rgba(40, 40, 40, 0.6);
flex-grow: 0px;
flex-shrink: 1px;
z-index: 100;
}
.Modal__modal {
padding: 20px;
min-height: 100px;
min-width: 300px;
display: flex;
flex-grow: 0px;
background-color: #fff;
flex-direction: column;
position: relative;
box-shadow: 0 0 20px 0 #444;
border-radius: 10px;
}
.Modal__title {
color: #444;
margin: 0px;
padding-bottom: 10px;
border-bottom: 1px solid #ccc;
}
.Modal__closeButton {
border: 0px;
position: absolute;
right: 20px;
border-radius: 20px;
justify-content: center;
align-items: center;
display: flex;
width: 30px;
height: 30px;
text-align: center;
cursor: pointer;
background-color: #eee;
}
.Modal__closeButton:hover {
background-color: #ddd;
}
.Modal__content {
padding-top: 20px;
}

View File

@ -102,53 +102,53 @@ export function InsertTableDialog({activeEditor,
); );
} }
export function InsertNewTableDialog({activeEditor, onClose, // export function InsertNewTableDialog({activeEditor, onClose,
}){ // }){
const [rows, setRows] = useState(''); // const [rows, setRows] = useState('');
const [columns, setColumns] = useState(''); // const [columns, setColumns] = useState('');
const [isDisabled, setIsDisabled] = useState(true); // const [isDisabled, setIsDisabled] = useState(true);
//
useEffect(() => { // useEffect(() => {
const row = Number(rows); // const row = Number(rows);
const column = Number(columns); // const column = Number(columns);
if (row && row > 0 && row <= 500 && column && column > 0 && column <= 50) { // if (row && row > 0 && row <= 500 && column && column > 0 && column <= 50) {
setIsDisabled(false); // setIsDisabled(false);
} else { // } else {
setIsDisabled(true); // setIsDisabled(true);
} // }
}, [rows, columns]); // }, [rows, columns]);
//
const onClick = () => { // const onClick = () => {
activeEditor.dispatchCommand(INSERT_NEW_TABLE_COMMAND, {columns, rows}); // activeEditor.dispatchCommand(INSERT_NEW_TABLE_COMMAND, {columns, rows});
onClose(); // onClose();
}; // };
//
return ( // return (
<> // <>
<TextInput // <TextInput
placeholder={'# of rows (1-500)'} // placeholder={'# of rows (1-500)'}
label="Rows" // label="Rows"
onChange={setRows} // onChange={setRows}
value={rows} // value={rows}
data-test-id="table-modal-rows" // data-test-id="table-modal-rows"
type="number" // type="number"
/> // />
<TextInput // <TextInput
placeholder={'# of columns (1-50)'} // placeholder={'# of columns (1-50)'}
label="Columns" // label="Columns"
onChange={setColumns} // onChange={setColumns}
value={columns} // value={columns}
data-test-id="table-modal-columns" // data-test-id="table-modal-columns"
type="number" // type="number"
/> // />
<DialogActions data-test-id="table-model-confirm-insert"> // <DialogActions data-test-id="table-model-confirm-insert">
<Button disabled={isDisabled} onClick={onClick}> // <Button disabled={isDisabled} onClick={onClick}>
Confirm // Confirm
</Button> // </Button>
</DialogActions> // </DialogActions>
</> // </>
); // );
} // }
export function TablePlugin({ export function TablePlugin({
cellEditorConfig, cellEditorConfig,
@ -165,7 +165,7 @@ export function TablePlugin({
cellContext.set(cellEditorConfig, children); cellContext.set(cellEditorConfig, children);
return editor.registerCommand( return editor.registerCommand(
INSERT_NEW_TABLE_COMMAND, INSERT_TABLE_COMMAND,
({columns, rows, includeHeaders}) => { ({columns, rows, includeHeaders}) => {
const tableNode = $createTableNodeWithDimensions( const tableNode = $createTableNodeWithDimensions(
Number(rows), Number(rows),

View File

@ -44,7 +44,7 @@ import DropDown, {DropDownItem} from "./Input/DropDown";
import {InsertImageDialog} from "./ImagesPlugin"; import {InsertImageDialog} from "./ImagesPlugin";
import useModal from "../hook/userModal"; import useModal from "../hook/userModal";
import {InsertTableDialog} from "./TablePlugin"; import {InsertTableDialog} from "./TablePlugin";
import {INSERT_HORIZONTAL_RULE_COMMAND} from "@lexical/react/LexicalHorizontalRuleNode.prod"; import {INSERT_HORIZONTAL_RULE_COMMAND} from "@lexical/react/LexicalHorizontalRuleNode";
const LowPriority = 1; const LowPriority = 1;
@ -701,11 +701,10 @@ export default function ToolbarPlugin() {
> >
<i className="format justify-align"/> <i className="format justify-align"/>
</button> </button>
{" "}
<Divider/> <Divider/>
<DropDown <DropDown
buttonClassName="toolbar-item spaced" buttonClassName="toolbar-item spaced"
buttonLabel="Insert" buttonLabel="插入"
buttonAriaLabel="Insert specialized editor node" buttonAriaLabel="Insert specialized editor node"
buttonIconClassName="icon plus"> buttonIconClassName="icon plus">
<DropDownItem <DropDownItem
@ -717,16 +716,16 @@ export default function ToolbarPlugin() {
}} }}
className="item"> className="item">
<i className="icon horizontal-rule"/> <i className="icon horizontal-rule"/>
<span className="text">Horizontal Rule</span> <span className="text">分割线</span>
</DropDownItem>
<DropDownItem
onClick={() => {
activeEditor.dispatchCommand(INSERT_PAGE_BREAK, undefined);
}}
className="item">
<i className="icon page-break"/>
<span className="text">Page Break</span>
</DropDownItem> </DropDownItem>
{/*<DropDownItem*/}
{/* onClick={() => {*/}
{/* activeEditor.dispatchCommand(INSERT_PAGE_BREAK, undefined);*/}
{/* }}*/}
{/* className="item">*/}
{/* <i className="icon page-break"/>*/}
{/* <span className="text">页分割线</span>*/}
{/*</DropDownItem>*/}
<DropDownItem <DropDownItem
onClick={() => { onClick={() => {
showModal('Insert Image', (onClose) => ( showModal('Insert Image', (onClose) => (
@ -738,7 +737,7 @@ export default function ToolbarPlugin() {
}} }}
className="item"> className="item">
<i className="icon image"/> <i className="icon image"/>
<span className="text">Image</span> <span className="text">图片</span>
</DropDownItem> </DropDownItem>
<DropDownItem <DropDownItem
onClick={() => { onClick={() => {
@ -751,7 +750,7 @@ export default function ToolbarPlugin() {
}} }}
className="item"> className="item">
<i className="icon image"/> <i className="icon image"/>
<span className="text">Inline Image</span> <span className="text">行内图片</span>
</DropDownItem> </DropDownItem>
<DropDownItem <DropDownItem
onClick={() => onClick={() =>
@ -762,7 +761,7 @@ export default function ToolbarPlugin() {
} }
className="item"> className="item">
<i className="icon gif"/> <i className="icon gif"/>
<span className="text">GIF</span> <span className="text">动态图片</span>
</DropDownItem> </DropDownItem>
<DropDownItem <DropDownItem
onClick={() => { onClick={() => {
@ -773,7 +772,7 @@ export default function ToolbarPlugin() {
}} }}
className="item"> className="item">
<i className="icon diagram-2"/> <i className="icon diagram-2"/>
<span className="text">Excalidraw</span> <span className="text">画图</span>
</DropDownItem> </DropDownItem>
<DropDownItem <DropDownItem
onClick={() => { onClick={() => {
@ -789,7 +788,7 @@ export default function ToolbarPlugin() {
<span className="text">表格</span> <span className="text">表格</span>
</DropDownItem> </DropDownItem>
</DropDown> </DropDown>
{modal}
</> </>
)} )}
</div> </div>