From eedf6f14d9f07c98b797579432868388b0106c1f Mon Sep 17 00:00:00 2001 From: littlefean <2028140990@qq.com> Date: Tue, 3 Dec 2024 21:55:43 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20=E5=A2=9E=E5=8A=A0=E5=90=84?= =?UTF-8?q?=E7=A7=8D=E6=A0=BC=E5=BC=8F=E7=9A=84=E7=BA=AF=E6=96=87=E6=9C=AC?= =?UTF-8?q?=E5=AF=BC=E5=87=BA=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/stage/StageSaveManager.tsx | 70 ++++++++++++++++++++--------- src/pages/_app.tsx | 6 ++- src/pages/_app_menu.tsx | 37 ++++++++++++++- src/pages/_export_text_panel.tsx | 70 +++++++++++++++++++++++++++++ src/pages/index.tsx | 2 +- src/state.tsx | 11 +++++ 6 files changed, 170 insertions(+), 26 deletions(-) create mode 100644 src/pages/_export_text_panel.tsx diff --git a/src/core/stage/StageSaveManager.tsx b/src/core/stage/StageSaveManager.tsx index 46ef9a3..3d9c4c6 100644 --- a/src/core/stage/StageSaveManager.tsx +++ b/src/core/stage/StageSaveManager.tsx @@ -180,27 +180,41 @@ export namespace StageSaveManager { textNode: TextNode, successCallback: () => void, errorCallback: (err: any) => void, + ) { + const content = getMarkdownStringByTextNode(textNode); + invoke("save_file_by_path", { + path, + content, + }) + .then((_) => { + successCallback(); + }) + .catch((err) => { + errorCallback(err); + }); + } + + export function getMarkdownStringByTextNode(textNode: TextNode) { + return getTreeTypeString(textNode, getNodeMarkdown); + } + + export function getTabStringByTextNode(textNode: TextNode) { + return getTreeTypeString(textNode, getTabText); + } + + function getTreeTypeString( + textNode: TextNode, + nodeToStringFunc: (node: TextNode, level: number) => string, ) { let content = ""; const visitedUUID = new Set(); - const getNodeMarkdown = (node: TextNode, level: number) => { - let stringResult = ""; - if (level < 6) { - stringResult += `${"#".repeat(level)} ${node.text}\n\n`; - } else { - stringResult += `**${node.text}**\n\n`; - } - stringResult += `${node.details}\n\n`; - return stringResult; - }; - const dfs = (node: TextNode, level: number) => { if (visitedUUID.has(node.uuid)) { return; } visitedUUID.add(node.uuid); - content += getNodeMarkdown(node, level); + content += nodeToStringFunc(node, level); const children = StageManager.nodeChildrenArray(node).filter( (v) => v instanceof TextNode, ); @@ -210,17 +224,29 @@ export namespace StageSaveManager { }; dfs(textNode, 1); + return content; + } - invoke("save_file_by_path", { - path, - content, - }) - .then((_) => { - successCallback(); - }) - .catch((err) => { - errorCallback(err); - }); + function getNodeMarkdown(node: TextNode, level: number): string { + let stringResult = ""; + if (level < 6) { + stringResult += `${"#".repeat(level)} ${node.text}\n\n`; + } else { + stringResult += `**${node.text}**\n\n`; + } + if (node.details.trim()) { + stringResult += `${node.details}\n\n`; + } + return stringResult; + } + + function getTabText(node: TextNode, level: number): string { + let stringResult = ""; + stringResult += `${"\t".repeat(Math.max(level - 1, 0))}${node.text}\n`; + if (node.details.trim()) { + stringResult += `${"\t".repeat(level)}${node.details}\n`; + } + return stringResult; } // region 自动保存相关 diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 0003625..4872180 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -34,6 +34,7 @@ import ErrorHandler from "./_error_handler"; import RecentFilesPanel from "./_recent_files_panel"; import StartFilePanel from "./_start_file_panel"; import TagPanel from "./_tag_panel"; +import ExportTreeTextPanel from "./_export_text_panel"; export default function App() { const [maxmized, setMaxmized] = React.useState(false); @@ -371,11 +372,12 @@ export default function App() { {/* 面板列表 */} - - + + + {/* ======= */} diff --git a/src/pages/_app_menu.tsx b/src/pages/_app_menu.tsx index 4fc1811..6cb9956 100644 --- a/src/pages/_app_menu.tsx +++ b/src/pages/_app_menu.tsx @@ -33,7 +33,7 @@ import { useRecoilState } from "recoil"; import { Camera } from "../core/stage/Camera"; import { StageDumper } from "../core/stage/StageDumper"; import { StageManager } from "../core/stage/stageManager/StageManager"; -import { fileAtom, isRecentFilePanelOpenAtom } from "../state"; +import { fileAtom, isExportTreeTextPanelOpenAtom, isRecentFilePanelOpenAtom } from "../state"; import { cn } from "../utils/cn"; import { useDialog } from "../utils/dialog"; import { isDesktop } from "../utils/platform"; @@ -60,6 +60,7 @@ export default function AppMenu({ const [file, setFile] = useRecoilState(fileAtom); const { t } = useTranslation("appMenu"); const [, setRecentFilePanelOpen] = useRecoilState(isRecentFilePanelOpenAtom); + const [, setExportTreeTextPanelOpen] = useRecoilState(isExportTreeTextPanelOpenAtom); const onNew = () => { if (StageSaveManager.isSaved()) { @@ -269,6 +270,37 @@ export default function AppMenu({ ); }; + const onExportTreeText = async () => { + const selectedNodes = StageManager.getSelectedEntities().filter( + (entity) => entity instanceof TextNode, + ); + if (selectedNodes.length === 0) { + dialog.show({ + title: "没有选中节点", + content: + "请先选中一个根节点再使用此功能,并且根节点所形成的结构必须为树状结构", + type: "error", + }); + return; + } else if (selectedNodes.length > 1) { + dialog.show({ + title: "选中节点数量过多", + content: "只能选中一个根节点,并且根节点所形成的结构必须为树状结构", + type: "error", + }); + return; + } + if (!StageManager.isTree(selectedNodes[0])) { + dialog.show({ + title: "结构错误", + content: "根节点所形成的结构必须为树状结构", + type: "error", + }); + return; + } + setExportTreeTextPanelOpen(true); + } + const onSaveMarkdownNew = async () => { const selectedNodes = StageManager.getSelectedEntities().filter( (entity) => entity instanceof TextNode, @@ -423,6 +455,9 @@ export default function AppMenu({ } onClick={onSaveMarkdownNew}> {t("export.items.exportAsMarkdownBySelected")} + } onClick={onExportTreeText}> + 导出纯文本 + } title={t("view.title")}> } onClick={() => Camera.reset()}> diff --git a/src/pages/_export_text_panel.tsx b/src/pages/_export_text_panel.tsx new file mode 100644 index 0000000..8e63f7f --- /dev/null +++ b/src/pages/_export_text_panel.tsx @@ -0,0 +1,70 @@ +import { useEffect, useState } from "react"; +import { useRecoilState } from "recoil"; +import { isExportTreeTextPanelOpenAtom } from "../state"; +import { cn } from "../utils/cn"; +import { StageSaveManager } from "../core/stage/StageSaveManager"; +import { StageManager } from "../core/stage/stageManager/StageManager"; +import { TextNode } from "../core/stageObject/entity/TextNode"; + +/** + * 导出节点纯文本相关的面板 + * 树形的 + */ +export default function ExportTreeTextPanel() { + const [isExportTreeTextPanelOpen, setIsExportTreeTextPanelOpen] = + useRecoilState(isExportTreeTextPanelOpenAtom); + + const [markdownText, setMarkdownText] = useState(""); + const [tabText, setTabText] = useState(""); + + useEffect(() => { + if (!isExportTreeTextPanelOpen) { + return; + } + // 当前选择的节点通常来说是一个节点,且在上游已经确定了是树形结构 + const selectedNode = StageManager.getSelectedEntities()[0]; + if (!(selectedNode instanceof TextNode)) { + return; + } + + setMarkdownText(StageSaveManager.getMarkdownStringByTextNode(selectedNode)); + setTabText(StageSaveManager.getTabStringByTextNode(selectedNode)); + }, [isExportTreeTextPanelOpen]); + + return ( +
{ + e.stopPropagation(); + }} + > +

导出节点纯文本

+
+ + +
+ +
+ ); +} + +function CodePre({ text, title }: { text: string; title: string }) { + return ( +
+

{title}

+
+        {text}
+      
+
+ ); +} diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 5a195c4..6f7c86c 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -97,7 +97,7 @@ export default function Home() { - {/* TODO: 下面这个写法有点奇怪 */} + {/* TODO: 下面这个写法有点奇怪 rgba值太长了 */}