Skip to content

Commit

Permalink
✨ 增加各种格式的纯文本导出功能
Browse files Browse the repository at this point in the history
  • Loading branch information
Littlefean committed Dec 3, 2024
1 parent d1d3a6e commit eedf6f1
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 26 deletions.
70 changes: 48 additions & 22 deletions src/core/stage/StageSaveManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -180,27 +180,41 @@ export namespace StageSaveManager {
textNode: TextNode,
successCallback: () => void,
errorCallback: (err: any) => void,
) {
const content = getMarkdownStringByTextNode(textNode);
invoke<string>("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<string>();

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,
);
Expand All @@ -210,17 +224,29 @@ export namespace StageSaveManager {
};

dfs(textNode, 1);
return content;
}

invoke<string>("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 自动保存相关
Expand Down
6 changes: 4 additions & 2 deletions src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -371,11 +372,12 @@ export default function App() {
</div>

{/* 面板列表 */}
<TagPanel open={isTagPanelOpen} className="z-10"/>
<AppMenu className="absolute left-4 top-16 z-20" open={isMenuOpen} />
<RecentFilesPanel />
<TagPanel open={isTagPanelOpen} className="z-10" />
<StartFilePanel open={isStartFilePanelOpen} />
<AiPanel open={isAiPanelOpen} />
<RecentFilesPanel />
<ExportTreeTextPanel/>
{/* ======= */}

<Outlet />
Expand Down
37 changes: 36 additions & 1 deletion src/pages/_app_menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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()) {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -423,6 +455,9 @@ export default function AppMenu({
<Col icon={<FileType />} onClick={onSaveMarkdownNew}>
{t("export.items.exportAsMarkdownBySelected")}
</Col>
<Col icon={<FileCode />} onClick={onExportTreeText}>
导出纯文本
</Col>
</Row>
<Row icon={<View />} title={t("view.title")}>
<Col icon={<SquareDashedKanbanIcon />} onClick={() => Camera.reset()}>
Expand Down
70 changes: 70 additions & 0 deletions src/pages/_export_text_panel.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div
className={cn(
"fixed left-1/2 top-1/2 z-10 flex h-4/5 w-3/4 -translate-x-1/2 -translate-y-1/2 transform flex-col items-center overflow-y-scroll rounded-md bg-gray-800 px-2 py-6",
{
hidden: !isExportTreeTextPanelOpen,
},
)}
onClick={(e) => {
e.stopPropagation();
}}
>
<h2 className="text-lg font-bold">导出节点纯文本</h2>
<div className="flex gap-2">
<CodePre text={tabText} title="纯缩进类型" />
<CodePre text={markdownText} title="markdown类型" />
</div>
<button
className="absolute right-0 top-0 rounded bg-red-500 px-4 py-2 font-bold text-white hover:bg-red-700"
onClick={() => setIsExportTreeTextPanelOpen(false)}
>
关闭
</button>
</div>
);
}

function CodePre({ text, title }: { text: string; title: string }) {
return (
<div>
<h4 className="text-sm font-bold">{title}</h4>
<pre className="select-text rounded-md bg-black p-2 text-xs text-slate-400">
{text}
</pre>
</div>
);
}
2 changes: 1 addition & 1 deletion src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export default function Home() {
<SearchingNodePanel />
<DetailsEditPanel />
<HintText />
{/* TODO: 下面这个写法有点奇怪 */}
{/* TODO: 下面这个写法有点奇怪 rgba值太长了 */}
<div
style={{
background: `rgba(${StageStyleManager.currentStyle.BackgroundColor.r},${StageStyleManager.currentStyle.BackgroundColor.g},${StageStyleManager.currentStyle.BackgroundColor.b},${bgAlpha})`,
Expand Down
11 changes: 11 additions & 0 deletions src/state.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,14 @@ export const isRecentFilePanelOpenAtom = atom({
key: "isRecentFilePanelOpen",
default: false,
});



/**
* 是否显示导出树形纯文本节点面板
*/
export const isExportTreeTextPanelOpenAtom = atom({
key: "isExportTreeTextPanelOpen",
default: false,
});

0 comments on commit eedf6f1

Please sign in to comment.