diff --git a/localization/locales/zh_CN/translation.json b/localization/locales/zh_CN/translation.json
index 2002f61f..3b58a08c 100644
--- a/localization/locales/zh_CN/translation.json
+++ b/localization/locales/zh_CN/translation.json
@@ -110,6 +110,9 @@
"CreateWiki": "创建WIKI",
"ImportWiki": "导入WIKI",
"CloneWiki": "导入线上Wiki",
+ "OpenLocalWikiFromHTML":"导入WIKI.HTML",
+ "LocalWikiHtml":"tiddlywiki.html文件路径",
+ "StoreWikiFolderLocation":"转换后的WIKI父文件夹路径",
"NotLoggedIn": "未登录",
"LogoutToGetStorageServiceToken": "登录在线存储服务以获取最新凭证",
"LogoutGithubAccount": "登出Github账号",
diff --git a/src/pages/AddWorkspace/ImportHtmlWikiDoneButton.tsx b/src/pages/AddWorkspace/ImportHtmlWikiDoneButton.tsx
new file mode 100644
index 00000000..37973b56
--- /dev/null
+++ b/src/pages/AddWorkspace/ImportHtmlWikiDoneButton.tsx
@@ -0,0 +1,59 @@
+/* eslint-disable @typescript-eslint/strict-boolean-expressions */
+import { useTranslation } from 'react-i18next';
+import Alert from '@material-ui/lab/Alert';
+
+import { Typography, LinearProgress, Snackbar } from '@material-ui/core';
+import type { IWikiWorkspaceFormProps } from './useForm';
+import { useValidateHtmlWiki, useImportHtmlWiki } from './useImportHtmlWiki';
+import { useWikiCreationProgress } from './useIndicator';
+import { WikiLocation, CloseButton, ReportErrorFabButton } from './FormComponents';
+
+export function ImportHtmlWikiDoneButton({
+ form,
+ isCreateMainWorkspace,
+ isCreateSyncedWorkspace,
+ errorInWhichComponentSetter,
+}: IWikiWorkspaceFormProps & { isCreateMainWorkspace: boolean; isCreateSyncedWorkspace: boolean }): JSX.Element {
+ const { t } = useTranslation();
+ const [hasError, wikiCreationMessage, wikiCreationMessageSetter, hasErrorSetter] = useValidateHtmlWiki(
+ isCreateMainWorkspace,
+ isCreateSyncedWorkspace,
+ form,
+ errorInWhichComponentSetter,
+ );
+ const onSubmit = useImportHtmlWiki(
+ isCreateMainWorkspace,
+ isCreateSyncedWorkspace,
+ form,
+ wikiCreationMessageSetter,
+ hasErrorSetter,
+ errorInWhichComponentSetter,
+ );
+ const [logPanelOpened, logPanelSetter, inProgressOrError] = useWikiCreationProgress(wikiCreationMessageSetter, wikiCreationMessage, hasError);
+ if (hasError) {
+ return (
+ <>
+
+ {wikiCreationMessage}
+
+ {wikiCreationMessage !== undefined && }
+ >
+ );
+ }
+ return (
+ <>
+ {inProgressOrError && }
+ {/* 这个好像是log面板 */}
+ logPanelSetter(false)}>
+ {wikiCreationMessage}
+
+
+
+
+ {t('AddWorkspace.ImportWiki')}
+
+ {form.wikiHtmlPath}
+
+ >
+ );
+}
diff --git a/src/pages/AddWorkspace/ImportHtmlWikiForm.tsx b/src/pages/AddWorkspace/ImportHtmlWikiForm.tsx
new file mode 100644
index 00000000..aa5a13f5
--- /dev/null
+++ b/src/pages/AddWorkspace/ImportHtmlWikiForm.tsx
@@ -0,0 +1,96 @@
+/* eslint-disable @typescript-eslint/strict-boolean-expressions */
+import React, { useCallback } from 'react';
+
+import { useTranslation } from 'react-i18next';
+import { Typography } from '@material-ui/core';
+import { Folder as FolderIcon } from '@material-ui/icons';
+import { useValidateHtmlWiki } from './useImportHtmlWiki';
+
+import { CreateContainer, LocationPickerContainer, LocationPickerInput, LocationPickerButton } from './FormComponents';
+
+import type { IWikiWorkspaceFormProps } from './useForm';
+
+export function ImportHtmlWikiForm({
+ form,
+ isCreateMainWorkspace,
+ isCreateSyncedWorkspace,
+ errorInWhichComponent,
+ errorInWhichComponentSetter,
+}: IWikiWorkspaceFormProps & { isCreateSyncedWorkspace: boolean }): JSX.Element {
+ const { t } = useTranslation();
+ const { wikiHtmlPathSetter, extractWikiHtmlParentFolderSetter } = form;
+
+ useValidateHtmlWiki(isCreateMainWorkspace, isCreateSyncedWorkspace, form, errorInWhichComponentSetter);
+
+ const onWikiLocationChange = useCallback(
+ async (newLocation: string) => {
+ if (newLocation !== undefined) {
+ wikiHtmlPathSetter(newLocation);
+ }
+ },
+ [wikiHtmlPathSetter],
+ );
+ const onSaveLocationChange = useCallback(
+ async (newLocation: string) => {
+ if (newLocation !== undefined) {
+ extractWikiHtmlParentFolderSetter(newLocation);
+ }
+ },
+ [extractWikiHtmlParentFolderSetter],
+ );
+ return (
+
+
+ {
+ // https://zh-hans.reactjs.org/docs/events.html#clipboard-events
+ onWikiLocationChange(event.target.value);
+ }}
+ label={t('AddWorkspace.LocalWikiHtml')}
+ value={form.wikiHtmlPath}
+ />
+ {
+ // first clear the text, so button will refresh
+ wikiHtmlPathSetter('');
+ const filePaths = await window.service.native.pickFile([{ name: 'html文件', extensions: ['html', 'htm'] }]);
+ if (filePaths?.length > 0) {
+ wikiHtmlPathSetter(filePaths[0]);
+ }
+ }}
+ endIcon={}>
+
+ {t('AddWorkspace.Choose')}
+
+
+
+
+ {
+ onSaveLocationChange(event.target.value);
+ }}
+ label={t('AddWorkspace.StoreWikiFolderLocation')}
+ value={form.extractWikiHtmlParentFolder}
+ />
+ {
+ // first clear the text, so button will refresh
+ extractWikiHtmlParentFolderSetter('');
+ const filePaths = await window.service.native.pickDirectory(form.wikiFolderLocation);
+ if (filePaths?.length > 0) {
+ extractWikiHtmlParentFolderSetter(filePaths[0]);
+ }
+ }}
+ endIcon={}>
+
+ {t('AddWorkspace.Choose')}
+
+
+
+
+ );
+}
diff --git a/src/pages/AddWorkspace/index.tsx b/src/pages/AddWorkspace/index.tsx
index 91200838..bc61f631 100644
--- a/src/pages/AddWorkspace/index.tsx
+++ b/src/pages/AddWorkspace/index.tsx
@@ -23,11 +23,14 @@ import { TokenForm } from '@/components/TokenForm';
import { GitRepoUrlForm } from './GitRepoUrlForm';
import { LocationPickerContainer, LocationPickerInput } from './FormComponents';
import { usePromiseValue } from '@/helpers/useServiceValue';
+import { ImportHtmlWikiForm } from './ImportHtmlWikiForm';
+import { ImportHtmlWikiDoneButton } from './ImportHtmlWikiDoneButton';
enum CreateWorkspaceTabs {
CloneOnlineWiki = 'CloneOnlineWiki',
CreateNewWiki = 'CreateNewWiki',
OpenLocalWiki = 'OpenLocalWiki',
+ OpenLocalWikiFromHtml = 'OpenLocalWikiFromHtml',
}
export const Paper = styled(PaperRaw)`
@@ -122,6 +125,7 @@ export function AddWorkspace(): JSX.Element {
+
@@ -172,6 +176,12 @@ export function AddWorkspace(): JSX.Element {
+
+
+
+
+
+
);
}
diff --git a/src/pages/AddWorkspace/useForm.ts b/src/pages/AddWorkspace/useForm.ts
index 626ae047..0d63f1ed 100644
--- a/src/pages/AddWorkspace/useForm.ts
+++ b/src/pages/AddWorkspace/useForm.ts
@@ -106,6 +106,23 @@ export function useWikiWorkspaceForm(options?: { fromExisted: boolean }) {
})();
}, [gitRepoUrlSetter, wikiFolderLocation, options?.fromExisted]);
+ /*
+ * 对于wikiHTML,我们使用两个状态保存文件与wiki解压父文件夹路径,并设置默认的wiki文件夹保存位置.
+ * wikiHtmlPath、wikiHtmlPathSetter、extractWikiHtmlParentFolder、extractWikiHtmlParentFolderSetter,
+ */
+ const [wikiHtmlPath, wikiHtmlPathSetter] = useState('');
+ useEffect(() => {
+ void (async function getDefaultWikiHtmlPathEffect() {})();
+ }, []);
+
+ const [extractWikiHtmlParentFolder, extractWikiHtmlParentFolderSetter] = useState('');
+ useEffect(() => {
+ void (async function getDefaultExtractWikiHtmlFolderPathEffect() {
+ const desktopPathAsDefaultExtractWikiHtmlParentFolderPath = await window.service.context.get('DEFAULT_WIKI_FOLDER');
+ extractWikiHtmlParentFolderSetter(desktopPathAsDefaultExtractWikiHtmlParentFolderPath);
+ })();
+ }, []);
+
return {
storageProvider,
storageProviderSetter,
@@ -128,6 +145,10 @@ export function useWikiWorkspaceForm(options?: { fromExisted: boolean }) {
workspaceList,
mainWorkspaceList,
mainWikiToLinkIndex,
+ wikiHtmlPath,
+ wikiHtmlPathSetter,
+ extractWikiHtmlParentFolder,
+ extractWikiHtmlParentFolderSetter,
};
}
diff --git a/src/pages/AddWorkspace/useImportHtmlWiki.ts b/src/pages/AddWorkspace/useImportHtmlWiki.ts
new file mode 100644
index 00000000..750f8c6d
--- /dev/null
+++ b/src/pages/AddWorkspace/useImportHtmlWiki.ts
@@ -0,0 +1,104 @@
+/* eslint-disable @typescript-eslint/strict-boolean-expressions */
+import { useCallback, useEffect, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { IErrorInWhichComponent, IWikiWorkspaceForm } from './useForm';
+import { updateErrorInWhichComponentSetterByErrorMessage } from './useIndicator';
+
+export function useValidateHtmlWiki(
+ isCreateMainWorkspace: boolean,
+ isCreateSyncedWorkspace: boolean,
+ form: IWikiWorkspaceForm,
+ errorInWhichComponentSetter: (errors: IErrorInWhichComponent) => void,
+): [boolean, string | undefined, (m: string) => void, (m: boolean) => void] {
+ const { t } = useTranslation();
+ const [wikiCreationMessage, wikiCreationMessageSetter] = useState();
+ const [hasError, hasErrorSetter] = useState(false);
+ useEffect(() => {
+ if (!form.parentFolderLocation) {
+ wikiCreationMessageSetter(`${t('AddWorkspace.NotFilled')}:${t('AddWorkspace.WorkspaceFolder')}`);
+ errorInWhichComponentSetter({ parentFolderLocation: true });
+ hasErrorSetter(true);
+ } else if (!form.wikiHtmlPath) {
+ // 判断wikiHtmlPath是否存在
+ wikiCreationMessageSetter(`${t('AddWorkspace.NotFilled')}:${t('AddWorkspace.LocalWikiHtml')}`);
+ errorInWhichComponentSetter({ wikiHtmlPath: true });
+ hasErrorSetter(true);
+ } else if (!form.wikiFolderName) {
+ wikiCreationMessageSetter(`${t('AddWorkspace.NotFilled')}:${t('AddWorkspace.WorkspaceFolderNameToCreate')}`);
+ errorInWhichComponentSetter({ wikiFolderName: true });
+ hasErrorSetter(true);
+ } else if (isCreateSyncedWorkspace && !form.gitRepoUrl) {
+ wikiCreationMessageSetter(`${t('AddWorkspace.NotFilled')}:${t('AddWorkspace.GitRepoUrl')}`);
+ errorInWhichComponentSetter({ gitRepoUrl: true });
+ hasErrorSetter(true);
+ } else if (!isCreateMainWorkspace && !form.mainWikiToLink?.wikiFolderLocation) {
+ wikiCreationMessageSetter(`${t('AddWorkspace.NotFilled')}:${t('AddWorkspace.MainWorkspace')}`);
+ errorInWhichComponentSetter({ mainWikiToLink: true });
+ hasErrorSetter(true);
+ } else if (isCreateSyncedWorkspace && (form.gitUserInfo === undefined || !(form.gitUserInfo.accessToken?.length > 0))) {
+ wikiCreationMessageSetter(t('AddWorkspace.NotLoggedIn'));
+ errorInWhichComponentSetter({ gitUserInfo: true });
+ hasErrorSetter(true);
+ } else {
+ wikiCreationMessageSetter('');
+ errorInWhichComponentSetter({});
+ hasErrorSetter(false);
+ }
+ }, [
+ t,
+ isCreateMainWorkspace,
+ isCreateSyncedWorkspace,
+ form.parentFolderLocation,
+ form.wikiFolderName,
+ form.gitRepoUrl,
+ form.gitUserInfo,
+ form.mainWikiToLink?.wikiFolderLocation,
+ form.tagName,
+ // 监听wikiHtmlPath,如果不存在就在提示用户。
+ form.wikiHtmlPath,
+ errorInWhichComponentSetter,
+ ]);
+
+ return [hasError, wikiCreationMessage, wikiCreationMessageSetter, hasErrorSetter];
+}
+
+export function useImportHtmlWiki(
+ isCreateMainWorkspace: boolean,
+ isCreateSyncedWorkspace: boolean,
+ form: IWikiWorkspaceForm,
+ wikiCreationMessageSetter: (m: string) => void,
+ hasErrorSetter: (m: boolean) => void,
+ errorInWhichComponentSetter: (errors: IErrorInWhichComponent) => void,
+): () => Promise {
+ const { t } = useTranslation();
+
+ const onSubmit = useCallback(async () => {
+ wikiCreationMessageSetter(t('AddWorkspace.Processing'));
+ hasErrorSetter(false);
+ try {
+ // 如果是HTML文件,即使转换错误,删掉在执行一次也不会出错。
+ // 我希望判断用户输入的是否是HTML文件,如果不是就不予执行。然后在判断如果失败了就删除这个数据并且提示错误信息。如果输入的是html类型的文件是不会出错的,即使是非wiki类型的文件。如果输出的目录非空,那么会导致异常闪退。
+ // 我希望,可以创建一个与HTML文件名一样的文件夹,这样的话就可以在父文件夹中解压出一个HTML文件名一样的文件夹而不只是wiki数据。
+ // 我希望信息可以显示在log面板。当出现解压错误后就提示用户。
+ const extractState = await window.service.wiki.extractWikiHTML(form.wikiHtmlPath, form.extractWikiHtmlParentFolder);
+ // 待解决: var extractState用于接收执行是否解压是否成功。但是接收不到window.service.wiki.extractWikiHTML函数的返回值,并且在该函数上下打log都未显示。
+ // 我希望在解压成功后设置好工作区的信息,执行打开解压后的wiki文件夹的操作。
+ } catch (error) {
+ wikiCreationMessageSetter((error as Error).message);
+ updateErrorInWhichComponentSetterByErrorMessage(t, (error as Error).message, errorInWhichComponentSetter);
+ hasErrorSetter(true);
+ }
+ }, [
+ wikiCreationMessageSetter,
+ t,
+ hasErrorSetter,
+ form,
+ isCreateMainWorkspace,
+ isCreateSyncedWorkspace,
+ form.wikiHtmlPath,
+ form.extractWikiHtmlParentFolder,
+ errorInWhichComponentSetter,
+ ]);
+
+ return onSubmit;
+}
diff --git a/src/services/wiki/index.ts b/src/services/wiki/index.ts
index 2a4e6ef5..f6629c92 100644
--- a/src/services/wiki/index.ts
+++ b/src/services/wiki/index.ts
@@ -168,6 +168,22 @@ export class Wiki implements IWikiService {
});
}
+ public async extractWikiHTML(htmlWikiPath: string, saveWikiFolderPath: string): Promise {
+ // hope saveWikiFolderPath = ParentFolderPath + wikifolderPath
+ // await fs.remove(saveWikiFolderPath); removes the folder function that failed to convert.
+ // We want the folder where the WIKI is saved to be empty, and we want the input htmlWiki to be an HTML file even if it is a non-wikiHTML file. Otherwise the program will exit abnormally.
+ // this.wikiWorkers[saveWikiFolderPath] = worker;
+ // Then, you can use this.getWorker (saveWikiFolderPath) method to call this wikiWorker that belongs to the HTMLWIKI after decompression
+ const worker = await spawn(new Worker(workerURL as string), { timeout: 1000 * 60 });
+ const result = await worker.ExtractWikiHTMLAndGetExtractState(htmlWikiPath, saveWikiFolderPath);
+ return result;
+ }
+
+ public async packetHTMLFromWikiFolder(folderWikiPath: string, saveWikiHtmlFolder: string): Promise {
+ const worker = await spawn(new Worker(workerURL as string), { timeout: 1000 * 60 });
+ await worker.packetHTMLFromWikiFolder(folderWikiPath, saveWikiHtmlFolder);
+ }
+
public async stopWiki(wikiFolderLocation: string): Promise {
const worker = this.getWorker(wikiFolderLocation);
if (worker === undefined) {
diff --git a/src/services/wiki/interface.ts b/src/services/wiki/interface.ts
index 641c8ece..b317cec7 100644
--- a/src/services/wiki/interface.ts
+++ b/src/services/wiki/interface.ts
@@ -35,6 +35,7 @@ export interface IWikiService {
*/
createSubWiki(parentFolderLocation: string, folderName: string, mainWikiPath: string, tagName?: string, onlyLink?: boolean): Promise;
ensureWikiExist(wikiPath: string, shouldBeMainWiki: boolean): Promise;
+ extractWikiHTML(htmlWikiPath: string, saveWikiFolderPath: string): Promise;
getSubWikiPluginContent(mainWikiPath: string): Promise;
getTiddlerText(workspace: IWorkspace, title: string): Promise;
getWikiLogs(homePath: string): Promise<{ content: string; filePath: string }>;
@@ -50,6 +51,7 @@ export interface IWikiService {
* @param title tiddler title to open
*/
openTiddlerInExternal(homePath: string, title: string): Promise;
+ packetHTMLFromWikiFolder(folderWikiPath: string, saveWikiHtmlfolder: string): Promise;
removeWiki(wikiPath: string, mainWikiToUnLink?: string, onlyRemoveLink?: boolean): Promise;
requestOpenTiddlerInWiki(tiddlerName: string): Promise;
/** send tiddlywiki action message to current active wiki */
@@ -98,6 +100,9 @@ export const WikiServiceIPCDescriptor = {
updateSubWikiPluginContent: ProxyPropertyType.Function,
wikiOperation: ProxyPropertyType.Function,
wikiStartup: ProxyPropertyType.Function,
+ // Register here to unpack and package wikiHTML functions
+ extractWikiHTML: ProxyPropertyType.Function,
+ packetHTMLFromWikiFolder: ProxyPropertyType.Function,
},
};
diff --git a/src/services/wiki/wikiWorker.ts b/src/services/wiki/wikiWorker.ts
index 0bf7bdee..91d73af6 100644
--- a/src/services/wiki/wikiWorker.ts
+++ b/src/services/wiki/wikiWorker.ts
@@ -131,6 +131,52 @@ function executeZxScript(file: IZxFileInput, zxPath: string): Observable wikiInstance?.boot?.files?.[tiddlerTitle], executeZxScript };
+function extractWikiHTML(htmlWikiPath: string, saveWikiFolderPath: string): boolean {
+ // tiddlywiki --load ./mywiki.html --savewikifolder ./mywikifolder
+ // --savewikifolder []
+ // . /mywikifolder is the path where the tiddlder and plugins folders are stored
+ let extractState = false;
+ // eslint-disable-next-line prefer-regex-literals
+ const reg = new RegExp(/(?:html|htm|Html|HTML|HTM)$/);
+ const isHtmlWiki = reg.test(htmlWikiPath);
+ if (!isHtmlWiki) {
+ console.error('Please enter the path to the tiddlywiki.html file. But the current path is: ' + htmlWikiPath);
+ return extractState;
+ } else {
+ try {
+ const wikiInstance = TiddlyWiki();
+ wikiInstance.boot.argv = ['--load', htmlWikiPath, '--savewikifolder', saveWikiFolderPath];
+ wikiInstance.boot.startup({});
+ // eslint-disable-next-line security-node/detect-crlf
+ console.log('Extract Wiki Html Successful: ' + saveWikiFolderPath);
+ extractState = true;
+ } catch (error) {
+ const message = `Tiddlywiki extractWikiHTML with error ${(error as Error).message} ${(error as Error).stack ?? ''}`;
+ console.error(message);
+ }
+ }
+ return extractState;
+}
+
+function packetHTMLFromWikiFolder(folderWikiPath: string, saveWikiHtmlFolder: string): void {
+ // tiddlywiki ./mywikifolder --rendertiddler '$:/core/save/all' mywiki.html text/plain
+ // . /mywikifolder is the path to the wiki folder, which generally contains the tiddlder and plugins directories
+ try {
+ const wikiInstance = TiddlyWiki();
+ wikiInstance.boot.argv = [folderWikiPath, '--rendertiddler', '$:/core/save/all', saveWikiHtmlFolder, 'text/plain'];
+ wikiInstance.boot.startup({});
+ } catch (error) {
+ const message = `Tiddlywiki packetHTMLFromWikiFolder with error ${(error as Error).message} ${(error as Error).stack ?? ''}`;
+ console.error(message);
+ }
+}
+
+const wikiWorker = {
+ startNodeJSWiki,
+ getTiddlerFileMetadata: (tiddlerTitle: string) => wikiInstance?.boot?.files?.[tiddlerTitle],
+ executeZxScript,
+ ExtractWikiHTMLAndGetExtractState: (htmlWikiPath: string, saveWikiFolderPath: string) => extractWikiHTML(htmlWikiPath, saveWikiFolderPath),
+ packetHTMLFromWikiFolder,
+};
export type WikiWorker = typeof wikiWorker;
expose(wikiWorker);