diff --git a/packages/mini-markdown-editor/src/assets/images/output-pdf.svg b/packages/mini-markdown-editor/src/assets/images/output-pdf.svg deleted file mode 100644 index 04e056ae9d9541ee86f49395fe844abc1ef7d55a..0000000000000000000000000000000000000000 --- a/packages/mini-markdown-editor/src/assets/images/output-pdf.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/mini-markdown-editor/src/assets/images/output.svg b/packages/mini-markdown-editor/src/assets/images/output.svg new file mode 100644 index 0000000000000000000000000000000000000000..5583c4366d6075a8e873f831cde11e4b0f83a6f2 --- /dev/null +++ b/packages/mini-markdown-editor/src/assets/images/output.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/mini-markdown-editor/src/assets/styles/preview.css b/packages/mini-markdown-editor/src/assets/styles/preview.css index 60809786c7967663f0bc375ae63328d8ee6d393a..dd022b9ad7a4986d6aa3f27d22d7dd893490bdf8 100644 --- a/packages/mini-markdown-editor/src/assets/styles/preview.css +++ b/packages/mini-markdown-editor/src/assets/styles/preview.css @@ -56,7 +56,6 @@ } .mini-md-image { - width: 100%; max-width: 100%; border: 1px solid #e6e6e6; border-radius: 3px; diff --git a/packages/mini-markdown-editor/src/components/Sidebar/Contents.tsx b/packages/mini-markdown-editor/src/components/Sidebar/Contents.tsx index cc68ed1e88fb201fd56cb8d0091e7b967e921223..5319fc67a614384a0c302ea01dd4cdd9c53efecf 100644 --- a/packages/mini-markdown-editor/src/components/Sidebar/Contents.tsx +++ b/packages/mini-markdown-editor/src/components/Sidebar/Contents.tsx @@ -6,6 +6,7 @@ import { useEditorContentStore } from "@/store/editor"; const Contents: FC = () => { const previewView = useEditorContentStore((state) => state.previewView); const [titles, setTitles] = useState([]); + const [activeLink, setActiveLink] = useState(""); const preview = document.querySelector(".markdown-editor-preview") as HTMLElement | null; const getRootElement = () => { return preview?.querySelectorAll("h1, h2, h3, h4, h5, h6") as NodeListOf; @@ -29,7 +30,11 @@ const Contents: FC = () => { // 初始化时立即执行一次 if (rootElement.length > 0) { - setTitles(addAnchor()); + const initialTitles = addAnchor(); + setTitles(initialTitles); + if (initialTitles.length > 0) { + setActiveLink(initialTitles[0].href); + } } const observer = new MutationObserver(() => { @@ -37,7 +42,11 @@ const Contents: FC = () => { const elements = getRootElement(); if (elements && elements.length > 0) { requestAnimationFrame(() => { - setTitles(formatContents(elements)); + const newTitles = formatContents(elements); + setTitles(newTitles); + if (newTitles.length > 0 && !activeLink) { + setActiveLink(newTitles[0].href); + } }); } }); @@ -54,11 +63,42 @@ const Contents: FC = () => { }; }, [preview, rootElement]); - // 自定义高亮锚点(默认选中第一个) - const getCurrentAnchor = (activeLink: string): string => { - if (!activeLink && titles.length > 0) { - activeLink = titles[0].href; - } + // 监听滚动更新高亮 + useEffect(() => { + if (!preview) return; + + const handleScroll = () => { + const elements = getRootElement(); + if (!elements) return; + + // 找到当前视口中最靠近顶部的标题 + let closestTitle = null; + let minDistance = Infinity; + + elements.forEach((element) => { + // 判断哪个标题离视口顶部最近 + const rect = element.getBoundingClientRect(); + const distance = Math.abs(rect.top); + if (distance < minDistance) { + minDistance = distance; + closestTitle = element; + } + }); + + if (closestTitle) { + const line = (closestTitle as HTMLElement).getAttribute("data-line"); + if (line) { + setActiveLink(`#${line}`); + } + } + }; + + preview.addEventListener("scroll", handleScroll); + return () => preview.removeEventListener("scroll", handleScroll); + }, [preview]); + + // 自定义高亮锚点 + const getCurrentAnchor = () => { return activeLink; }; @@ -71,8 +111,11 @@ const Contents: FC = () => { ) => { e.preventDefault(); if (link.href && previewView) { - const targetElement = previewView.querySelector(`[data-line="${link.href}"]`); + // 从href中提取data-line值 + const dataLine = link.href.replace("#", ""); + const targetElement = previewView.querySelector(`[data-line="${dataLine}"]`); if (targetElement) { + setActiveLink(link.href); targetElement.scrollIntoView({ behavior: "smooth", block: "center", diff --git a/packages/mini-markdown-editor/src/components/Sidebar/Output.tsx b/packages/mini-markdown-editor/src/components/Sidebar/Output.tsx new file mode 100644 index 0000000000000000000000000000000000000000..faa9306c3d012166199fd2907fd0c0c1a50a2601 --- /dev/null +++ b/packages/mini-markdown-editor/src/components/Sidebar/Output.tsx @@ -0,0 +1,73 @@ +import styled from "styled-components"; +import { Button, Form, Input, Select } from "antd"; +import { exportHTML } from "@/utils/output-html"; +import { useEditorContentStore } from "@/store/editor"; +import { useState } from "react"; +import { exportPdf } from "@/utils/output-pdf"; + +const Wrapper = styled.div` + width: 100%; + height: 100%; + padding: 10px; + color: #3f4a54; +`; + +type FieldType = { + "file-type": "PDF" | "HTML"; + "file-name": string; +}; + +const Output = () => { + const [form] = Form.useForm(); + const preview = useEditorContentStore((state) => state.previewView); + const [loading, setLoading] = useState(false); + + const handleExport = async () => { + const { "file-type": fileType, "file-name": fileName } = form.getFieldsValue(); + setLoading(true); + switch (fileType) { + case "PDF": { + await exportPdf(preview!, fileName); + break; + } + case "HTML": { + if (!preview) return; + await exportHTML(preview, fileName); + break; + } + default: + break; + } + setLoading(false); + }; + + return ( + +
+ label="导出文件类型" name="file-type"> + + + + + + +
+ ); +}; + +export default Output; diff --git a/packages/mini-markdown-editor/src/components/Toolbar/ShowLayout.tsx b/packages/mini-markdown-editor/src/components/Toolbar/ShowLayout.tsx index de7727dd9b41812ecce82434f30e6a9a955ce8d5..736c5554c3cad2ee3cab78bcbca4670e06c93e04 100644 --- a/packages/mini-markdown-editor/src/components/Toolbar/ShowLayout.tsx +++ b/packages/mini-markdown-editor/src/components/Toolbar/ShowLayout.tsx @@ -5,9 +5,11 @@ import WriteIcon from "@/assets/images/write.svg?raw"; import PreviewIcon from "@/assets/images/perview.svg?raw"; import ContentsIcon from "@/assets/images/contents.svg?raw"; import HelpIcon from "@/assets/images/help.svg?raw"; +import OutputIcon from "@/assets/images/output.svg?raw"; import { useToolbarStore } from "@/store/toolbar"; import SidebarContents from "@/components/Sidebar/Contents"; import SidebarHelp from "@/components/Sidebar/Help"; +import SidebarOutput from "@/components/Sidebar/Output"; const Wrapper = styled.div<{ $isSelect: boolean }>` display: flex; @@ -78,3 +80,19 @@ export const Help: FC = () => { ); }; + +// 导出按钮 +export const Output: FC = () => { + const ComponentsMark = "Output"; + const { isSidebar, componentMark, setSidebar } = useToolbarStore(); + return ( + <> + setSidebar(, ComponentsMark)}> + + + + ); +}; diff --git a/packages/mini-markdown-editor/src/config/toolbar/base.tsx b/packages/mini-markdown-editor/src/config/toolbar/base.tsx index 8ee235bcf4bfc6e1cbf6d6bcb1db356f21e9fe4a..5ff8d984326532d04d074dcc30d82ace727c3ed4 100644 --- a/packages/mini-markdown-editor/src/config/toolbar/base.tsx +++ b/packages/mini-markdown-editor/src/config/toolbar/base.tsx @@ -14,13 +14,12 @@ import ImageIcon from "@/assets/images/image.svg"; import TableIcon from "@/assets/images/table.svg"; import Undo from "@/assets/images/undo.svg"; import Redo from "@/assets/images/redo.svg"; -import OutputPDFIcon from "@/assets/images/output-pdf.svg"; import { InsertTextEvent } from "./event"; import { ToolbarItem } from "@/types/toolbar"; // 组件 import Upload from "@/components/Toolbar/Upload"; import FullScreen from "@/components/Toolbar/FullScreen"; -import { Contents, Read, Write, Help } from "@/components/Toolbar/ShowLayout"; +import { Contents, Read, Write, Help, Output } from "@/components/Toolbar/ShowLayout"; export const toolbar: ToolbarItem[] = [ { @@ -182,13 +181,12 @@ export const toolbar: ToolbarItem[] = [ component: , }, { - type: "pdf", - icon: OutputPDFIcon, - title: "导出为PDF", + type: "output", + component: , }, ]; -// 渲染其他定制节点 +// 渲染列表其他定制节点 export const render = { "image-upload": , }; diff --git a/packages/mini-markdown-editor/src/types/toolbar.ts b/packages/mini-markdown-editor/src/types/toolbar.ts index 75647c5fd9546f5b674cbe043d93c9f7445bf45b..5295325232b3f890573661bea091c396f3e271eb 100644 --- a/packages/mini-markdown-editor/src/types/toolbar.ts +++ b/packages/mini-markdown-editor/src/types/toolbar.ts @@ -53,4 +53,4 @@ export type ToolbarType = | "preview" | "contents" | "help" - | "pdf"; + | "output"; diff --git a/packages/mini-markdown-editor/src/utils/output-html.ts b/packages/mini-markdown-editor/src/utils/output-html.ts new file mode 100644 index 0000000000000000000000000000000000000000..9867b305b28085eddd03f046231164367a762a95 --- /dev/null +++ b/packages/mini-markdown-editor/src/utils/output-html.ts @@ -0,0 +1,54 @@ +export const exportHTML = (element: HTMLElement, fileName: string) => { + return new Promise((resolve) => { + if (!element) { + console.error("Element not found"); + return; + } + + // 获取元素的HTML内容 + const htmlContent = element.outerHTML; + + // 获取所有样式 + const styles = Array.from(document.styleSheets) + .map((sheet) => { + try { + return Array.from(sheet.cssRules) + .map((rule) => rule.cssText) + .join("\n"); + } catch (e) { + console.error("Error accessing stylesheet:", e); + return ""; + } + }) + .join("\n"); + + // 创建包含样式的HTML内容 + const fullHtmlContent = ` + + + + + + Exported HTML + + + + ${htmlContent} + + + `; + + const blob = new Blob([fullHtmlContent], { type: "text/html" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = `${fileName}.html`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + resolve({}); + }); +}; diff --git a/packages/mini-markdown-editor/src/utils/output-pdf.ts b/packages/mini-markdown-editor/src/utils/output-pdf.ts new file mode 100644 index 0000000000000000000000000000000000000000..6c5c09a8f7066cbeaf554dc6f21af625df2b1954 --- /dev/null +++ b/packages/mini-markdown-editor/src/utils/output-pdf.ts @@ -0,0 +1,3 @@ +export const exportPdf = async (element: HTMLElement, filename: string) => { + console.log(element, filename); +};