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">
+
+
+ label="导出文件名" name="file-name">
+
+
+
+
+
+
+
+ );
+};
+
+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);
+};