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,21 +15,19 @@ export default function useModal(){
return null;
}
const {title, content, closeOnClickOutside} = modalContent;
return (<div>
console.log("modalContent !== null",modalContent)
return (
<Modal
open={isOpen}
//onOk={handleOk}
onCancel={onClose}
onClose={onClose}
title={title}
// closeOnClickOutside={closeOnClickOutside}
>
closeOnClickOutside={closeOnClickOutside}>
{content}
</Modal></div>
</Modal>
);
}, [modalContent, onClose,isOpen]);
}, [modalContent, onClose]);
const showModal = useCallback((
const showModal = useCallback(
(
title,
getContent,
closeOnClickOutside = false,

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;
@ -186,17 +186,17 @@ export class TableNode extends DecoratorNode {
constructor(rows, key) {
super(key);
this.__rows = rows || [];
}
}
createDOM() {
createDOM() {
return document.createElement('div');
}
}
updateDOM() {
updateDOM() {
return false;
}
}
mergeRows(startX, startY, mergeRows) {
mergeRows(startX, startY, mergeRows) {
const self = this.getWritable();
const rows = self.__rows;
const endY = Math.min(rows.length, startY + mergeRows.length);
@ -215,10 +215,10 @@ mergeRows(startX, startY, mergeRows) {
cellsClone[x] = cellClone;
}
rows[y] = rowClone;
}
}
}
}
updateCellJSON(x, y, json) {
updateCellJSON(x, y, json) {
const self = this.getWritable();
const rows = self.__rows;
const row = rows[y];
@ -229,9 +229,9 @@ updateCellJSON(x, y, json) {
const rowClone = {...row, cells: cellsClone};
cellsClone[x] = cellClone;
rows[y] = rowClone;
}
}
updateCellType(x, y, type) {
updateCellType(x, y, type) {
const self = this.getWritable();
const rows = self.__rows;
const row = rows[y];
@ -242,9 +242,9 @@ updateCellType(x, y, type) {
const rowClone = {...row, cells: cellsClone};
cellsClone[x] = cellClone;
rows[y] = rowClone;
}
}
insertColumnAt(x) {
insertColumnAt(x) {
const self = this.getWritable();
const rows = self.__rows;
for (let y = 0; y < rows.length; y++) {
@ -255,10 +255,10 @@ insertColumnAt(x) {
const type = (cells[x] || cells[x - 1]).type;
cellsClone.splice(x, 0, createCell(type));
rows[y] = rowClone;
}
}
}
}
deleteColumnAt(x) {
deleteColumnAt(x) {
const self = this.getWritable();
const rows = self.__rows;
for (let y = 0; y < rows.length; y++) {
@ -268,10 +268,10 @@ deleteColumnAt(x) {
const rowClone = {...row, cells: cellsClone};
cellsClone.splice(x, 1);
rows[y] = rowClone;
}
}
}
}
addColumns(count) {
addColumns(count) {
const self = this.getWritable();
const rows = self.__rows;
for (let y = 0; y < rows.length; y++) {
@ -284,10 +284,10 @@ addColumns(count) {
cellsClone.push(createCell(type));
}
rows[y] = rowClone;
}
}
}
}
insertRowAt(y) {
insertRowAt(y) {
const self = this.getWritable();
const rows = self.__rows;
const prevRow = rows[y] || rows[y - 1];
@ -296,17 +296,17 @@ insertRowAt(y) {
for (let x = 0; x < cellCount; x++) {
const cell = createCell(prevRow.cells[x].type);
row.cells.push(cell);
}
rows.splice(y, 0, row);
}
}
rows.splice(y, 0, row);
}
deleteRowAt(y) {
deleteRowAt(y) {
const self = this.getWritable();
const rows = self.__rows;
rows.splice(y, 1);
}
}
addRows(count) {
addRows(count) {
const self = this.getWritable();
const rows = self.__rows;
const prevRow = rows[rows.length - 1];
@ -319,10 +319,10 @@ addRows(count) {
row.cells.push(cell);
}
rows.push(row);
}
}
}
}
updateColumnWidth(x, width) {
updateColumnWidth(x, width) {
const self = this.getWritable();
const rows = self.__rows;
for (let y = 0; y < rows.length; y++) {
@ -332,10 +332,10 @@ updateColumnWidth(x, width) {
const rowClone = {...row, cells: cellsClone};
cellsClone[x].width = width;
rows[y] = rowClone;
}
}
}
}
decorate(_, config) {
decorate(_, config) {
return (
<Suspense>
<TableComponent
@ -345,16 +345,16 @@ decorate(_, config) {
/>
</Suspense>
);
}
}
isInline() {
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;

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>