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

View File

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

View File

@ -71,7 +71,7 @@ export function extractRowsFromHTML(tableElem) {
return rows;
}
function convertTableElement(domNode){
function convertTableElement(domNode) {
const rowElems = domNode.querySelectorAll('tr');
if (!rowElems || rowElems.length === 0) {
return null;
@ -184,177 +184,177 @@ export class TableNode extends DecoratorNode {
}
constructor(rows, key) {
super(key);
this.__rows = rows || [];
}
super(key);
this.__rows = rows || [];
}
createDOM() {
return document.createElement('div');
}
createDOM() {
return document.createElement('div');
}
updateDOM() {
return false;
}
updateDOM() {
return false;
}
mergeRows(startX, startY, mergeRows) {
const self = this.getWritable();
const rows = self.__rows;
const endY = Math.min(rows.length, startY + mergeRows.length);
for (let y = startY; y < endY; y++) {
const row = rows[y];
const mergeRow = mergeRows[y - startY];
const cells = row.cells;
const cellsClone = Array.from(cells);
const rowClone = {...row, cells: cellsClone};
const mergeCells = mergeRow.cells;
const endX = Math.min(cells.length, startX + mergeCells.length);
for (let x = startX; x < endX; x++) {
mergeRows(startX, startY, mergeRows) {
const self = this.getWritable();
const rows = self.__rows;
const endY = Math.min(rows.length, startY + mergeRows.length);
for (let y = startY; y < endY; y++) {
const row = rows[y];
const mergeRow = mergeRows[y - startY];
const cells = row.cells;
const cellsClone = Array.from(cells);
const rowClone = {...row, cells: cellsClone};
const mergeCells = mergeRow.cells;
const endX = Math.min(cells.length, startX + mergeCells.length);
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 mergeCell = mergeCells[x - startX];
const cellClone = {...cell, json: mergeCell.json, type: mergeCell.type};
const cellsClone = Array.from(cells);
const cellClone = {...cell, json};
const rowClone = {...row, cells: cellsClone};
cellsClone[x] = cellClone;
rows[y] = rowClone;
}
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 cellsClone = Array.from(cells);
const cellClone = {...cell, json};
const rowClone = {...row, cells: cellsClone};
cellsClone[x] = cellClone;
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));
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;
}
rows[y] = rowClone;
}
}
insertRowAt(y) {
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);
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;
}
}
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;
}
}
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;
}
}
decorate(_, config) {
return (
<Suspense>
<TableComponent
nodeKey={this.__key}
theme={config.theme}
rows={this.__rows}
/>
</Suspense>
);
}
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;
}
}
isInline() {
return false;
}
insertRowAt(y) {
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(
node ,
){
node,
) {
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 {AutoLinkNode, LinkNode} from "@lexical/link";
import {ImageNode} from "./ImageNode";
import {HorizontalRuleNode} from "@lexical/react/LexicalHorizontalRuleNode";
const UsefulNodes=[
HeadingNode,
@ -17,6 +18,7 @@ const UsefulNodes=[
TableRowNode,
AutoLinkNode,
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,
}){
const [rows, setRows] = useState('');
const [columns, setColumns] = useState('');
const [isDisabled, setIsDisabled] = useState(true);
useEffect(() => {
const row = Number(rows);
const column = Number(columns);
if (row && row > 0 && row <= 500 && column && column > 0 && column <= 50) {
setIsDisabled(false);
} else {
setIsDisabled(true);
}
}, [rows, columns]);
const onClick = () => {
activeEditor.dispatchCommand(INSERT_NEW_TABLE_COMMAND, {columns, rows});
onClose();
};
return (
<>
<TextInput
placeholder={'# of rows (1-500)'}
label="Rows"
onChange={setRows}
value={rows}
data-test-id="table-modal-rows"
type="number"
/>
<TextInput
placeholder={'# of columns (1-50)'}
label="Columns"
onChange={setColumns}
value={columns}
data-test-id="table-modal-columns"
type="number"
/>
<DialogActions data-test-id="table-model-confirm-insert">
<Button disabled={isDisabled} onClick={onClick}>
Confirm
</Button>
</DialogActions>
</>
);
}
// export function InsertNewTableDialog({activeEditor, onClose,
// }){
// const [rows, setRows] = useState('');
// const [columns, setColumns] = useState('');
// const [isDisabled, setIsDisabled] = useState(true);
//
// useEffect(() => {
// const row = Number(rows);
// const column = Number(columns);
// if (row && row > 0 && row <= 500 && column && column > 0 && column <= 50) {
// setIsDisabled(false);
// } else {
// setIsDisabled(true);
// }
// }, [rows, columns]);
//
// const onClick = () => {
// activeEditor.dispatchCommand(INSERT_NEW_TABLE_COMMAND, {columns, rows});
// onClose();
// };
//
// return (
// <>
// <TextInput
// placeholder={'# of rows (1-500)'}
// label="Rows"
// onChange={setRows}
// value={rows}
// data-test-id="table-modal-rows"
// type="number"
// />
// <TextInput
// placeholder={'# of columns (1-50)'}
// label="Columns"
// onChange={setColumns}
// value={columns}
// data-test-id="table-modal-columns"
// type="number"
// />
// <DialogActions data-test-id="table-model-confirm-insert">
// <Button disabled={isDisabled} onClick={onClick}>
// Confirm
// </Button>
// </DialogActions>
// </>
// );
// }
export function TablePlugin({
cellEditorConfig,
@ -165,7 +165,7 @@ export function TablePlugin({
cellContext.set(cellEditorConfig, children);
return editor.registerCommand(
INSERT_NEW_TABLE_COMMAND,
INSERT_TABLE_COMMAND,
({columns, rows, includeHeaders}) => {
const tableNode = $createTableNodeWithDimensions(
Number(rows),

View File

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