├── CODEOWNERS ├── tiddlers ├── README.md ├── README.zh-CN.md ├── $__favicon.ico.svg.meta ├── $__StoryList.tid ├── README.md.meta ├── README.zh-CN.md.meta ├── $__SiteTitle.tid ├── $__themes_tiddlywiki_vanilla_options_sidebarlayout.tid ├── $__DefaultTiddlers.tid ├── $__SiteSubtitle.tid ├── $__config_FileSystemPaths.tid └── $__favicon.ico.svg ├── .gitconfig ├── aur ├── README.md ├── build.sh └── PKGBUILD ├── banner.png ├── assets ├── README.md └── menu │ ├── open.svg │ ├── plus2.svg │ ├── release.svg │ ├── host.svg │ ├── update.svg │ ├── File.svg │ ├── winState.svg │ ├── folder.svg │ ├── plus.svg │ ├── error.svg │ ├── import.svg │ ├── power.svg │ ├── windows.svg │ ├── about.svg │ ├── folder-opened.svg │ ├── conf.svg │ ├── image.svg │ ├── username.svg │ ├── new-folder-template.svg │ ├── console.svg │ ├── star.svg │ ├── link.svg │ ├── settings.svg │ ├── devtools.svg │ ├── recent.svg │ ├── folder-close.svg │ ├── markdown.svg │ ├── html.svg │ ├── search.svg │ ├── help.svg │ ├── log.svg │ ├── folder-windows.svg │ ├── web_app.svg │ ├── downloading.svg │ ├── format.svg │ ├── module.svg │ ├── bug.svg │ ├── trash.svg │ ├── log2.svg │ ├── link2.svg │ ├── reload.svg │ ├── config.svg │ ├── folder-macOs.svg │ ├── macOs.svg │ ├── logo.svg │ ├── vscode.svg │ ├── searchGoogle.svg │ ├── gitHub.svg │ ├── gitHub_dark.svg │ ├── tw-light.svg │ ├── minimize.svg │ ├── tw-dark.svg │ ├── stop.svg │ ├── add.svg │ ├── folder-wiki.svg │ ├── screens.svg │ ├── copy.svg │ ├── folder-wiki1-dark.svg │ ├── folder-wiki1-light.svg │ ├── qrcode.svg │ ├── paste.svg │ ├── reset.svg │ ├── lock.svg │ ├── zoomOut.svg │ ├── save.svg │ ├── warning.svg │ ├── findInPage.svg │ ├── findInPage_dark.svg │ ├── clear.svg │ ├── info.svg │ ├── exit.svg │ ├── zoomIn.svg │ ├── i18n2.svg │ ├── success.svg │ ├── i18n.svg │ ├── restart.svg │ ├── read.svg │ ├── cut.svg │ ├── build.svg │ ├── featureSearch.svg │ ├── panda.svg │ ├── expandAll.svg │ ├── expandAll_dark.svg │ ├── export.svg │ ├── web.svg │ └── gear.svg ├── banner03.png ├── banner04.png ├── resources └── pngquant │ ├── README.md │ ├── pngquant-macOs │ └── pngquant-windows.exe ├── public └── assets │ ├── menu │ ├── File.png │ ├── add.png │ ├── bug.png │ ├── conf.png │ ├── copy.png │ ├── cut.png │ ├── exit.png │ ├── gear.png │ ├── help.png │ ├── host.png │ ├── html.png │ ├── i18n.png │ ├── info.png │ ├── link.png │ ├── lock.png │ ├── log.png │ ├── log2.png │ ├── logo.png │ ├── open.png │ ├── plus.png │ ├── read.png │ ├── save.png │ ├── star.png │ ├── stop.png │ ├── web.png │ ├── about.png │ ├── build.png │ ├── clear.png │ ├── config.png │ ├── console.png │ ├── error.png │ ├── export.png │ ├── folder.png │ ├── format.png │ ├── gitHub.png │ ├── i18n2.png │ ├── image.png │ ├── import.png │ ├── link2.png │ ├── linux.png │ ├── macOs.png │ ├── module.png │ ├── panda.png │ ├── paste.png │ ├── plus2.png │ ├── power.png │ ├── qrcode.png │ ├── recent.png │ ├── release.png │ ├── reload.png │ ├── reset.png │ ├── restart.png │ ├── screens.png │ ├── search.png │ ├── success.png │ ├── trash.png │ ├── tw-dark.png │ ├── update.png │ ├── vscode.png │ ├── warning.png │ ├── web_app.png │ ├── windows.png │ ├── zoomIn.png │ ├── zoomOut.png │ ├── devtools.png │ ├── expandAll.png │ ├── markdown.png │ ├── minimize.png │ ├── settings.png │ ├── tw-light.png │ ├── username.png │ ├── winState.png │ ├── downloading.png │ ├── findInPage.png │ ├── folder-close.png │ ├── folder-linux.png │ ├── folder-macOs.png │ ├── folder-wiki.png │ ├── gitHub_dark.png │ ├── searchGoogle.png │ ├── expandAll_dark.png │ ├── featureSearch.png │ ├── folder-opened.png │ ├── folder-windows.png │ ├── chrome-web-store.png │ ├── findInPage_dark.png │ ├── folder-wiki1-dark.png │ ├── folder-wiki1-light.png │ └── new-folder-template.png │ ├── tray-icon.png │ └── tray-icon-dev.png ├── .npmrc ├── __config.yml ├── src ├── utils │ ├── generateRandomPort.ts │ ├── updater.ts │ ├── generateId.ts │ ├── getFileSize.ts │ ├── checkEmptyDir.ts │ ├── getHost.ts │ ├── tiddlywiki.json │ ├── wikiTemplates.ts │ ├── renderer │ │ ├── clipper.ts │ │ ├── setSubwiki.ts │ │ ├── setOfficialLib.ts │ │ ├── markdown.ts │ │ └── setFavicon.ts │ ├── menubar │ │ ├── edit.ts │ │ ├── index.ts │ │ ├── help.ts │ │ └── view.ts │ ├── getPlatform.ts │ ├── config.js │ ├── tw-dialog.ts │ ├── wiki │ │ ├── constant.ts │ │ └── index.ts │ ├── trackWindowState.ts │ ├── importWeb.ts │ ├── convertPathToVSCodeUri.ts │ ├── createTray.ts │ ├── logger.ts │ ├── injectScript.ts │ ├── tiddlywiki.ts │ ├── subwiki │ │ └── index.ts │ ├── icon.ts │ └── downloadTpl.ts ├── i18n │ └── index.ts ├── modules │ ├── showInputBox │ │ └── index.ts │ └── markdown-importer │ │ └── index.ts └── preload │ └── index.ts ├── .gitignore ├── .vscode ├── settings.json └── launch.json ├── tsconfig.node.json ├── manifests └── o │ └── oeyoews │ └── tiddlywiki-app │ └── 4.7.1 │ ├── oeyoews.tiddlywiki-app.yaml │ ├── oeyoews.tiddlywiki-app.locale.en-US.yaml │ └── oeyoews.tiddlywiki-app.installer.yaml ├── scripts ├── locales │ ├── settings.js │ ├── tray.js │ ├── log.js │ └── app.js ├── minify-tw.js ├── createSymbolink.js └── generate-locales.js ├── .github └── workflows │ ├── page.yml │ └── build.yml ├── @types ├── i18next.d.ts ├── electron-extra.d.ts └── types.d.ts ├── tiddlywiki.info ├── test └── electron.test.ts ├── LICENSE ├── patches └── tw5-typed.patch ├── tsconfig.json ├── electron-builder.ts ├── README.zh-CN.md ├── package.json ├── vite.config.ts └── README.md /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @oeyoews -------------------------------------------------------------------------------- /tiddlers/README.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /tiddlers/README.zh-CN.md: -------------------------------------------------------------------------------- 1 | ../README.zh-CN.md -------------------------------------------------------------------------------- /.gitconfig: -------------------------------------------------------------------------------- 1 | [core] 2 | autocrlf = false 3 | ignorecase = false -------------------------------------------------------------------------------- /aur/README.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/banner.png -------------------------------------------------------------------------------- /tiddlers/$__favicon.ico.svg.meta: -------------------------------------------------------------------------------- 1 | title: $:/favicon.ico 2 | type: image/svg+xml -------------------------------------------------------------------------------- /assets/README.md: -------------------------------------------------------------------------------- 1 | > icon from https://github.com/AtomMaterialUI/a-file-icon-idea/ -------------------------------------------------------------------------------- /banner03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/banner03.png -------------------------------------------------------------------------------- /banner04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/banner04.png -------------------------------------------------------------------------------- /resources/pngquant/README.md: -------------------------------------------------------------------------------- 1 | > lib from https://github.com/imagemin/pngquant-bin/tree/main/vendor -------------------------------------------------------------------------------- /public/assets/menu/File.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/File.png -------------------------------------------------------------------------------- /public/assets/menu/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/add.png -------------------------------------------------------------------------------- /public/assets/menu/bug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/bug.png -------------------------------------------------------------------------------- /public/assets/menu/conf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/conf.png -------------------------------------------------------------------------------- /public/assets/menu/copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/copy.png -------------------------------------------------------------------------------- /public/assets/menu/cut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/cut.png -------------------------------------------------------------------------------- /public/assets/menu/exit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/exit.png -------------------------------------------------------------------------------- /public/assets/menu/gear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/gear.png -------------------------------------------------------------------------------- /public/assets/menu/help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/help.png -------------------------------------------------------------------------------- /public/assets/menu/host.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/host.png -------------------------------------------------------------------------------- /public/assets/menu/html.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/html.png -------------------------------------------------------------------------------- /public/assets/menu/i18n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/i18n.png -------------------------------------------------------------------------------- /public/assets/menu/info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/info.png -------------------------------------------------------------------------------- /public/assets/menu/link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/link.png -------------------------------------------------------------------------------- /public/assets/menu/lock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/lock.png -------------------------------------------------------------------------------- /public/assets/menu/log.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/log.png -------------------------------------------------------------------------------- /public/assets/menu/log2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/log2.png -------------------------------------------------------------------------------- /public/assets/menu/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/logo.png -------------------------------------------------------------------------------- /public/assets/menu/open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/open.png -------------------------------------------------------------------------------- /public/assets/menu/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/plus.png -------------------------------------------------------------------------------- /public/assets/menu/read.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/read.png -------------------------------------------------------------------------------- /public/assets/menu/save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/save.png -------------------------------------------------------------------------------- /public/assets/menu/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/star.png -------------------------------------------------------------------------------- /public/assets/menu/stop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/stop.png -------------------------------------------------------------------------------- /public/assets/menu/web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/web.png -------------------------------------------------------------------------------- /public/assets/tray-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/tray-icon.png -------------------------------------------------------------------------------- /tiddlers/$__StoryList.tid: -------------------------------------------------------------------------------- 1 | list: README.md README.zh-CN.md 2 | title: $:/StoryList 3 | type: text/vnd.tiddlywiki -------------------------------------------------------------------------------- /public/assets/menu/about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/about.png -------------------------------------------------------------------------------- /public/assets/menu/build.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/build.png -------------------------------------------------------------------------------- /public/assets/menu/clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/clear.png -------------------------------------------------------------------------------- /public/assets/menu/config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/config.png -------------------------------------------------------------------------------- /public/assets/menu/console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/console.png -------------------------------------------------------------------------------- /public/assets/menu/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/error.png -------------------------------------------------------------------------------- /public/assets/menu/export.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/export.png -------------------------------------------------------------------------------- /public/assets/menu/folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/folder.png -------------------------------------------------------------------------------- /public/assets/menu/format.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/format.png -------------------------------------------------------------------------------- /public/assets/menu/gitHub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/gitHub.png -------------------------------------------------------------------------------- /public/assets/menu/i18n2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/i18n2.png -------------------------------------------------------------------------------- /public/assets/menu/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/image.png -------------------------------------------------------------------------------- /public/assets/menu/import.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/import.png -------------------------------------------------------------------------------- /public/assets/menu/link2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/link2.png -------------------------------------------------------------------------------- /public/assets/menu/linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/linux.png -------------------------------------------------------------------------------- /public/assets/menu/macOs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/macOs.png -------------------------------------------------------------------------------- /public/assets/menu/module.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/module.png -------------------------------------------------------------------------------- /public/assets/menu/panda.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/panda.png -------------------------------------------------------------------------------- /public/assets/menu/paste.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/paste.png -------------------------------------------------------------------------------- /public/assets/menu/plus2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/plus2.png -------------------------------------------------------------------------------- /public/assets/menu/power.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/power.png -------------------------------------------------------------------------------- /public/assets/menu/qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/qrcode.png -------------------------------------------------------------------------------- /public/assets/menu/recent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/recent.png -------------------------------------------------------------------------------- /public/assets/menu/release.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/release.png -------------------------------------------------------------------------------- /public/assets/menu/reload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/reload.png -------------------------------------------------------------------------------- /public/assets/menu/reset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/reset.png -------------------------------------------------------------------------------- /public/assets/menu/restart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/restart.png -------------------------------------------------------------------------------- /public/assets/menu/screens.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/screens.png -------------------------------------------------------------------------------- /public/assets/menu/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/search.png -------------------------------------------------------------------------------- /public/assets/menu/success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/success.png -------------------------------------------------------------------------------- /public/assets/menu/trash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/trash.png -------------------------------------------------------------------------------- /public/assets/menu/tw-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/tw-dark.png -------------------------------------------------------------------------------- /public/assets/menu/update.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/update.png -------------------------------------------------------------------------------- /public/assets/menu/vscode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/vscode.png -------------------------------------------------------------------------------- /public/assets/menu/warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/warning.png -------------------------------------------------------------------------------- /public/assets/menu/web_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/web_app.png -------------------------------------------------------------------------------- /public/assets/menu/windows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/windows.png -------------------------------------------------------------------------------- /public/assets/menu/zoomIn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/zoomIn.png -------------------------------------------------------------------------------- /public/assets/menu/zoomOut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/zoomOut.png -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | # registry=https://registry.npmmirror.com 2 | electron_mirror=https://npmmirror.com/mirrors/electron/ ## 解决超市下载失败问题 -------------------------------------------------------------------------------- /public/assets/menu/devtools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/devtools.png -------------------------------------------------------------------------------- /public/assets/menu/expandAll.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/expandAll.png -------------------------------------------------------------------------------- /public/assets/menu/markdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/markdown.png -------------------------------------------------------------------------------- /public/assets/menu/minimize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/minimize.png -------------------------------------------------------------------------------- /public/assets/menu/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/settings.png -------------------------------------------------------------------------------- /public/assets/menu/tw-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/tw-light.png -------------------------------------------------------------------------------- /public/assets/menu/username.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/username.png -------------------------------------------------------------------------------- /public/assets/menu/winState.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/winState.png -------------------------------------------------------------------------------- /public/assets/tray-icon-dev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/tray-icon-dev.png -------------------------------------------------------------------------------- /public/assets/menu/downloading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/downloading.png -------------------------------------------------------------------------------- /public/assets/menu/findInPage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/findInPage.png -------------------------------------------------------------------------------- /public/assets/menu/folder-close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/folder-close.png -------------------------------------------------------------------------------- /public/assets/menu/folder-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/folder-linux.png -------------------------------------------------------------------------------- /public/assets/menu/folder-macOs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/folder-macOs.png -------------------------------------------------------------------------------- /public/assets/menu/folder-wiki.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/folder-wiki.png -------------------------------------------------------------------------------- /public/assets/menu/gitHub_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/gitHub_dark.png -------------------------------------------------------------------------------- /public/assets/menu/searchGoogle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/searchGoogle.png -------------------------------------------------------------------------------- /resources/pngquant/pngquant-macOs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/resources/pngquant/pngquant-macOs -------------------------------------------------------------------------------- /tiddlers/README.md.meta: -------------------------------------------------------------------------------- 1 | created: 20250416135711185 2 | modified: 20250416135711185 3 | title: README.md 4 | type: text/x-markdown -------------------------------------------------------------------------------- /public/assets/menu/expandAll_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/expandAll_dark.png -------------------------------------------------------------------------------- /public/assets/menu/featureSearch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/featureSearch.png -------------------------------------------------------------------------------- /public/assets/menu/folder-opened.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/folder-opened.png -------------------------------------------------------------------------------- /public/assets/menu/folder-windows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/folder-windows.png -------------------------------------------------------------------------------- /public/assets/menu/chrome-web-store.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/chrome-web-store.png -------------------------------------------------------------------------------- /public/assets/menu/findInPage_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/findInPage_dark.png -------------------------------------------------------------------------------- /public/assets/menu/folder-wiki1-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/folder-wiki1-dark.png -------------------------------------------------------------------------------- /resources/pngquant/pngquant-windows.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/resources/pngquant/pngquant-windows.exe -------------------------------------------------------------------------------- /public/assets/menu/folder-wiki1-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/folder-wiki1-light.png -------------------------------------------------------------------------------- /public/assets/menu/new-folder-template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeyoews/tiddlywiki-app/HEAD/public/assets/menu/new-folder-template.png -------------------------------------------------------------------------------- /tiddlers/README.zh-CN.md.meta: -------------------------------------------------------------------------------- 1 | created: 20250416135620146 2 | modified: 20250416135621362 3 | title: README.zh-CN.md 4 | type: text/x-markdown -------------------------------------------------------------------------------- /__config.yml: -------------------------------------------------------------------------------- 1 | remote_theme: pages-themes/cayman@v0.2.0 2 | plugins: 3 | - jekyll-remote-theme # add this line to the plugins list if you already have one 4 | -------------------------------------------------------------------------------- /src/utils/generateRandomPort.ts: -------------------------------------------------------------------------------- 1 | export function generateRandomPrivatePort() { 2 | return Math.floor(Math.random() * (65535 - 49152 + 1)) + 49152; 3 | } 4 | -------------------------------------------------------------------------------- /assets/menu/open.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tiddlers/$__SiteTitle.tid: -------------------------------------------------------------------------------- 1 | created: 20250416135506822 2 | modified: 20250416135827128 3 | title: $:/SiteTitle 4 | type: text/vnd.tiddlywiki 5 | 6 | TiddlyWiki App 🌟 -------------------------------------------------------------------------------- /assets/menu/plus2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tiddlers/$__themes_tiddlywiki_vanilla_options_sidebarlayout.tid: -------------------------------------------------------------------------------- 1 | title: $:/themes/tiddlywiki/vanilla/options/sidebarlayout 2 | type: text/vnd.tiddlywiki 3 | 4 | fluid-fixed -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | /output/ 3 | src/wiki/tiddlers/ 4 | builder*.yaml 5 | /release 6 | /wiki 7 | /dist/ 8 | test-results/ 9 | /.VSCodeCounter 10 | /.DS_Store -------------------------------------------------------------------------------- /assets/menu/release.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/menu/host.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/menu/update.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /tiddlers/$__DefaultTiddlers.tid: -------------------------------------------------------------------------------- 1 | created: 20250416135355433 2 | modified: 20250416141033117 3 | title: $:/DefaultTiddlers 4 | type: text/vnd.tiddlywiki 5 | 6 | [[README.md]] README.zh-CN.md -------------------------------------------------------------------------------- /assets/menu/File.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/menu/winState.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /assets/menu/folder.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "i18n-ally.localesPaths": [ 4 | "scripts/locales", 5 | "src/i18n", 6 | "src/locales", 7 | "release/4.9.4/win-unpacked/locales" 8 | ], 9 | } -------------------------------------------------------------------------------- /assets/menu/plus.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tiddlers/$__SiteSubtitle.tid: -------------------------------------------------------------------------------- 1 | created: 20250416135829441 2 | modified: 20250416135834032 3 | title: $:/SiteSubtitle 4 | type: text/vnd.tiddlywiki 5 | 6 | A TiddlyWiki desktop application that provides a smoother desktop experience. -------------------------------------------------------------------------------- /assets/menu/error.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/menu/import.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/menu/power.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tiddlers/$__config_FileSystemPaths.tid: -------------------------------------------------------------------------------- 1 | created: 20250416133658298 2 | creator: oeyoews 3 | modified: 20250416133658298 4 | modifier: oeyoews 5 | title: $:/config/FileSystemPaths 6 | type: text/vnd.tiddlywiki 7 | 8 | [tag[private]addprefix[subwiki/]] -------------------------------------------------------------------------------- /assets/menu/windows.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/menu/about.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | -------------------------------------------------------------------------------- /assets/menu/folder-opened.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/menu/conf.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | -------------------------------------------------------------------------------- /assets/menu/image.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/utils/updater.ts: -------------------------------------------------------------------------------- 1 | export const updaterConfig = { 2 | provider: 'github', 3 | owner: 'oeyoews', 4 | repo: 'tiddlywiki-app', 5 | }; 6 | 7 | export const updaterDevConfig = { 8 | provider: 'generic', 9 | url: 'http://localhost:8080', 10 | }; 11 | -------------------------------------------------------------------------------- /assets/menu/username.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | -------------------------------------------------------------------------------- /assets/menu/new-folder-template.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | -------------------------------------------------------------------------------- /assets/menu/console.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/utils/generateId.ts: -------------------------------------------------------------------------------- 1 | export function generateId(str: string) { 2 | // 使用简单的哈希算法来生成短 id 3 | return str 4 | .split('') 5 | .reduce((hash, char) => { 6 | return (hash << 5) - hash + char.charCodeAt(0); // 类似于 JavaScript 内置的哈希函数 7 | }, 0) 8 | .toString(16); 9 | } 10 | -------------------------------------------------------------------------------- /assets/menu/star.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/menu/link.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/menu/settings.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/menu/devtools.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/menu/recent.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "resolveJsonModule": true, 7 | "allowSyntheticDefaultImports": true, 8 | "types": ["tw5-typed", "node"] 9 | }, 10 | "include": ["vite.config.ts", "package.json", "electron"] 11 | } 12 | -------------------------------------------------------------------------------- /manifests/o/oeyoews/tiddlywiki-app/4.7.1/oeyoews.tiddlywiki-app.yaml: -------------------------------------------------------------------------------- 1 | # Created using wingetcreate 1.9.4.0 2 | # yaml-language-server: $schema=https://aka.ms/winget-manifest.version.1.9.0.schema.json 3 | 4 | PackageIdentifier: oeyoews.tiddlywiki-app 5 | PackageVersion: 4.7.1 6 | DefaultLocale: en-US 7 | ManifestType: version 8 | ManifestVersion: 1.9.0 9 | -------------------------------------------------------------------------------- /assets/menu/folder-close.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | -------------------------------------------------------------------------------- /assets/menu/markdown.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | -------------------------------------------------------------------------------- /assets/menu/html.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | -------------------------------------------------------------------------------- /assets/menu/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | -------------------------------------------------------------------------------- /assets/menu/help.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/menu/log.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/menu/folder-windows.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/menu/web_app.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/menu/downloading.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | -------------------------------------------------------------------------------- /src/utils/getFileSize.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | 3 | export function getFileSizeInMB(filePath: string): string | null { 4 | try { 5 | const stats = fs.statSync(filePath); 6 | const fileSizeInMB: number = stats.size / (1024 * 1024); // 转换为MB 7 | return `${fileSizeInMB.toFixed(2)}M`; // 保留两位小数 8 | } catch (error) { 9 | console.error((error as Error).message); 10 | return null; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /scripts/locales/settings.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | languageChanged: { 3 | 'en-US': 'Language Changed', 4 | 'zh-CN': '语言已更改', 5 | }, 6 | settingChanged: { 7 | 'en-US': 'setting has changed', 8 | 'zh-CN': '设置已更改', 9 | }, 10 | restartTips: { 11 | 'en-US': 12 | 'Settings have been changed, some changes may require a restart to take effect', 13 | 'zh-CN': '设置已更改,可能需要重启应用后生效', 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /assets/menu/format.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | -------------------------------------------------------------------------------- /assets/menu/module.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | -------------------------------------------------------------------------------- /assets/menu/bug.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Debug Main Process", 6 | "type": "node", 7 | "request": "launch", 8 | "cwd": "${workspaceFolder}", 9 | "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron", 10 | "windows": { 11 | "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd" 12 | }, 13 | "args": ["."] 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /src/utils/checkEmptyDir.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import { log } from '@/utils/logger'; 3 | 4 | /** 检查目录是否为空 */ 5 | export function isEmptyDirectory(directoryPath: string) { 6 | try { 7 | if (!fs.existsSync(directoryPath)) { 8 | return true; 9 | } 10 | const files = fs.readdirSync(directoryPath); 11 | return files.length === 0; 12 | } catch (err) { 13 | log.error('check dir error:', directoryPath, err); 14 | return false; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /scripts/locales/tray.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | tooltip: { 3 | 'en-US': 'TiddlyWiki App', 4 | 'zh-CN': 'TiddlyWiki App', 5 | }, 6 | showWindow: { 7 | 'en-US': 'Show Window', 8 | 'zh-CN': '显示主窗口', 9 | }, 10 | openInBrowser: { 11 | 'en-US': 'Open in Browser', 12 | 'zh-CN': '在浏览器中打开', 13 | }, 14 | about: { 15 | 'en-US': 'About', 16 | 'zh-CN': '关于', 17 | }, 18 | exit: { 19 | 'en-US': 'Exit', 20 | 'zh-CN': '退出', 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /assets/menu/trash.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/workflows/page.yml: -------------------------------------------------------------------------------- 1 | name: Deploy TiddlyWiki to GitHub Pages 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | workflow_dispatch: 7 | 8 | permissions: 9 | pages: write 10 | id-token: write 11 | contents: write 12 | 13 | concurrency: 14 | group: pages 15 | cancel-in-progress: true 16 | 17 | jobs: 18 | release-and-page: 19 | runs-on: ubuntu-latest 20 | name: Publish 21 | steps: 22 | - uses: oeyoews/tiddlywiki-publish@main 23 | with: 24 | version: master 25 | -------------------------------------------------------------------------------- /@types/i18next.d.ts: -------------------------------------------------------------------------------- 1 | import 'i18next'; 2 | 3 | import { defaultNS } from '@/i18n'; 4 | 5 | import enTranslation from '../src/locales/en-US/translation.json'; 6 | import zhTranslation from '../src/locales/zh-CN/translation.json'; 7 | 8 | const resources = { 9 | en: { translation: enTranslation }, 10 | zh: { translation: zhTranslation }, 11 | }; 12 | 13 | declare module 'i18next' { 14 | interface CustomTypeOptions { 15 | defaultNS: typeof defaultNS; 16 | resources: (typeof resources)['en']; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /scripts/locales/log.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | startBuild: { 3 | 'en-US': 'Start building', 4 | 'zh-CN': '开始构建', 5 | }, 6 | startInit: { 7 | 'en-US': 'Start initialization', 8 | 'zh-CN': '开始初始化', 9 | }, 10 | finishInit: { 11 | 'en-US': 'Initialization complete', 12 | 'zh-CN': '初始化完成', 13 | }, 14 | startImport: { 15 | 'en-US': 'Start importing single-file Wiki', 16 | 'zh-CN': '开始导入单文件 Wiki', 17 | }, 18 | sameFolder: { 19 | 'en-US': 'Already the current open Wiki folder', 20 | 'zh-CN': '已经是当前打开的 Wiki 文件夹', 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /src/utils/getHost.ts: -------------------------------------------------------------------------------- 1 | import os from 'os'; 2 | import { NetworkInterfaceInfo } from 'os'; 3 | 4 | export const getAllLocalIPv4Addresses = (): string[] => { 5 | const interfaces = os.networkInterfaces(); 6 | 7 | return Object.values(interfaces) 8 | .flatMap((ifaceList: NetworkInterfaceInfo[] | undefined) => 9 | ifaceList ? ifaceList : [] 10 | ) 11 | .filter( 12 | (iface: NetworkInterfaceInfo) => 13 | iface.family === 'IPv4' && !iface.internal && !!iface.address 14 | ) 15 | .map((iface: NetworkInterfaceInfo) => iface.address!); 16 | }; 17 | -------------------------------------------------------------------------------- /@types/electron-extra.d.ts: -------------------------------------------------------------------------------- 1 | import { electronAPI as IElectronAPI } from '@/preload'; 2 | 3 | // 获取 electronAPI 的类型 4 | type IElectronAPI = typeof electronAPI; // 这里推断出 electronAPI 的类型 5 | export {}; 6 | 7 | declare global { 8 | namespace Electron { 9 | interface App { 10 | isQuitting?: boolean; 11 | } 12 | } 13 | interface Window { 14 | electronAPI: typeof electronAPI; // 使用 typeof 推断 electronAPI 的类型 15 | // $tw: any; 16 | } 17 | // export const $tw: ITiddlyWiki; 18 | export const electronAPI: typeof IElectronAPI; // 使用 typeof 推断 electronAPI 的类型 19 | } 20 | -------------------------------------------------------------------------------- /manifests/o/oeyoews/tiddlywiki-app/4.7.1/oeyoews.tiddlywiki-app.locale.en-US.yaml: -------------------------------------------------------------------------------- 1 | # Created using wingetcreate 1.9.4.0 2 | # yaml-language-server: $schema=https://aka.ms/winget-manifest.defaultLocale.1.9.0.schema.json 3 | 4 | PackageIdentifier: oeyoews.tiddlywiki-app 5 | PackageVersion: 4.7.1 6 | PackageLocale: en-US 7 | Publisher: oeyoews 8 | PackageName: tiddlywiki-app 9 | License: MIT License 10 | Copyright: Copyright © 2025 oeyoews 11 | ShortDescription: Your next TiddlyWiki app offers a smoother TiddlyWiki experience. 12 | ManifestType: defaultLocale 13 | ManifestVersion: 1.9.0 14 | -------------------------------------------------------------------------------- /src/utils/tiddlywiki.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Basic client-server edition", 3 | "plugins": [ 4 | "tiddlywiki/tiddlyweb", 5 | "tiddlywiki/filesystem", 6 | "tiddlywiki/highlight" 7 | ], 8 | "themes": [ 9 | "tiddlywiki/vanilla" 10 | ], 11 | "build": { 12 | "index": [ 13 | "--render", 14 | "$:/plugins/tiddlywiki/tiddlyweb/save/offline", 15 | "index.html", 16 | "text/plain", 17 | "", 18 | "publishFilter", 19 | "-[tag[private]] -[is[draft]]" 20 | ] 21 | }, 22 | "languages": [], 23 | "config": { 24 | "retain-original-tiddler-path": true 25 | } 26 | } -------------------------------------------------------------------------------- /manifests/o/oeyoews/tiddlywiki-app/4.7.1/oeyoews.tiddlywiki-app.installer.yaml: -------------------------------------------------------------------------------- 1 | # Created using wingetcreate 1.9.4.0 2 | # yaml-language-server: $schema=https://aka.ms/winget-manifest.installer.1.9.0.schema.json 3 | 4 | PackageIdentifier: oeyoews.tiddlywiki-app 5 | PackageVersion: 4.7.1 6 | InstallerType: nullsoft 7 | Installers: 8 | - Architecture: x64 9 | InstallerUrl: https://github.com/oeyoews/tiddlywiki-app/releases/download/v4.7.1/tiddlywiki-app-4.7.1.exe 10 | InstallerSha256: 21F38106355A2AD3D1EE183F065556B5F8C93D901A9D9E22E2C717F9ACF96F3E 11 | ManifestType: installer 12 | ManifestVersion: 1.9.0 13 | -------------------------------------------------------------------------------- /assets/menu/log2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/menu/link2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/menu/reload.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /aur/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | repo="oeyoews/tiddlywiki-app" 4 | api_url="https://api.github.com/repos/$repo/releases/latest" 5 | 6 | # 获取最新的版本号,并去掉开头的 'v' 7 | latest_version=$(curl -s "$api_url" | grep '"tag_name":' | sed 's/.*"tag_name": "\(v.*\)".*/\1/' | sed 's/^v//') 8 | 9 | # 检查是否成功获取版本号 10 | if [ -z "$latest_version" ]; then 11 | echo "获取最新版本失败" 12 | exit 1 13 | fi 14 | 15 | # 打印最新版本号 16 | echo "最新版本号: $latest_version" 17 | 18 | # 更新 PKGBUILD 文件中的 pkgver 19 | sed -i "s/pkgver=[^ ]*/pkgver=$latest_version/" PKGBUILD 20 | 21 | echo "PKGBUILD 已更新为最新版本 $latest_version" 22 | 23 | echo "begin build tiddlywiki-app package" 24 | 25 | makepkg -si ## please make sure that fakeroot is installed -------------------------------------------------------------------------------- /scripts/minify-tw.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | const editionsPath = path.join( 5 | __dirname, 6 | '../node_modules/tiddlywiki/editions' 7 | ); 8 | 9 | function deleteEditionsExceptServer() { 10 | if (fs.existsSync(editionsPath)) { 11 | fs.readdirSync(editionsPath).forEach((dir) => { 12 | const dirPath = path.join(editionsPath, dir); 13 | if (fs.statSync(dirPath).isDirectory() && dir !== 'server') { 14 | fs.rmSync(dirPath, { recursive: true, force: true }); 15 | console.log(`Deleted: ${dirPath}`); 16 | } 17 | }); 18 | } else { 19 | console.log(`Path not found: ${editionsPath}`); 20 | } 21 | } 22 | 23 | deleteEditionsExceptServer(); 24 | -------------------------------------------------------------------------------- /src/utils/wikiTemplates.ts: -------------------------------------------------------------------------------- 1 | export const wikiTemplates = { 2 | default: 'server', 3 | '-': '', 4 | 'tiddlywiki starter kit': 'https://neotw.vercel.app/offline.html', 5 | notebook: 'https://oeyoews.github.io/tiddlywiki-templates/notebook.html', 6 | xp: 'https://keatonlao.github.io/tiddlywiki-xp/index.html', 7 | mptw5: 'https://mptw5.tiddlyhost.com', 8 | 'delphes-notes': 'https://delphes-notes-light-edition.tiddlyhost.com', 9 | grok: 'https://grok-tiddlywiki-official.tiddlyhost.com/', 10 | 'tiddly-template': 11 | 'https://oeyoews.github.io/tiddlywiki-templates/tiddly-template.html', 12 | '--': '', 13 | help: '', 14 | }; 15 | 16 | export type IWikiTemplate = Omit; 17 | -------------------------------------------------------------------------------- /src/i18n/index.ts: -------------------------------------------------------------------------------- 1 | import i18next from 'i18next'; 2 | 3 | const defaultNS = 'translation'; 4 | 5 | const initI18n = async (config: any) => { 6 | const enTranslation = await import('@/locales/en-US/translation.json'); 7 | const zhTranslation = await import('@/locales/zh-CN/translation.json'); 8 | 9 | const resources = { 10 | en: { translation: enTranslation }, 11 | zh: { translation: zhTranslation }, 12 | }; 13 | 14 | await i18next.init({ 15 | resources, 16 | lng: config.get('language') || 'en-US', 17 | fallbackLng: 'en-US', 18 | interpolation: { 19 | escapeValue: false, 20 | }, 21 | }); 22 | 23 | return i18next; 24 | }; 25 | // const t = i18next.t; 26 | 27 | export { initI18n, i18next, defaultNS }; 28 | -------------------------------------------------------------------------------- /scripts/locales/app.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: { 3 | 'en-US': 'TiddlyWiki App', 4 | 'zh-CN': 'TiddlyWiki App', 5 | }, 6 | name: { 7 | 'en-US': 'TiddlyWiki App', 8 | 'zh-CN': 'TiddlyWiki 应用', 9 | }, 10 | about: { 11 | 'en-US': 'About Wiki', 12 | 'zh-CN': '关于维基', 13 | }, 14 | version: { 15 | 'en-US': 'Version', 16 | 'zh-CN': '版本', 17 | }, 18 | configPath: { 19 | 'en-US': 'Config Path', 20 | 'zh-CN': '配置路径', 21 | }, 22 | currentWikiPath: { 23 | 'en-US': 'Current Wiki Path', 24 | 'zh-CN': '当前维基路径', 25 | }, 26 | runningPort: { 27 | 'en-US': 'Running Port', 28 | 'zh-CN': '当前端口', 29 | }, 30 | notRunning: { 31 | 'en-US': 'Not Running', 32 | 'zh-CN': '未运行', 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /src/utils/renderer/clipper.ts: -------------------------------------------------------------------------------- 1 | import { type ITiddlerFields } from 'tw5-typed'; 2 | 3 | export function importTiddlerFromBrowser(tiddler: ITiddlerFields) { 4 | const { title, ...fields } = tiddler; 5 | if (title) { 6 | let tiddlers = { 7 | tiddlers: {} as any, 8 | }; 9 | tiddlers.tiddlers[title] = tiddler; 10 | const importedTitle = '$:/webImported'; 11 | $tw.wiki.addTiddler({ 12 | title: importedTitle, 13 | ['popup-' + title]: 'yes', // 默认展开 14 | 'plugin-type': 'import', 15 | type: 'application/json', 16 | text: JSON.stringify(tiddlers), 17 | status: 'pending', 18 | }); 19 | new $tw.Story().navigateTiddler(importedTitle); 20 | } else { 21 | console.warn('no title'); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tiddlywiki.info: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Basic client-server edition", 3 | "plugins": [ 4 | "tiddlywiki/tiddlyweb", 5 | "tiddlywiki/filesystem", 6 | "tiddlywiki/highlight", 7 | "tiddlywiki/markdown" 8 | ], 9 | "themes": [ 10 | "tiddlywiki/vanilla" 11 | ], 12 | "build": { 13 | "index": [ 14 | "--render", 15 | "$:/plugins/tiddlywiki/tiddlyweb/save/offline", 16 | "index.html", 17 | "text/plain", 18 | "", 19 | "publishFilter", 20 | "-[tag[private]] -[is[draft]]" 21 | ] 22 | }, 23 | "languages": [ 24 | "zh-Hans" 25 | ], 26 | "config": { 27 | "retain-original-tiddler-path": true 28 | } 29 | } -------------------------------------------------------------------------------- /assets/menu/config.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/menu/folder-macOs.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/utils/menubar/edit.ts: -------------------------------------------------------------------------------- 1 | import { type MenuItemConstructorOptions } from 'electron'; 2 | import { t } from 'i18next'; 3 | 4 | export const editMenu = (): MenuItemConstructorOptions => ({ 5 | label: t('menu.edit'), 6 | submenu: [ 7 | { role: 'undo', label: t('menu.undo') }, 8 | { role: 'redo', label: t('menu.redo') }, 9 | { type: 'separator' }, 10 | { role: 'cut', label: t('menu.cut') }, 11 | { role: 'copy', label: t('menu.copy') }, 12 | { role: 'paste', label: t('menu.paste') }, 13 | { type: 'separator' }, 14 | { 15 | label: 'Speech', 16 | submenu: [{ role: 'startSpeaking' }, { role: 'stopSpeaking' }], 17 | }, 18 | { type: 'separator' }, 19 | { role: 'delete', label: t('menu.delete') }, 20 | { role: 'selectAll', label: t('menu.selectAll') }, 21 | ], 22 | }); 23 | -------------------------------------------------------------------------------- /assets/menu/macOs.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | -------------------------------------------------------------------------------- /assets/menu/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /assets/menu/vscode.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/utils/renderer/setSubwiki.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | export function setSubwiki() { 3 | // support subwiki 4 | const twFilePathConfigTiddler = '$:/config/FileSystemPaths'; 5 | const subwikiText = '[tag[private]addprefix[subwiki/]]'; 6 | if ( 7 | !$tw.wiki.tiddlerExists(twFilePathConfigTiddler) || 8 | !$tw.wiki.getTiddlerText(twFilePathConfigTiddler) 9 | ) { 10 | // 直接写入 11 | $tw.wiki.setText(twFilePathConfigTiddler, 'text', null, subwikiText); 12 | console.log(twFilePathConfigTiddler, 'not exist'); 13 | } else { 14 | let oldText = $tw.wiki.getTiddlerText(twFilePathConfigTiddler); 15 | if (!oldText.includes(subwikiText)) { 16 | oldText = `${oldText}\n${subwikiText}`; 17 | console.log(twFilePathConfigTiddler, 'updated'); 18 | // 更新tiddler 19 | $tw.wiki.setText(twFilePathConfigTiddler, 'text', null, oldText); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/electron.test.ts: -------------------------------------------------------------------------------- 1 | // @see: https://playwright.net.cn/docs/api/class-electron 2 | import { _electron } from 'playwright'; 3 | import { test, expect } from '@playwright/test'; 4 | 5 | test('启动 Electron 应用并检查标题', async () => { 6 | const electronApp = await _electron.launch({ args: ['.'] }); 7 | 8 | const window = await electronApp.firstWindow(); 9 | const title = await window.title(); 10 | 11 | console.log('应用窗口标题:', title); 12 | expect(title).toContain('TiddlyWiki5'); 13 | 14 | await electronApp.close(); 15 | }); 16 | 17 | test('APP 是否打包', async () => { 18 | const electronApp = await _electron.launch({ args: ['.'] }); 19 | const isPackaged = await electronApp.evaluate(async ({ app }) => { 20 | return app.isPackaged; 21 | }); 22 | console.log('是否打包:', isPackaged ? 'Yes' : 'NO'); // false(因为我们处在开发环境) 23 | expect(isPackaged).toBe(false); 24 | // 关闭应用程序 25 | await electronApp.close(); 26 | }); 27 | -------------------------------------------------------------------------------- /aur/PKGBUILD: -------------------------------------------------------------------------------- 1 | # Maintainer: oeyoews 2 | pkgname=tiddlywiki-app 3 | pkgver=3.8.4 4 | pkgrel=1 5 | pkgdesc="你的最后一个 TiddlyWiki 桌面应用, 提供更加丝滑的TiddlyWiki使用体验" 6 | arch=('x86_64' 'aarch64') 7 | license=('MIT') 8 | url="https://oeyoews.github.io/tiddlywiki-app/" 9 | _ghurl="https://github.com/oeyoews/tiddlywiki-app" 10 | 11 | # 根据架构选择正确的 DEB 包 12 | case "$CARCH" in 13 | x86_64) 14 | deb_arch="amd64" 15 | ;; 16 | aarch64) 17 | deb_arch="arm64" 18 | ;; 19 | *) 20 | echo "Unsupported architecture: $CARCH" 21 | exit 1 22 | ;; 23 | esac 24 | 25 | source=("${_ghurl}/releases/download/v${pkgver}/${pkgname}-${pkgver}-${deb_arch}.deb") 26 | md5sums=('SKIP') 27 | 28 | package() { 29 | bsdtar -xf data.tar.xz -C "$pkgdir/" 30 | 31 | # 确保目标目录存在 32 | install -d "$pkgdir/usr/bin" 33 | 34 | # 创建一个符号链接到可执行文件 35 | ln -s "/opt/tiddlywiki-app/tiddlywiki-app" "$pkgdir/usr/bin/tiddlywiki-app" 36 | } 37 | -------------------------------------------------------------------------------- /assets/menu/searchGoogle.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/menu/gitHub.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/utils/getPlatform.ts: -------------------------------------------------------------------------------- 1 | const platform = process.platform; 2 | 3 | export function getPlatform() { 4 | // @ts-ignore 5 | const osTypes: Record = { 6 | win32: 'windows', 7 | darwin: 'macOs', 8 | linux: 'linux', 9 | // cygwin: 'windows', 10 | // aix: 'linux', 11 | // freebsd: 'linux', 12 | // openbsd: 'linux', 13 | // android: 'linux', 14 | // haiku: 'linux', 15 | // sunos: 'linux', 16 | // netbsd: 'linux', 17 | }; 18 | return osTypes[platform] || osTypes.win32; 19 | } 20 | 21 | function getOS() { 22 | const userAgent = navigator.userAgent.toLowerCase(); 23 | if (userAgent.includes('win')) return 'Windows'; 24 | if (userAgent.includes('mac')) return 'MacOS'; 25 | if (userAgent.includes('linux')) return 'Linux'; 26 | if ( 27 | userAgent.includes('iphone') || 28 | userAgent.includes('ipad') || 29 | userAgent.includes('ipod') 30 | ) 31 | return 'iOS'; 32 | if (userAgent.includes('android')) return 'Android'; 33 | return 'Unknown'; 34 | } 35 | -------------------------------------------------------------------------------- /assets/menu/gitHub_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | -------------------------------------------------------------------------------- /src/utils/config.js: -------------------------------------------------------------------------------- 1 | // 会导致打包出现两遍 node:xxx 2 | // import { Conf, type ConfOptions } from 'electron-conf/main'; 3 | // const { Conf: Config } = require('electron-conf'); 4 | const { Conf } = require('electron-conf'); 5 | 6 | import { app } from 'electron'; 7 | import path from 'path'; 8 | 9 | // NOTE: C:/program files 需要提权 10 | export const DEFAULT_WIKI_DIR = path.join(app.getPath('desktop'), 'wiki'); // use app.getPath('desktop') 11 | // test 12 | // const DEFAULT_WIKI_DIR = path.resolve('wiki'); // use app.getPath('desktop') 13 | // C:\\Program Files 14 | 15 | const options = { 16 | defaults: { 17 | winState: true, // 默认开启, 记录上次窗口位置 18 | // betaChannel: false, 19 | defaultPort: 9090, 20 | username: null, 21 | lan: false, 22 | icon: false, 23 | wikiPath: DEFAULT_WIKI_DIR, 24 | language: null, 25 | markdown: false, 26 | autocorrect: false, 27 | 'lang-CN': false, 28 | recentWikis: [], 29 | github: { 30 | token: null, 31 | }, 32 | }, 33 | }; 34 | 35 | export const config = new Conf(options); 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2024 oeyoews 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included 11 | in all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 14 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 15 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 16 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 18 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 19 | OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /src/utils/tw-dialog.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ipcMain, 3 | type BrowserWindow, 4 | dialog, 5 | type MessageBoxSyncOptions, 6 | } from 'electron'; 7 | import { getAppIcon } from './icon'; 8 | import { t } from 'i18next'; 9 | 10 | interface ITWDialog { 11 | type: DialogType; 12 | message: string; 13 | } 14 | 15 | /** handle tw dialog to fix tw vanilla alert fn cause input cannot focus bug */ 16 | export const twDialog = (win: BrowserWindow) => { 17 | ipcMain.on('custom-dialog', (event: any, opt: ITWDialog) => { 18 | const { type, message } = opt; 19 | const options: MessageBoxSyncOptions = { 20 | type: type === 'confirm' ? 'question' : 'info', 21 | icon: getAppIcon(256, 'tw-light'), 22 | buttons: 23 | type === 'confirm' 24 | ? [t('dialog.cancel'), t('dialog.confirm')] 25 | : [t('dialog.confirm')], 26 | defaultId: type === 'confirm' ? 1 : 0, 27 | title: t('dialog.confirm'), 28 | message, 29 | }; 30 | const result = dialog.showMessageBoxSync(win, options as any); 31 | event.returnValue = type === 'confirm' ? result === 1 : undefined; 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /patches/tw5-typed.patch: -------------------------------------------------------------------------------- 1 | diff --git a/package.json b/package.json 2 | index 9d0888b8a2ba9287f15ccc4fa0770c7208b6c032..c3a4a1317db5c32fe9740e8a21f05468a9a5487e 100644 3 | --- a/package.json 4 | +++ b/package.json 5 | @@ -23,6 +23,13 @@ 6 | "url": "git+https://github.com/tiddly-gittly/tw5-typed.git" 7 | }, 8 | "types": "src/index.d.ts", 9 | + "exports": { 10 | + ".": { 11 | + "import": "./src/index.d.ts", 12 | + "require": "./src/index.d.ts", 13 | + "types": "./src/index.d.ts" 14 | + } 15 | + }, 16 | "files": [ 17 | "src/" 18 | ], 19 | diff --git a/src/modules/server/index.d.ts b/src/modules/server/index.d.ts 20 | index 44e6a0cf64f8aba5436dd15488fdc0fb5d55ca2f..a93b01f0c3e5ba241bcfc9a7be0aa7408d39cac4 100644 21 | --- a/src/modules/server/index.d.ts 22 | +++ b/src/modules/server/index.d.ts 23 | @@ -98,7 +98,7 @@ declare module 'tiddlywiki' { 24 | authorizationType: 'readers' | 'writers', 25 | username?: string | undefined, 26 | ): boolean; 27 | - close(): void; 28 | + close(callback): http.Server; 29 | } 30 | 31 | export interface ServerEndpointContext { 32 | -------------------------------------------------------------------------------- /src/utils/wiki/constant.ts: -------------------------------------------------------------------------------- 1 | import { config } from '../config'; 2 | 3 | export const wikiInitArgs = (path: string) => [path, '--init', 'server']; 4 | const host = 'host=0.0.0.0'; 5 | 6 | export const wikiStartupArgs = ( 7 | path: string, 8 | port: number | string 9 | // lan: boolean = true 10 | ) => [ 11 | path, 12 | '--listen', 13 | `port=${port}`, 14 | `anon-username=${config.get('username') || ''}`, 15 | ...(!!config.get('lan') ? [host] : []), 16 | // 'root-tiddler=$:/core/save/all-external-js', 17 | // 'use-browser-cache=yes', 18 | ]; 19 | 20 | export const wikiBuildArgs = (path: string, password?: string) => { 21 | let args = [path, '--build', 'index']; 22 | if (password) { 23 | args = [path, '--password', password, ...buildIndexHTMLArgs]; 24 | } 25 | return args; 26 | }; 27 | 28 | export const buildIndexHTMLArgs = [ 29 | '--render', 30 | '$:/plugins/tiddlywiki/tiddlyweb/save/offline', 31 | 'index.html', 32 | 'text/plain', 33 | '', 34 | 'publishFilter', 35 | '-[tag[private]] -[is[draft]]', 36 | ]; 37 | 38 | export const defaultPlugins = [ 39 | 'tiddlywiki/tiddlyweb', 40 | 'tiddlywiki/filesystem', 41 | 'tiddlywiki/highlight', 42 | ]; 43 | -------------------------------------------------------------------------------- /src/utils/trackWindowState.ts: -------------------------------------------------------------------------------- 1 | import { BrowserWindow } from 'electron'; 2 | import { config } from './config'; 3 | 4 | /** 5 | * 监听并动态更新窗口状态 6 | * @param {BrowserWindow} win - Electron 窗口实例 7 | * @param {Function} callback - 状态更新回调函数 8 | */ 9 | export function trackWindowState(win: BrowserWindow) { 10 | if (!win || !(win instanceof BrowserWindow)) { 11 | throw new Error('Invalid BrowserWindow instance'); 12 | } 13 | 14 | const updateState = () => { 15 | const bounds = win.getBounds(); 16 | const state = { 17 | width: bounds.width, 18 | height: bounds.height, 19 | x: bounds.x, 20 | y: bounds.y, 21 | isMaximized: win.isMaximized(), 22 | isFullScreen: win.isFullScreen(), 23 | // isMinimized: win.isMinimized(), 24 | }; 25 | config.set('window', state); 26 | }; 27 | 28 | // 监听窗口变化事件 29 | win.on('resize', () => updateState()); 30 | win.on('move', updateState); 31 | win.on('maximize', updateState); 32 | win.on('unmaximize', updateState); 33 | win.on('enter-full-screen', updateState); 34 | win.on('leave-full-screen', updateState); 35 | // win.on('minimize', updateState); 36 | // win.on('restore', updateState); 37 | } 38 | -------------------------------------------------------------------------------- /scripts/createSymbolink.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-extra'); 2 | const path = require('path'); 3 | 4 | const log = console.log; 5 | /** 6 | * 创建相对路径的软链接(支持文件) 7 | * @param targetPath 目标文件路径(将被软链接指向,使用相对路径) 8 | * @param symlinkPath 软链接路径 9 | */ 10 | async function createRelativeSymlink(targetPath, symlinkPath) { 11 | try { 12 | // 计算相对路径 13 | const relativeTarget = path.relative(path.dirname(symlinkPath), targetPath); 14 | 15 | // 确保目标文件的父目录存在 16 | const targetDir = path.dirname(targetPath); 17 | if (!(await fs.pathExists(targetDir))) { 18 | await fs.mkdirp(targetDir); 19 | } 20 | 21 | // 检查软链接是否已存在 22 | if (await fs.pathExists(symlinkPath)) { 23 | log(`Softlink ${symlinkPath} has exist, skip`); 24 | return; 25 | } 26 | 27 | // 创建软链接(相对路径) 28 | await fs.ensureSymlink(relativeTarget, symlinkPath, 'file'); 29 | log( 30 | `successfully create relative subwiki softlink: ${symlinkPath} -> ${relativeTarget}` 31 | ); 32 | } catch (error) { 33 | log(`failed to create relative softlink:`, error); 34 | throw error; 35 | } 36 | } 37 | 38 | createRelativeSymlink('./README.md', 'tiddlers/README.md'); 39 | createRelativeSymlink('./README.zh-CN.md', 'tiddlers/README.zh-CN.md'); 40 | -------------------------------------------------------------------------------- /src/utils/renderer/setOfficialLib.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | const libtag = '$:/tags/PluginLibrary' 4 | const options = { 5 | suppressTimestamp: true, 6 | } 7 | 8 | export function setOfficialLib() { 9 | // enable official plugin library 10 | // console.log($tw.version); 11 | const pluginLibraryUrl = `https://tiddlywiki.com/library/v${$tw.version}/index.html`; 12 | const officialLibraryTiddler = '$:/config/OfficialPluginLibrary'; 13 | if ($tw.wiki.getTiddler(officialLibraryTiddler)?.fields?.enabled === 'no') { 14 | $tw.wiki.setText(officialLibraryTiddler, 'url', null, pluginLibraryUrl, options); 15 | $tw.wiki.setText(officialLibraryTiddler, 'enabled', null, 'yes', options); 16 | } 17 | } 18 | 19 | // @ts-nocheck 20 | export function setCustomPluginLib() { 21 | const CM6LibraryTiddler = '$:/Library/Codemirror6'; 22 | const fields = $tw.wiki.getTiddler(CM6LibraryTiddler) 23 | if (fields?.enabled !== 'yes' || fields.version !== '1.0.0') { 24 | const pluginLibraryUrl = `https://oeyoews.github.io/tiddlywiki-codemirror6/library/index.html`; 25 | $tw.wiki.addTiddler({ 26 | title: CM6LibraryTiddler, 27 | tags: libtag, 28 | enabled: 'yes', 29 | caption: "CodeMirror6", 30 | url: pluginLibraryUrl, 31 | version: '1.0.0' 32 | }) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/utils/importWeb.ts: -------------------------------------------------------------------------------- 1 | import { TWProtocol } from '@/main'; 2 | import { BrowserWindow, dialog } from 'electron'; 3 | import { log } from './logger'; 4 | 5 | // second-instance or ready 6 | // tiddlywiki://?_source=web&title=999 7 | export function importWeb(win: BrowserWindow, argv: string[], _url?: string) { 8 | if (!win) { 9 | // dialog.showErrorBox('no win', 'no win'); 10 | log.info('Not Ready') 11 | return; 12 | } 13 | let url; 14 | // if (process.platform != 'darwin') { 15 | // Windows 下从 argv 中解析协议 URL 16 | if (argv) { 17 | url = argv.find((arg: string) => arg.startsWith(TWProtocol + '://')); 18 | } else if (_url) { 19 | url = _url; 20 | } 21 | if (url) { 22 | const info = new URL(url); 23 | const source = info.searchParams.get('_source'); 24 | if (source !== 'web') { 25 | log.info('not from web'); 26 | return; 27 | } 28 | // test: 考虑为空的情况 29 | const tags = info.searchParams.getAll('tags'); 30 | const { 31 | _source, 32 | tags: _tags, 33 | ...tiddler 34 | } = Object.fromEntries(info.searchParams.entries()); 35 | 36 | // 校验来源 37 | log.info('Begin import tiddler from', source); 38 | // 注意, 这里需要等待render.js 执行完毕 39 | setTimeout(() => { 40 | win.webContents.send('open-url', { ...tiddler, tags }); 41 | }, 100); 42 | } else { 43 | // log.info('no get valid url', url, _url); 44 | } 45 | // } 46 | } 47 | -------------------------------------------------------------------------------- /src/utils/renderer/markdown.ts: -------------------------------------------------------------------------------- 1 | export function importMarkdown(content: IMarkdownTiddler[]) { 2 | const importedTitle = '$:/markdownImported'; 3 | let tiddlers = { 4 | tiddlers: {}, 5 | }; 6 | content.forEach((content) => { 7 | // let renameTitle = null; 8 | // if ( 9 | // // $tw.wiki.filterTiddlers(`[[${content.title}]!is[missing]]`).length > 0 10 | // $tw.wiki.tiddlerExists(content.title) 11 | // ) { 12 | // renameTitle = window.prompt( 13 | // `${content.title} tiddler has exist, please rename this tiddler`, 14 | // content.title + '-' + Date.now() 15 | // ); 16 | // // 跳过此条目的导入 17 | // if (!renameTitle) { 18 | // return; 19 | // } 20 | // } 21 | // if (renameTitle) { 22 | // content.title = renameTitle; 23 | // } 24 | const tiddler = { 25 | tags: ['markdown'], 26 | // @ts-ignore 27 | title: content.title, 28 | ...content, 29 | type: 'text/markdown', // 放到最后面, 防止frontmatter 修改 30 | }; 31 | // @ts-ignore 32 | tiddlers.tiddlers[content.title] = tiddler; 33 | }); 34 | 35 | // $tw.wiki.deleteTiddler(importedTitle); 36 | $tw.wiki.addTiddler({ 37 | title: importedTitle, 38 | 'plugin-type': 'import', 39 | type: 'application/json', 40 | text: JSON.stringify(tiddlers), 41 | status: 'pending', 42 | }); 43 | new $tw.Story().navigateTiddler(importedTitle); 44 | } 45 | -------------------------------------------------------------------------------- /assets/menu/tw-light.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tiddlers/$__favicon.ico.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/utils/convertPathToVSCodeUri.ts: -------------------------------------------------------------------------------- 1 | import { log } from '@/utils/logger'; 2 | import { pathToFileURL } from 'url'; 3 | 4 | /** 5 | * deprecated use pathtourl 6 | * 将文件路径转换为 VS Code 的 vscode://file/ URI,兼容所有系统。 7 | * 8 | * @param filePath 文件路径,例如 "C:\\Users\\YourName\\Documents\\myFile.txt" (Windows) 或 "/Users/yourname/Documents/myFile.txt" (macOS/Linux)。 9 | * @returns 转换后的 vscode://file/ URI,例如 "vscode://file/C:/Users/YourName/Documents/myFile.txt" 或 "vscode://file//Users/yourname/Documents/myFile.txt"。 10 | */ 11 | export function _convertPathToVSCodeUri(filePath: string): string { 12 | if (!filePath) { 13 | return ''; 14 | } 15 | 16 | // 统一使用正斜杠作为路径分隔符 17 | const normalizedPath = filePath.replace(/\\/g, '/'); 18 | 19 | // 如果路径以盘符开头(例如 Windows 的 C:/),则保持原样 20 | // 如果路径以单个正斜杠开头(例如 macOS/Linux 的 /Users),则保持原样 21 | // 否则,添加一个额外的正斜杠以处理相对路径或不带盘符的 Windows 路径 22 | let vscodePath = normalizedPath; 23 | if (!/^[a-zA-Z]:\//.test(normalizedPath) && !/^\//.test(normalizedPath)) { 24 | // 注意:这里为了兼容性,即使在 Windows 上也可能出现不带盘符的路径 25 | // 例如,在 WSL 中或者某些构建工具中 26 | vscodePath = `/${normalizedPath}`; 27 | } 28 | log.info('open file(vscode)', `vscode://file/${vscodePath}`); 29 | 30 | return `vscode://file/${vscodePath}`; 31 | } 32 | 33 | export function convertPathToVSCodeUri(filePath: string) { 34 | if (!filePath) { 35 | return ''; 36 | } 37 | const uri = 'vscode://' + pathToFileURL(filePath).href; 38 | log.info('open file(vscode)', uri); 39 | return uri; 40 | } 41 | -------------------------------------------------------------------------------- /assets/menu/minimize.svg: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /assets/menu/tw-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/menu/stop.svg: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 28 | 29 | -------------------------------------------------------------------------------- /src/utils/createTray.ts: -------------------------------------------------------------------------------- 1 | import { app, Menu, Tray, BrowserWindow } from 'electron'; 2 | import { getAppIcon, getMenuIcon } from './icon'; 3 | import { getPlatform } from './getPlatform'; 4 | import { log } from '@/utils/logger'; 5 | import { t } from 'i18next'; 6 | 7 | export function createTray( 8 | win: BrowserWindow, 9 | server: { 10 | tray: Tray; 11 | } 12 | ) { 13 | const platform = getPlatform(); 14 | 15 | if (!server.tray) { 16 | 17 | server.tray = new Tray(getAppIcon()!); 18 | server.tray.on('click', () => { 19 | if (!win.isVisible() || win.isMinimized()) { 20 | win.show(); 21 | win.restore(); 22 | } else { 23 | win.minimize(); 24 | } 25 | }); 26 | } 27 | server.tray.setToolTip(t('tray.tooltip')); 28 | const contextMenu = Menu.buildFromTemplate([ 29 | { 30 | label: t('tray.showWindow'), 31 | icon: getMenuIcon(platform as any), 32 | click: () => { 33 | win.show(); 34 | }, 35 | }, 36 | // { 37 | // label: t('tray.openInBrowser'), 38 | // click: () => { 39 | // if (server.currentPort) { 40 | // shell.openExternal(`http://localhost:${server.currentPort}`); 41 | // } 42 | // }, 43 | // }, 44 | // { type: 'separator' }, 45 | // { 46 | // label: t('tray.about'), 47 | // click: showWikiInfo, 48 | // }, 49 | { 50 | label: t('tray.exit'), 51 | icon: getMenuIcon('exit'), 52 | click: () => { 53 | log.info('exit fomr tray'); 54 | app.quit(); 55 | }, 56 | }, 57 | ]); 58 | server.tray.setContextMenu(contextMenu); 59 | } 60 | -------------------------------------------------------------------------------- /assets/menu/add.svg: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 28 | 29 | -------------------------------------------------------------------------------- /src/utils/menubar/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type MenuItemConstructorOptions, 3 | // Menu, 4 | // Tray, 5 | // BrowserWindow, 6 | } from 'electron'; 7 | import { server, type IConfig } from '@/utils'; 8 | import { helpMenu } from '@/utils/menubar/help'; 9 | import { settingsMenu } from '@/utils/menubar/settings'; 10 | import { viewMenu } from './view'; 11 | import { fileMenu } from './file'; 12 | import { editMenu } from './edit'; 13 | import { getPlatform } from '../getPlatform'; 14 | import { wikisMenu } from './wikis'; 15 | import { generateId } from '../generateId'; 16 | 17 | export const createMenubar = (config: IConfig) => { 18 | return function () { 19 | const recentWikis = config.get('recentWikis') || []; 20 | const currentWiki = config.get('wikiPath'); 21 | 22 | const recentWikisWithTag = recentWikis.map((item) => { 23 | const wiki = { 24 | port: null as any, 25 | path: item, 26 | isCurrentWiki: false, 27 | isRunning: false, 28 | }; 29 | if (currentWiki === item) { 30 | wiki.isCurrentWiki = true; 31 | } 32 | const wikiServer = server.twServers.get(generateId(item)); 33 | if (wikiServer?.port) { 34 | wiki.port = wikiServer.port; 35 | wiki.isRunning = true; 36 | } 37 | return wiki; 38 | }); 39 | 40 | const menubars: MenuItemConstructorOptions[] = [ 41 | fileMenu(), 42 | viewMenu(), 43 | wikisMenu(recentWikisWithTag), 44 | settingsMenu(), 45 | helpMenu(), 46 | ]; 47 | 48 | if (getPlatform() === 'macOs') { 49 | menubars.splice(1, 0, editMenu()); 50 | } 51 | 52 | return menubars; 53 | }; 54 | }; 55 | -------------------------------------------------------------------------------- /assets/menu/folder-wiki.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /assets/menu/screens.svg: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 28 | 29 | -------------------------------------------------------------------------------- /assets/menu/copy.svg: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 28 | 29 | -------------------------------------------------------------------------------- /assets/menu/folder-wiki1-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/menu/folder-wiki1-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/menu/qrcode.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/utils/logger.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { app } from 'electron'; 3 | import fs from 'fs/promises'; 4 | 5 | export const log = require('electron-log/main'); 6 | 7 | const logDate = new Date().toISOString().split('T').shift()!.replace('-', '/'); // 替换第一个- 8 | export const logPath = path.join(app.getPath('logs'), logDate, `main.log`); 9 | 10 | export async function cleanupOldLogs() { 11 | const logsRoot = app.getPath('logs'); 12 | const now = new Date(); 13 | 14 | try { 15 | const yearDirs = await fs.readdir(logsRoot); 16 | await Promise.all( 17 | yearDirs.map(async (year) => { 18 | const yearPath = path.join(logsRoot, year); 19 | const stat = await fs.stat(yearPath); 20 | if (!stat.isDirectory()) return; 21 | 22 | const dayDirs = await fs.readdir(yearPath); 23 | await Promise.all( 24 | dayDirs.map(async (dayDir) => { 25 | const fullDirPath = path.join(yearPath, dayDir); 26 | const stat = await fs.stat(fullDirPath); 27 | if (!stat.isDirectory()) return; 28 | 29 | const [month, day] = dayDir.split('-'); 30 | const dirDate = new Date(`${year}-${month}-${day}`); 31 | const diffTime = now.getTime() - dirDate.getTime(); 32 | const diffDays = diffTime / (1000 * 60 * 60 * 24); 33 | 34 | if (diffDays > 7) { 35 | await fs.rm(fullDirPath, { recursive: true, force: true }); 36 | console.log(`[Logs Cleaned]: ${fullDirPath}`); 37 | } 38 | }) 39 | ); 40 | }) 41 | ); 42 | } catch (err) { 43 | console.error('Clean Logs Error:', err); 44 | } 45 | } 46 | 47 | export function logInit() { 48 | log.transports.file.resolvePathFn = () => logPath; 49 | } 50 | -------------------------------------------------------------------------------- /assets/menu/paste.svg: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 28 | 29 | -------------------------------------------------------------------------------- /assets/menu/reset.svg: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 28 | 29 | -------------------------------------------------------------------------------- /src/utils/injectScript.ts: -------------------------------------------------------------------------------- 1 | import { config } from '@/utils/config'; 2 | import path from 'path'; 3 | import { type BrowserWindow } from 'electron'; 4 | 5 | import { log } from '@/utils/logger'; 6 | import { processEnv } from '@/main'; 7 | 8 | export const injectRenderScript = (win: BrowserWindow, cbl: Function) => { 9 | const render = path.join(processEnv.VITE_DIST, 'renderer/index.js'); 10 | 11 | win.webContents.executeJavaScript(` 12 | (() => { 13 | const script = document.createElement('script'); 14 | script.src = 'file://${render.replace(/\\/g, '/')}'; 15 | document.body.appendChild(script); 16 | })() 17 | `); 18 | log.info('injected renderjs'); 19 | if (typeof cbl === 'function') { 20 | cbl(); 21 | } 22 | }; 23 | 24 | export const injectScript = (win: BrowserWindow) => { 25 | // const render = path.join(processEnv.VITE_DIST, 'renderer/index.js'); 26 | const confetti = path.join(processEnv.VITE_PUBLIC, 'lib', 'confetti.min.js'); 27 | const qrcode = path.join(processEnv.VITE_PUBLIC, 'lib', 'qrcode.min.js'); 28 | // const swal = path.join(process.env.VITE_PUBLIC, 'lib/sweetalert.min.js'); 29 | const autocorrectLib = path.join( 30 | processEnv.VITE_PUBLIC, 31 | 'lib/autocorrect.min.js' 32 | ); 33 | 34 | log.info('Begin inject script'); 35 | const scripts = [confetti, qrcode]; 36 | 37 | if (config.get('autocorrect')) { 38 | scripts.push(autocorrectLib); 39 | log.info('Enable autocorrect lib'); 40 | } 41 | 42 | scripts.forEach((src) => { 43 | win.webContents.executeJavaScript(` 44 | (() => { 45 | const script = document.createElement('script'); 46 | script.src = 'file://${src.replace(/\\/g, '/')}'; 47 | document.body.appendChild(script); 48 | })(); 49 | `); 50 | }); 51 | }; 52 | -------------------------------------------------------------------------------- /assets/menu/lock.svg: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 28 | 29 | -------------------------------------------------------------------------------- /src/utils/tiddlywiki.ts: -------------------------------------------------------------------------------- 1 | import { type ITiddlyWiki } from 'tw5-typed'; 2 | import path from 'path'; 3 | import { app, } from 'electron'; 4 | import { log } from './logger'; 5 | import fs from 'fs' 6 | 7 | // import { TiddlyWiki } from 'tiddlywiki'; 8 | 9 | let wiki; 10 | export const tiddlywikiExtensionDir = path.join(app.getPath('userData'), 'extensions') 11 | const wikiDir = path.join(tiddlywikiExtensionDir, 'tiddlywiki') 12 | const pkgPath = path.join(wikiDir, 'package.json') 13 | 14 | function isValidTiddlyWiki(dir: string) { 15 | if (!fs.existsSync(dir)) return false 16 | if (!fs.existsSync(pkgPath)) return false 17 | 18 | try { 19 | const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')) 20 | return pkg.name && pkg.name.startsWith('tiddlywiki') && pkg.version 21 | } catch (err) { 22 | return false 23 | } 24 | } 25 | 26 | if (isValidTiddlyWiki(wikiDir)) { 27 | try { 28 | wiki = require(wikiDir) 29 | const pkg = require(pkgPath) 30 | log.info(`use user installed tiddlywiki ${pkg.version}, path is`, wikiDir) 31 | } catch (e) { 32 | log.warn("failed to load user tiddlywiki, fallback to system one", e) 33 | wiki = require('tiddlywiki') 34 | } 35 | } else { 36 | log.info("use system tiddlywiki") 37 | wiki = require('tiddlywiki') 38 | } 39 | 40 | export const dynamicWiki = wiki; 41 | 42 | export const tiddlywiki = ( 43 | // dir = '.', 44 | args: string[] = [], 45 | preloadTiddlers: Record[] = [], 46 | callback?: Function 47 | ): ITiddlyWiki => { 48 | const $tw = wiki.TiddlyWiki(); 49 | $tw.boot.argv = [...args]; 50 | if (preloadTiddlers.length > 0) { 51 | $tw.preloadTiddlerArray(preloadTiddlers); 52 | } 53 | if (typeof callback === 'function') { 54 | callback($tw); 55 | } 56 | $tw.boot.boot(); 57 | return $tw; 58 | }; 59 | -------------------------------------------------------------------------------- /assets/menu/zoomOut.svg: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 28 | 29 | -------------------------------------------------------------------------------- /assets/menu/save.svg: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 28 | 29 | -------------------------------------------------------------------------------- /assets/menu/warning.svg: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 28 | 29 | -------------------------------------------------------------------------------- /src/modules/showInputBox/index.ts: -------------------------------------------------------------------------------- 1 | import { processEnv } from '@/main'; 2 | import { ipcMain, BrowserWindow } from 'electron'; 3 | import path from 'path'; 4 | 5 | let inputWin: BrowserWindow | null = null; 6 | 7 | export function showInputBox( 8 | parentWindow: BrowserWindow, 9 | message: string = 'Please input ...', 10 | type: 'text' | 'password' = 'text', 11 | inputValue: string = '' 12 | ): Promise { 13 | return new Promise((resolve) => { 14 | if (inputWin) { 15 | inputWin.show(); 16 | inputWin?.webContents.send('set-title', message); 17 | inputWin?.webContents.send('set-inputvalue', { 18 | inputValue, 19 | type, 20 | }); 21 | inputWin.focus(); 22 | } else { 23 | inputWin = new BrowserWindow({ 24 | width: 400, 25 | height: 150, 26 | parent: parentWindow, 27 | modal: true, 28 | resizable: false, 29 | minimizable: false, 30 | maximizable: false, 31 | autoHideMenuBar: true, 32 | frame: false, 33 | show: false, 34 | webPreferences: { 35 | nodeIntegration: true, 36 | contextIsolation: false, 37 | spellcheck: false, 38 | }, 39 | }); 40 | inputWin.loadFile(path.join(processEnv.VITE_PUBLIC, 'input.html')); 41 | 42 | inputWin.once('ready-to-show', () => { 43 | inputWin!.show(); 44 | inputWin?.focus(); 45 | }); 46 | } 47 | 48 | inputWin.webContents.on('did-finish-load', () => { 49 | inputWin?.webContents.send('set-title', message); 50 | inputWin?.webContents.send('set-inputvalue', { inputValue, type }); 51 | }); 52 | 53 | ipcMain.on('input-value', (event, value) => { 54 | resolve(value); 55 | if (inputWin) { 56 | inputWin.hide(); 57 | } 58 | }); 59 | }); 60 | } 61 | -------------------------------------------------------------------------------- /scripts/generate-locales.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | const LOCALES_DIR = path.join(__dirname, '../src/locales'); 5 | const SUPPORTED_LOCALES = ['en-US', 'zh-CN']; 6 | 7 | const menu = require('./locales/menu'); 8 | const app = require('./locales/app'); 9 | const tray = require('./locales/tray'); 10 | const dialog = require('./locales/dialog'); 11 | const log = require('./locales/log'); 12 | const settings = require('./locales/settings'); 13 | 14 | // 统一的翻译对象 15 | const translations = { 16 | menu, 17 | app, 18 | tray, 19 | dialog, 20 | log, 21 | settings, 22 | }; 23 | 24 | // 将统一格式转换为单语言格式 25 | function extractLocale(obj, locale) { 26 | const result = {}; 27 | 28 | function extract(src, target) { 29 | for (const key in src) { 30 | if (typeof src[key] === 'object') { 31 | if (src[key][locale]) { 32 | target[key] = src[key][locale]; 33 | } else { 34 | target[key] = {}; 35 | extract(src[key], target[key]); 36 | } 37 | } 38 | } 39 | } 40 | 41 | extract(obj, result); 42 | return result; 43 | } 44 | 45 | // 生成翻译文件 46 | function generateLocales() { 47 | ensureDirectoryExists(LOCALES_DIR); 48 | 49 | SUPPORTED_LOCALES.forEach((locale) => { 50 | const localeDir = path.join(LOCALES_DIR, locale); 51 | ensureDirectoryExists(localeDir); 52 | 53 | const localeTranslations = extractLocale(translations, locale); 54 | 55 | fs.writeFileSync( 56 | path.join(localeDir, 'translation.json'), 57 | JSON.stringify(localeTranslations, null, 2) 58 | ); 59 | }); 60 | 61 | console.log('Locales generated successfully!'); 62 | } 63 | 64 | // 确保目录存在 65 | function ensureDirectoryExists(dir) { 66 | if (!fs.existsSync(dir)) { 67 | fs.mkdirSync(dir, { recursive: true }); 68 | } 69 | } 70 | 71 | generateLocales(); 72 | -------------------------------------------------------------------------------- /src/utils/renderer/setFavicon.ts: -------------------------------------------------------------------------------- 1 | export function setFavicon() { 2 | if ( 3 | !$tw.wiki.getTiddlerText('$:/favicon.ico') || 4 | !$tw.wiki.tiddlerExists('$:/favicon.ico') 5 | ) { 6 | console.log('set default $:/favicon.ico'); 7 | $tw.wiki.addTiddler({ 8 | title: '$:/favicon.ico', 9 | type: 'image/svg+xml', 10 | text: '', 11 | }); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /assets/menu/findInPage.svg: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 28 | 29 | -------------------------------------------------------------------------------- /assets/menu/findInPage_dark.svg: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 28 | 29 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src", "@types"], 3 | "references": [{ "path": "./tsconfig.node.json" }], 4 | "exclude": ["dist"], 5 | "compilerOptions": { 6 | "baseUrl": "./", 7 | "paths": { 8 | "@/*": ["src/*"] 9 | }, 10 | "skipLibCheck": true, 11 | "target": "ESNext", 12 | "module": "ESNext", 13 | "lib": ["ESNext", "dom"], 14 | "allowJs": true, 15 | // "checkJs": true, 16 | "declaration": false, 17 | "sourceMap": false, 18 | "strict": true, 19 | "strictNullChecks": true /* Enable strict null checks. */, 20 | "strictFunctionTypes": true /* Enable strict checking of function types. */, 21 | "strictPropertyInitialization": true /* Enable strict checking of property initialization in classes. */, 22 | "noImplicitThis": true /* Raise error on 'this' expressions with an implied 'any' type. */, 23 | "alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */, 24 | 25 | /* Module Resolution Options */ 26 | "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, 27 | // "types": [] /* Type declaration files to be included in compilation. */, 28 | "allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */, 29 | "resolveJsonModule": true, 30 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 31 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 32 | "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */, 33 | "emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */ 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /assets/menu/clear.svg: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 28 | 29 | -------------------------------------------------------------------------------- /assets/menu/info.svg: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 28 | 29 | -------------------------------------------------------------------------------- /assets/menu/exit.svg: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 28 | 29 | -------------------------------------------------------------------------------- /assets/menu/zoomIn.svg: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 28 | 29 | -------------------------------------------------------------------------------- /assets/menu/i18n2.svg: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 28 | 29 | -------------------------------------------------------------------------------- /assets/menu/success.svg: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 28 | 29 | -------------------------------------------------------------------------------- /assets/menu/i18n.svg: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 28 | 29 | -------------------------------------------------------------------------------- /assets/menu/restart.svg: -------------------------------------------------------------------------------- 1 | 24 | 25 | 27 | 29 | 31 | 32 | -------------------------------------------------------------------------------- /assets/menu/read.svg: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 28 | 29 | -------------------------------------------------------------------------------- /assets/menu/cut.svg: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 28 | 29 | -------------------------------------------------------------------------------- /src/utils/subwiki/index.ts: -------------------------------------------------------------------------------- 1 | // 给定一个目标文件夹, 和软连接的文件夹, 使用fs-extra进行软连接生成, 如果目标文件夹不存在就手动创建,如果软链接文件夹已存在就跳过 2 | 3 | import fs from 'fs-extra'; 4 | import { log } from '@/utils/logger'; 5 | 6 | /** 7 | * 创建软链接 8 | * @param targetPath 目标文件夹路径 9 | * @param symlinkPath 软链接路径 10 | */ 11 | export async function createSymlink( 12 | targetPath: string, 13 | symlinkPath: string 14 | ): Promise { 15 | try { 16 | // 确保目标文件夹存在 17 | if (!(await fs.pathExists(targetPath))) { 18 | await fs.mkdirp(targetPath); 19 | } 20 | 21 | // 检查软链接是否已存在 22 | if (await fs.pathExists(symlinkPath)) { 23 | log.info(`Softlink ${symlinkPath} has exist, skip`); 24 | return; 25 | } 26 | 27 | // 创建软链接 28 | await fs.ensureSymlink(targetPath, symlinkPath, 'junction'); 29 | log.info( 30 | `successfully create subwiki softlink: ${symlinkPath} -> ${targetPath}` 31 | ); 32 | } catch (error) { 33 | log.error(`failed to create softlink:`, error); 34 | throw error; 35 | } 36 | } 37 | 38 | // TODO: 导入时产生的files,需要检测是否有效, 如果无效就创建 39 | // TODO: 由于output/files 是嵌套软连接, 构建的时候也需要检测 40 | /** 41 | * 检查软链接是否有效 42 | * @param symlinkPath 软链接路径 43 | * @returns 是否有效 44 | */ 45 | export async function isSymlinkValid(symlinkPath: string): Promise { 46 | try { 47 | if (!(await fs.pathExists(symlinkPath))) { 48 | log.info(`softlink ${symlinkPath} does not exist`); 49 | return false; 50 | } 51 | 52 | const stats = await fs.lstat(symlinkPath); 53 | if (!stats.isSymbolicLink()) { 54 | log.info(`${symlinkPath} is not a symlink`); 55 | return false; 56 | } 57 | 58 | const target = await fs.readlink(symlinkPath); 59 | if (!(await fs.pathExists(target))) { 60 | log.info(`softlink ${symlinkPath} points to invalid target: ${target}`); 61 | return false; 62 | } 63 | 64 | log.info(`softlink ${symlinkPath} is valid`); 65 | return true; 66 | } catch (error) { 67 | log.error(`failed to check softlink:`, error); 68 | return false; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /assets/menu/build.svg: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 28 | 29 | -------------------------------------------------------------------------------- /assets/menu/featureSearch.svg: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 28 | 29 | -------------------------------------------------------------------------------- /electron-builder.ts: -------------------------------------------------------------------------------- 1 | const config = { 2 | // $schema: 'https://raw.githubusercontent.com/electron-userland/electron-builder/master/packages/app-builder-lib/scheme.json', 3 | protocols: [ 4 | { 5 | name: 'TiddlyWiki Protocol', 6 | schemes: ['tiddlywiki'], 7 | }, 8 | ], 9 | // appId: 'tiddlywiki.app', 10 | asar: true, 11 | directories: { 12 | output: 'release/${version}', 13 | }, 14 | generateUpdatesFilesForAllChannels: true, 15 | extraFiles: [ 16 | { 17 | from: 'LICENSE', 18 | to: 'LICENSE', 19 | }, 20 | { 21 | from: 'resources/', 22 | to: 'resources/', 23 | // filter: ['**/*.exe'], 24 | }, 25 | ], 26 | files: ['dist', 'LICENSE'], 27 | mac: { 28 | artifactName: '${productName}-${version}-${arch}.${ext}', 29 | target: [ 30 | { 31 | target: 'dmg', 32 | arch: ['x64', 'arm64'], 33 | }, 34 | { 35 | target: 'zip', 36 | arch: ['x64', 'arm64'], 37 | }, 38 | ], 39 | }, 40 | linux: { 41 | artifactName: '${productName}-${version}-${arch}.${ext}', 42 | target: [ 43 | { 44 | target: 'deb', 45 | arch: ['arm64', 'x64'], 46 | }, 47 | { 48 | target: 'AppImage', 49 | arch: ['arm64', 'x64'], 50 | }, 51 | { 52 | target: 'pacman', 53 | arch: ['arm64', 'x64'], 54 | }, 55 | // { 56 | // target: 'rpm', 57 | // arch: ['arm64', 'x64'], 58 | // }, 59 | ], 60 | maintainer: 'oeyoews', 61 | category: 'Utility', 62 | }, 63 | win: { 64 | artifactName: '${productName}-${version}.${ext}', 65 | target: ['nsis'], 66 | }, 67 | nsis: { 68 | oneClick: false, 69 | allowElevation: true, 70 | perMachine: false, 71 | allowToChangeInstallationDirectory: true, 72 | deleteAppDataOnUninstall: false, 73 | displayLanguageSelector: false, 74 | shortcutName: 'TiddlyWiki5 App', 75 | }, 76 | publish: { 77 | provider: 'github', 78 | owner: 'oeyoews', 79 | repo: 'tiddlywiki-app', 80 | }, 81 | }; 82 | 83 | export default config; 84 | -------------------------------------------------------------------------------- /src/preload/index.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | import { contextBridge, ipcRenderer } from 'electron'; 4 | 5 | export const electronAPI = { 6 | alert: (message) => 7 | ipcRenderer.sendSync('custom-dialog', { type: 'alert', message }), 8 | confirm: (message) => 9 | ipcRenderer.sendSync('custom-dialog', { type: 'confirm', message }), 10 | onShowQRCode: (callback) => ipcRenderer.on('show-qrcode', callback), 11 | // markdown importer 12 | onImportMarkdown: (callback) => ipcRenderer.on('import-markdown', callback), 13 | onImportMDFromWeb: (callback) => 14 | ipcRenderer.on('open-url', (e, data) => callback(data)), 15 | 16 | // github 17 | onConfigGithub: (callback) => 18 | ipcRenderer.on('config-github', (_event, data) => callback(data)), 19 | 20 | onGetGHConfig: (cbl) => ipcRenderer.on('get-gh-config', cbl), 21 | sendGHConfig: (data) => ipcRenderer.send('update-gh-config', data), 22 | 23 | // 双向 24 | onTidInfo: (callback) => 25 | ipcRenderer.on('update-tid', (_event, value) => callback(value)), 26 | onTidInfoVscode: (callback) => 27 | ipcRenderer.on('update-tid-vscode', (_event, value) => callback(value)), 28 | sendTidInfo: (value) => ipcRenderer.send('tid-info', value), 29 | sendTidInfoVscode: (value) => ipcRenderer.send('tid-info-vscode', value), 30 | 31 | // 接收图片坐标 32 | onTitleFetched: (callback) => 33 | ipcRenderer.on('title-fetched', (_event, value) => callback(value)), 34 | 35 | // 渲染进程向主进程发起的双向通信 36 | startFetchData: async (data) => ipcRenderer.invoke('get-data', data), 37 | startFetchRSSData: async (data) => ipcRenderer.invoke('fetchRss', data), 38 | 39 | onConfetti: (callback) => ipcRenderer.on('wiki-imported', callback), 40 | 41 | // onTwInstanceUpdate: (callback) => 42 | // ipcRenderer.on('tw-instance-update', callback), 43 | // openWiki: () => ipcRenderer.invoke('dialog:openWiki'), 44 | // buildWiki: () => ipcRenderer.invoke('wiki:build'), 45 | // openInBrowser: () => ipcRenderer.invoke('wiki:openInBrowser'), 46 | // getWikiInfo: () => ipcRenderer.invoke('wiki:getInfo'), 47 | }; 48 | 49 | contextBridge.exposeInMainWorld('electronAPI', electronAPI); 50 | -------------------------------------------------------------------------------- /assets/menu/panda.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.zh-CN.md: -------------------------------------------------------------------------------- 1 | # TiddlyWiki App 🌟 2 | 3 | [English](https://github.com/oeyoews/tiddlywiki-app/blob/main/README.md) | 简体中文 4 | 5 | ![img](https://github.com/oeyoews/tiddlywiki-app/raw/main/banner04.png) 6 | ![img](https://github.com/oeyoews/tiddlywiki-app/raw/main/banner03.png) 7 | 8 | 一个 TiddlyWiki 桌面应用,提供更加丝滑的桌面端使用体验。 9 | 10 | ## ✨ 功能特点 11 | 12 | - 🔧 系统托盘支持,最小化到托盘 13 | - 📂 支持模板导入, 单文件导入 14 | - 📂 支持 Markdown 批量导入 15 | - 🔒 支持加密构建 HTML 16 | - 🚀 支持多种启动方式: 17 | - 💻 本地服务器模式 18 | - 🌐 浏览器打开 19 | - 📄 支持子Wiki 20 | - 📝 Wiki 管理功能: 21 | - 📂 打开/切换 Wiki 22 | - 🔨 构建静态 Wiki 23 | - 📁 在系统文件管理器中打开 24 | - 🚀 一键部署到 GitHub Pages 25 | - 🌍 国际化支持 26 | - 🇨🇳 简体中文 27 | - 🇺🇸 English 28 | - 🔄 自动更新功能 29 | 30 | ## 📖 使用说明 31 | 32 | ### 🔰 安装 33 | 34 | [下载 tiddlywiki-app](https://github.com/oeyoews/tiddlywiki-app/releases) 35 | 36 | 41 | 42 | ### ⚡ 基本操作 43 | 44 | 1. 使用菜单栏或系统托盘进行操作: 45 | - 📋 文件菜单: 46 | - 📂 打开 Wiki:选择其他 Wiki 文件夹 47 | - 🔨 构建 Wiki:生成静态 HTML 文件 48 | - 🌐 在浏览器中打开:使用默认浏览器打开当前 Wiki 49 | - 📁 打开当前 Wiki 文件夹:在文件管理器中查看 50 | - 🔽 系统托盘: 51 | - 🖱️ 左键点击:切换窗口显示/隐藏 52 | - 📌 右键菜单:快速访问常用功能 53 | 54 | ### ⌨️ 快捷操作 55 | 56 | - 🔽 最小化:窗口会自动隐藏到系统托盘 57 | - ❌ 关闭按钮:默认最小化到托盘,可通过托盘菜单完全退出 58 | 59 | ## 👨‍💻 开发 60 | 61 | ### 🛠️ 环境要求 62 | 63 | - 📦 Node.js 64 | - 📦 npm 或 yarn 65 | - 📦 git 66 | 67 | ### 🚀 本地开发 68 | 69 | ```bash 70 | # 安装依赖 71 | npm install 72 | 73 | # 启动开发服务 74 | npm run dev 75 | ``` 76 | 77 | ## 🤔 Why create Tiddlywiki APP? 78 | 79 | 首先是解决 TiddlyWiki 长期困扰用户的保存问题。尽管社区已经提供了许多解决方案,但还有什么方法能比直接下载 exe 软件,双击安装来得更简单、更方便呢? 80 | 81 | 其次,Tiddlywiki APP 和类似应用的主要区别在于,它完全不侵入用户的 Wiki,也不会对其进行任何改动,真正意义上的开箱即用。我希望它尽可能简单,让初次尝试 TiddlyWiki 的用户无需额外学习其他知识。 82 | 83 | 关于空白版本,我希望用户在初次接触 TiddlyWiki 时,能够体验到 100% 纯正的原始 TiddlyWiki,而不是被各种插件包围,导致困惑不已。这也许就是 Jermolene 选择提供空白版本作为用户入门体验 TiddlyWIki 的初衷吧。 84 | 85 | 至于“空白版本可能吓跑新用户”的说法,这里不作过多讨论。我希望更多人了解 TiddlyWiki,但不会刻意推广它。毕竟,就当前而言,TiddlyWiki 的易用性并没有太大的推广优势。 86 | 87 | ## 📚 相关项目 88 | 89 | * [UseWiki2](https://github.com/oeyoews/usewiki2) 90 | -------------------------------------------------------------------------------- /assets/menu/expandAll.svg: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 28 | 29 | -------------------------------------------------------------------------------- /assets/menu/expandAll_dark.svg: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 28 | 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tiddlywiki-app", 3 | "version": "4.9.13", 4 | "description": "TiddlyWiki App", 5 | "main": "./dist/main/index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/oeyoews/tiddlywiki-app.git" 9 | }, 10 | "scripts": { 11 | "updateIcon": "node scripts/svg2png.js ./assets/menu/ public/assets/menu/", 12 | "test": "playwright test", 13 | "dev": "vite", 14 | "preview": "vite build && electron .", 15 | "docs:dev": "tiddlywiki --listen", 16 | "test:importweb": "vite build && electron . \"tiddlywiki://?_source=web&title=hello&text=测试导入\"", 17 | "build": "vite build && electron-builder", 18 | "release": "vite build && node ./scripts/minify-tw.js && electron-builder" 19 | }, 20 | "keywords": [ 21 | "tiddlywiki" 22 | ], 23 | "type": "commonjs", 24 | "author": { 25 | "name": "oeyoews", 26 | "email": "jyao4783@gmail.com", 27 | "url": "https://github.com/oeyoews/tiddlywiki-app" 28 | }, 29 | "license": "MIT", 30 | "dependencies": { 31 | "cross-spawn": "^7.0.6", 32 | "electron-conf": "^1.3.0", 33 | "electron-log": "^5.3.2", 34 | "electron-updater": "^6.3.9", 35 | "fs-extra": "^11.3.0", 36 | "get-port": "^7.1.0", 37 | "gray-matter": "^4.0.3", 38 | "i18next": "^24.2.2", 39 | "tiddlywiki": "^5.3.8" 40 | }, 41 | "pnpm": { 42 | "onlyBuiltDependencies": [ 43 | "electron", 44 | "electron-winstaller", 45 | "esbuild", 46 | "sharp" 47 | ], 48 | "patchedDependencies": { 49 | "tw5-typed": "patches/tw5-typed.patch" 50 | } 51 | }, 52 | "devDependencies": { 53 | "@playwright/test": "^1.51.1", 54 | "@types/fs-extra": "^11.0.4", 55 | "@types/node": "^22.14.0", 56 | "electron": "^39.2.3", 57 | "electron-builder": "^26.0.12", 58 | "playwright": "^1.51.1", 59 | "tw5-typed": "^0.5.14", 60 | "vite": "^6.2.2", 61 | "vite-plugin-electron": "^0.29.0", 62 | "vite-plugin-electron-renderer": "^0.14.6" 63 | }, 64 | "optionalDependencies": { 65 | "sharp": "^0.33.5", 66 | "svgo": "^3.3.2" 67 | }, 68 | "engines": { 69 | "node": ">= 18" 70 | }, 71 | "volta": { 72 | "node": "22.17.0" 73 | } 74 | } -------------------------------------------------------------------------------- /assets/menu/export.svg: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 28 | 29 | -------------------------------------------------------------------------------- /assets/menu/web.svg: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 28 | 29 | -------------------------------------------------------------------------------- /assets/menu/gear.svg: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 28 | 29 | -------------------------------------------------------------------------------- /src/modules/markdown-importer/index.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import { dialog } from 'electron'; 4 | import matter from 'gray-matter'; 5 | 6 | const defaultIgnoreFolders: string[] = [ 7 | 'node_modules', 8 | '.vscode', 9 | '.git', 10 | '.idea', 11 | ]; 12 | 13 | type FlattenObject = Record; 14 | 15 | type MarkdownFile = { 16 | title: string; 17 | text: string; 18 | modified: string; 19 | created: string; // 添加创建时间字段 20 | tags?: string[]; 21 | } & FlattenObject; 22 | 23 | export async function readMarkdownFolder( 24 | dirPath: string | null = null, 25 | ignoreFolders: string[] = defaultIgnoreFolders 26 | ): Promise { 27 | if (!dirPath) { 28 | const result = await dialog.showOpenDialog({ 29 | properties: ['openDirectory'], 30 | }); 31 | if (result.canceled || result.filePaths.length === 0) return []; 32 | dirPath = result.filePaths[0]; 33 | } 34 | 35 | const mdFiles: MarkdownFile[] = []; 36 | 37 | function readDir(currentPath: string) { 38 | const entries = fs.readdirSync(currentPath, { withFileTypes: true }); 39 | for (const entry of entries) { 40 | const fullPath = path.join(currentPath, entry.name); 41 | if (entry.isFile() && entry.name.endsWith('.md')) { 42 | const text = fs.readFileSync(fullPath, 'utf-8'); 43 | const { data: fields, content: body } = matter(text); 44 | const stats = fs.statSync(fullPath); 45 | 46 | const modified = (new Date(stats.mtime) || new Date()) 47 | .toISOString() 48 | .replace(/\D/g, ''); 49 | 50 | const created = (new Date(stats.birthtime) || new Date()) 51 | .toISOString() 52 | .replace(/\D/g, ''); 53 | 54 | const mdFile: MarkdownFile = { 55 | title: entry.name.slice(0, -3), 56 | text: body, 57 | created, 58 | ...fields, 59 | modified, 60 | }; 61 | 62 | if (mdFile.tags && Array.isArray(mdFile.tags)) { 63 | mdFile.tags = mdFile.tags.map((tag) => tag.replace(/`/g, '')); 64 | } 65 | mdFiles.push(mdFile); 66 | } else if ( 67 | entry.isDirectory() && 68 | !ignoreFolders.includes(entry.name) && 69 | !entry.name.startsWith('.') 70 | ) { 71 | readDir(fullPath); 72 | } 73 | } 74 | } 75 | 76 | readDir(dirPath); 77 | return mdFiles; 78 | } 79 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | # tags: 8 | # - 'v*.*.*' # 只有 vX.X.X 形式的 tag 触发 9 | workflow_dispatch: 10 | 11 | jobs: 12 | release: 13 | name: build and release electron app 14 | runs-on: ${{ matrix.os }} 15 | 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | # , ubuntu-latest 20 | os: [windows-latest, macos-latest, ubuntu-latest] 21 | 22 | steps: 23 | - name: install missing dependencies 24 | if: startsWith(matrix.os, 'ubuntu') 25 | run: sudo apt-get install -y libarchive-tools 26 | 27 | - name: Check out git repository 28 | uses: actions/checkout@v3.0.0 29 | 30 | - name: Install Node.js 31 | uses: actions/setup-node@v3 32 | with: 33 | node-version: 20 34 | 35 | - name: Setup pnpm 36 | uses: pnpm/action-setup@v2 37 | with: 38 | version: 'latest' 39 | run_install: false 40 | 41 | - name: Cache dependencies 42 | uses: actions/cache@v3 43 | with: 44 | path: | 45 | **/node_modules 46 | ~/.pnpm-store 47 | ~/.npm 48 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 49 | restore-keys: | 50 | ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 51 | ${{ runner.os }}-node- 52 | 53 | - name: Install Dependencies 54 | run: pnpm install 55 | 56 | - name: Build Electron App 57 | run: pnpm run release 58 | env: 59 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 60 | 61 | # - name: upload artifacts 62 | # uses: actions/upload-artifact@v4 63 | # with: 64 | # name: ${{ matrix.os }} 65 | # path: release/tiddlywiki-app_*.* 66 | 67 | # - name: release 68 | # uses: softprops/action-gh-release@v0.1.14 69 | # if: startsWith(github.ref, 'refs/tags/') 70 | # with: 71 | # files: | 72 | # release/**/tiddlywiki-app-**.exe 73 | # release/**/tiddlywiki-app-**.dmg 74 | # release/**/tiddlywiki-app-**.AppImage 75 | # release/**/tiddlywiki-app-**.deb 76 | # release/**/tiddlywiki-app-**.rpm 77 | # release/**/latest*.yml 78 | # env: 79 | # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 80 | -------------------------------------------------------------------------------- /src/utils/icon.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { nativeImage, nativeTheme } from 'electron'; 3 | import { config } from '@/utils/config'; 4 | import { getPlatform } from '@/utils/getPlatform'; 5 | import { processEnv } from '@/main'; 6 | 7 | // **缓存对象**(key: `name-size`,value: `nativeImage`) 8 | const iconCache = new Map(); 9 | const enableIcon = config.get('icon'); 10 | 11 | export const getMenuIcon = ( 12 | name: IMenuIcon, 13 | size: number = 16, 14 | force = false 15 | ) => { 16 | if (!enableIcon && !force) return; 17 | 18 | const cacheKey = `${name}-${size}`; 19 | if (iconCache.has(cacheKey)) { 20 | return iconCache.get(cacheKey)!; 21 | } 22 | 23 | const iconPath = path.join( 24 | processEnv.VITE_PUBLIC, 25 | 'assets/menu', 26 | `${name}.png` 27 | ); 28 | const image = nativeImage 29 | .createFromPath(iconPath) 30 | .resize({ width: size, height: size, quality: 'best' }); 31 | 32 | // **缓存结果** 33 | iconCache.set(cacheKey, image); 34 | return image; 35 | }; 36 | 37 | export const twImage = (size: number = 16) => getMenuIcon('tw-light', size); // 使用缓存 38 | 39 | // 强制显示图标 40 | export const getAppIcon = ( 41 | size: number = 22, 42 | darkmode: 'auto' | 'tw-dark' | 'tw-light' = 'auto' 43 | ) => { 44 | if (darkmode !== 'auto') { 45 | return getMenuIcon(darkmode, size); 46 | } else { 47 | return nativeTheme.shouldUseDarkColors 48 | ? getMenuIcon('tw-dark', size, true) 49 | : getMenuIcon('tw-light', size, true); 50 | } 51 | }; 52 | 53 | export const getAppIcon2 = ( 54 | size: number = 22, 55 | darkmode: 'auto' | 'tw-dark' | 'tw-light' = 'auto' 56 | ) => { 57 | if (darkmode !== 'auto') { 58 | return getMenuIcon(darkmode, size); 59 | } else { 60 | return nativeTheme.shouldUseDarkColors 61 | ? getMenuIcon('tw-dark', size) 62 | : getMenuIcon('tw-light', size); 63 | } 64 | }; 65 | 66 | // TODO: 动态更新监听 67 | export const getWikiFolderIcon = ( 68 | size: number = 16, 69 | darkmode: 'auto' | 'folder-wiki1-dark' | 'folder-wiki1-light' = 'auto' 70 | ) => { 71 | if (darkmode !== 'auto') { 72 | return getMenuIcon(darkmode, size); 73 | } else { 74 | return nativeTheme.shouldUseDarkColors 75 | ? getMenuIcon('folder-wiki1-dark', size) 76 | : getMenuIcon('folder-wiki1-light', size); 77 | } 78 | }; 79 | 80 | // @ts-ignore 81 | export const getFolderIcon = () => getMenuIcon(`folder-${getPlatform()}`); 82 | -------------------------------------------------------------------------------- /src/utils/wiki/index.ts: -------------------------------------------------------------------------------- 1 | import { log } from '@/utils/logger'; 2 | import { buildIndexHTMLArgs, defaultPlugins } from '@/utils/wiki/constant'; 3 | import fs from 'fs'; 4 | import path from 'path'; 5 | 6 | /** 7 | * enable retain-original-tiddler-path 8 | * @param {*} infoPath 9 | */ 10 | export function updateOriginalPath(infoPath: string) { 11 | const infoKey = 'retain-original-tiddler-path'; 12 | 13 | let twConfigInfo = JSON.parse(fs.readFileSync(infoPath, 'utf8')); 14 | if (!twConfigInfo.config || !twConfigInfo.config?.[infoKey]) { 15 | twConfigInfo.config = { 16 | 'retain-original-tiddler-path': true, 17 | }; 18 | log.info('update twinfo config'); 19 | fs.writeFileSync(infoPath, JSON.stringify(twConfigInfo, null, 4), 'utf8'); 20 | } 21 | } 22 | 23 | /** 24 | * 检测 tiddlywiki.info 是否有效, 并修复 25 | * @param infoPath 26 | */ 27 | export function checkTWPlugins(infoPath: string) { 28 | let twInfo = JSON.parse(fs.readFileSync(infoPath, 'utf8')); 29 | // 插件为空的情况 30 | if (!twInfo.plugins || twInfo.plugins.length === 0) { 31 | log.info(infoPath, 'is not correct, has already fix it'); 32 | twInfo.plugins = defaultPlugins; 33 | } else { 34 | // 插件缺少的情况 35 | const hasAllNesPlugins = defaultPlugins.every((item: string) => 36 | twInfo.plugins.includes(item) 37 | ); 38 | if (!hasAllNesPlugins) { 39 | log.info(infoPath, 'is missing some nessary plugins, has already fix it'); 40 | const plugins = [...twInfo.plugins, ...defaultPlugins]; 41 | twInfo.plugins = [...new Set(plugins)]; 42 | } 43 | } 44 | fs.writeFileSync(infoPath, JSON.stringify(twInfo, null, 4), 'utf8'); 45 | } 46 | 47 | export function checkBuildInfo(wikiPath: string) { 48 | const bootPath = path.join(wikiPath, 'tiddlywiki.info'); 49 | let twInfo = JSON.parse(fs.readFileSync(bootPath, 'utf8')); 50 | 51 | // 检查并添加构建配置,修复导入的文件夹无法构建 52 | if (!twInfo.build || !twInfo.build.index) { 53 | twInfo.build = { 54 | ...twInfo.build, 55 | index: buildIndexHTMLArgs, 56 | }; 57 | } else { 58 | if (twInfo.build.index.length !== 7) { 59 | twInfo.build.index = buildIndexHTMLArgs; 60 | log.info('update', bootPath, 'to support ignore subwiki on build'); 61 | } 62 | } 63 | fs.writeFileSync(bootPath, JSON.stringify(twInfo, null, 4), 'utf8'); 64 | } 65 | 66 | export function checkThemes(infoPath: string) { 67 | let twInfo = JSON.parse(fs.readFileSync(infoPath, 'utf8')); 68 | 69 | // 检查并添加构建配置,修复导入的文件夹无法构建 70 | if (!twInfo.themes || !twInfo.themes.index) { 71 | twInfo.themes = ['tiddlywiki/vanilla']; 72 | fs.writeFileSync(infoPath, JSON.stringify(twInfo, null, 4), 'utf8'); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import path from 'path'; 3 | import pkg from './package.json'; 4 | import electron from 'vite-plugin-electron'; 5 | 6 | export default defineConfig(({ command }) => { 7 | const isBuild = command === 'build'; 8 | return { 9 | resolve: { 10 | alias: { 11 | '@': path.resolve(__dirname, 'src'), 12 | }, 13 | }, 14 | plugins: [ 15 | electron([ 16 | { 17 | entry: { 18 | 'main/index': 'src/main/index.ts', 19 | 'preload/index': 'src/preload/index.ts', 20 | 'renderer/index': 'src/renderer/index.ts', 21 | }, 22 | vite: { 23 | resolve: { 24 | alias: { 25 | '@': path.resolve(__dirname, 'src'), 26 | }, 27 | }, 28 | build: { 29 | emptyOutDir: !isBuild, 30 | minify: isBuild, 31 | outDir: 'dist', 32 | rollupOptions: { 33 | output: { 34 | // manualChunks(id) { 35 | // return undefined; 36 | // }, 37 | // inlineDynamicImports: true, // 将动态导入合并 38 | }, 39 | external: Object.keys( 40 | 'dependencies' in pkg ? pkg.dependencies : {} 41 | ), 42 | }, 43 | }, 44 | }, 45 | }, 46 | ]), 47 | ], 48 | // build: { 49 | // // watch: {}, 50 | // outDir: 'dist', 51 | // // emptyOutDir: false, 52 | // // minify: 'esbuild', 53 | // // minify: false, 54 | // // minify: process.env.NODE_ENV === 'production' ? 'esbuild' : false, 55 | // assetsInlineLimit: 0, // 禁止资源内联,避免图片被转为 base64 56 | // target: 'node18', 57 | // // assetsDir: 'assets', // 静态资源目录 58 | // rollupOptions: { 59 | // // input: { 60 | // // main: 'src/main/index.js', 61 | // // }, 62 | // // output: { 63 | // // entryFileNames: 'index.js', 64 | // // format: 'cjs', 65 | // // }, 66 | // input: { 67 | // 'main/index': 'src/main/index.ts', 68 | // 'preload/index': 'src/preload/index.js', 69 | // 'renderer/index': 'src/renderer/index.js', 70 | // }, 71 | // output: { 72 | // dir: 'dist', 73 | // format: 'cjs', 74 | // sourcemap: false, 75 | // entryFileNames: '[name].js', // 保持原文件名 76 | // }, 77 | // external: [ 78 | // ...Object.keys('dependencies' in pkg ? pkg.dependencies : {}), 79 | // 'electron', 80 | // 'path', 81 | // 'fs', 82 | // 'url', 83 | // 'node:process', 84 | // 'node:os', 85 | // 'node:fs', 86 | // 'node:url', 87 | // 'node:path', 88 | // 'node:util', 89 | // ], 90 | // }, 91 | // }, 92 | }; 93 | }); 94 | -------------------------------------------------------------------------------- /@types/types.d.ts: -------------------------------------------------------------------------------- 1 | type DialogType = 'info' | 'confirm' | 'question'; 2 | 3 | interface ISaver { 4 | owner: string; 5 | repo: string; 6 | branch: string; 7 | wikiFolder: string; 8 | GITHUB_TOKEN: string; 9 | COMMIT_MESSAGE?: string; 10 | } 11 | 12 | interface CommitBody { 13 | message: string; 14 | content: string; 15 | branch: string; 16 | sha?: string; // 可选的 SHA 属性 17 | } 18 | 19 | type IMenuIconFile = 20 | | 'File' 21 | | 'warning' 22 | | 'config' 23 | | 'logo' 24 | | 'export' 25 | | 'success' 26 | | 'username' 27 | | 'stop' 28 | | 'winState' 29 | | 'qrcode' 30 | | 'host' 31 | | 'vscode' 32 | | 'open' 33 | | 'gear' 34 | | 'plus' 35 | | 'plus2' 36 | | 'web_app' 37 | | 'folder-wiki' 38 | | 'folder-opened' 39 | | 'panda' 40 | | 'folder-wiki1-dark' 41 | | 'folder-wiki1-light' 42 | | 'trash' 43 | | 'error' 44 | | 'lock' 45 | | 'import' 46 | | 'minimize' 47 | | 'tw-light' 48 | | 'tw-dark' 49 | | 'new-wiki' 50 | | 'html' 51 | | 'new-folder-template' 52 | | 'folder' 53 | | 'folder-windows' 54 | | 'folder-linux' 55 | | 'folder-macOS' 56 | | 'import' 57 | | 'open-wiki' 58 | | 'exit' 59 | | 'restart' 60 | | 'build' 61 | | 'recent' 62 | | 'clear' 63 | | 'markdown' 64 | | 'format' 65 | | 'help' 66 | | 'search' 67 | | 'searchGoogle' 68 | | 'downloading' 69 | | 'release'; 70 | 71 | type IContextIcon = 'copy' | 'save' | 'paste' | 'cut' | 'image' | 'menu'; 72 | 73 | type IMenuViewIcon = 74 | | 'web' 75 | | 'subwiki' 76 | | 'zoomIn' 77 | | 'zoomOut' 78 | | 'reset' 79 | | 'reload' 80 | | 'read' 81 | | 'screens'; 82 | 83 | type IMenuIconSetting = 84 | | 'language' 85 | | 'autostart' 86 | | 'gitHub' 87 | | 'settings' 88 | | 'power'; 89 | 90 | type IMenuHelpIcon = 91 | | 'about' 92 | | 'info' 93 | | 'bug' 94 | | 'update' 95 | | 'log' 96 | | 'log2' 97 | | 'issue' 98 | | 'devtools' 99 | | 'console' 100 | | 'link' 101 | | 'link2' 102 | | 'i18n2' 103 | | 'star' 104 | | 'conf' 105 | | 'module' 106 | | 'chrome-web-store' 107 | | 'i18n'; 108 | 109 | type IPlatform = 'macOS' | 'windows' | 'linux'; 110 | 111 | type IMenuIcon = 112 | | IMenuIconFile 113 | | IMenuIconSetting 114 | | IPlatform 115 | | IMenuHelpIcon 116 | | IContextIcon 117 | | IMenuViewIcon; 118 | 119 | type IUpdateMenuType = 120 | | 'updateMenu' 121 | | 'updatingMenu' 122 | | 'downloadingApp' 123 | | 'restartMenu'; 124 | 125 | type IMarkdownTiddler = { 126 | title: string; 127 | text: string; 128 | modified: string; 129 | }; 130 | 131 | interface IBuildOptions { 132 | password?: string; 133 | // outputPath?: string 134 | } 135 | 136 | type IWinState = { 137 | width: number; 138 | height: number; 139 | x: number; 140 | y: number; 141 | isMaximized: boolean; 142 | isFullScreen: boolean; 143 | }; 144 | 145 | interface IWikiMenu { 146 | port: null | number; 147 | path: string; 148 | isCurrentWiki: boolean; 149 | isRunning: boolean; 150 | } 151 | -------------------------------------------------------------------------------- /src/utils/menubar/help.ts: -------------------------------------------------------------------------------- 1 | import { shell, app, MenuItemConstructorOptions } from 'electron'; 2 | import { getMenuIcon } from '@/utils/icon'; 3 | import { checkForUpdates } from '@/utils/checkUpdate'; 4 | import { showWikiInfo, server } from '@/utils'; 5 | import { i18next } from '@/i18n'; 6 | import { t } from 'i18next'; 7 | import { logPath } from '../logger'; 8 | import { getPlatform } from '@/utils/getPlatform'; 9 | 10 | const { autoUpdater } = require('electron-updater'); 11 | 12 | // 需要等到 t 初始化 13 | export const helpMenu = (): MenuItemConstructorOptions => ({ 14 | label: t('menu.help'), 15 | id: 'Help', 16 | submenu: [ 17 | { 18 | label: t('menu.devTools'), 19 | accelerator: 'CmdOrCtrl+Shift+I', 20 | click: () => server.win.webContents.openDevTools({ mode: 'right' }), 21 | icon: getMenuIcon('console'), 22 | }, 23 | { 24 | label: t('menu.checkUpdate'), 25 | // @see https://www.electronjs.org/zh/docs/latest/api/auto-updater 26 | visible: getPlatform() === 'windows' || !!process.env.APPIMAGE,// macos update need application sign 27 | id: 'update', 28 | enabled: app.isPackaged, 29 | click: () => checkForUpdates(), 30 | icon: getMenuIcon('update'), 31 | }, 32 | { 33 | label: t('dialog.checkingUpdate'), 34 | id: 'updating', 35 | visible: false, 36 | enabled: false, 37 | icon: getMenuIcon('update'), 38 | }, 39 | { 40 | label: t('dialog.downloading'), 41 | id: 'downloadingApp', 42 | visible: false, 43 | enabled: false, 44 | icon: getMenuIcon('downloading'), 45 | }, 46 | { 47 | label: t('dialog.restartNow'), 48 | id: 'restartApp', 49 | visible: false, 50 | click: () => autoUpdater.quitAndInstall(true, true), // 静默安装并启动 51 | icon: getMenuIcon('restart'), 52 | }, 53 | { 54 | label: t('menu.showLogs'), 55 | icon: getMenuIcon('log'), 56 | click: () => { 57 | shell.openPath(logPath); 58 | }, 59 | }, 60 | { 61 | label: t('menu.reportIssue'), 62 | icon: getMenuIcon('bug'), 63 | click: () => 64 | shell.openExternal('https://github.com/oeyoews/tiddlywiki-app/issues'), 65 | }, 66 | { 67 | label: t('menu.chromeExt'), 68 | icon: getMenuIcon('chrome-web-store'), 69 | click: () => { 70 | shell.openExternal('https://github.com/oeyoews/usewiki2'); 71 | }, 72 | }, 73 | { 74 | label: t('menu.twdocs'), 75 | icon: getMenuIcon('read'), 76 | click: () => { 77 | const isZH = i18next.language.startsWith('zh'); 78 | shell.openExternal( 79 | isZH 80 | ? 'https://bramchen.github.io/tw5-docs/zh-Hans' 81 | : 'https://tiddlywiki.com/' 82 | ); 83 | }, 84 | }, 85 | { 86 | label: t('menu.forum'), 87 | icon: getMenuIcon('link2'), 88 | click: () => shell.openExternal('https://talk.tiddlywiki.org/'), 89 | }, 90 | { 91 | label: t('menu.about'), 92 | click: showWikiInfo, 93 | icon: getMenuIcon('about'), 94 | }, 95 | ], 96 | }); 97 | -------------------------------------------------------------------------------- /src/utils/menubar/view.ts: -------------------------------------------------------------------------------- 1 | import { app, type MenuItemConstructorOptions, shell } from 'electron'; 2 | import path from 'path'; 3 | 4 | import { getFolderIcon, getMenuIcon } from '@/utils/icon'; 5 | import { server } from '@/utils'; 6 | import { config } from '../config'; 7 | import { t } from 'i18next'; 8 | import { getAllLocalIPv4Addresses } from '../getHost'; 9 | let showFindBar: any; 10 | 11 | app.on('browser-window-created', async (_: any, win: any) => { 12 | const { setFindBar } = await import('@/main/find-bar'); 13 | showFindBar = setFindBar(win, { top: 55 }); 14 | }); 15 | 16 | export const viewMenu = (): MenuItemConstructorOptions => ({ 17 | label: t('menu.view'), 18 | id: 'View', 19 | submenu: [ 20 | { 21 | role: 'togglefullscreen', 22 | icon: getMenuIcon('screens'), 23 | label: t('menu.toggleFullscreen'), 24 | accelerator: 'F11', 25 | }, 26 | { 27 | label: t('menu.toggleMenuBar'), 28 | icon: getMenuIcon('gear'), 29 | accelerator: 'Alt+M', 30 | click: () => { 31 | const isVisible = server.win.isMenuBarVisible(); 32 | server.win.setMenuBarVisibility(!isVisible); 33 | }, 34 | }, 35 | { 36 | label: t('menu.openInBrowser'), 37 | icon: getMenuIcon('web'), 38 | accelerator: 'CmdOrCtrl+Shift+O', 39 | click: () => { 40 | if (server.currentPort) { 41 | shell.openExternal(`http://localhost:${server.currentPort}`); 42 | } 43 | }, 44 | }, 45 | { 46 | label: t('menu.openFolder'), 47 | icon: getFolderIcon(), 48 | accelerator: 'CmdOrCtrl+E', 49 | click: () => { 50 | shell.openPath(config.get('wikiPath')); 51 | }, 52 | }, 53 | { 54 | label: t('menu.subwiki'), 55 | accelerator: 'CmdOrCtrl+Shift+E', 56 | icon: getMenuIcon('folder'), 57 | click: () => { 58 | shell.openPath(path.join(config.get('wikiPath'), 'subwiki')); 59 | }, 60 | }, 61 | { 62 | label: t('menu.showQRCode'), 63 | icon: getMenuIcon('qrcode'), 64 | visible: config.get('lan'), 65 | click: () => { 66 | const host = getAllLocalIPv4Addresses(); // 获取局域网地址 67 | server.win.webContents.send('show-qrcode', { 68 | host: host.pop(), 69 | port: server.currentPort, 70 | message: t('dialog.scalQRCode'), 71 | }); 72 | }, 73 | }, 74 | { type: 'separator' }, 75 | { 76 | role: 'resetZoom', 77 | icon: getMenuIcon('reset'), 78 | label: t('menu.resetZoom'), 79 | }, 80 | { 81 | role: 'zoomIn', 82 | label: t('menu.zoomIn'), 83 | icon: getMenuIcon('zoomIn'), 84 | accelerator: 'CmdOrCtrl+=', 85 | }, 86 | { 87 | role: 'zoomOut', 88 | label: t('menu.zoomOut'), 89 | icon: getMenuIcon('zoomOut'), 90 | }, 91 | { type: 'separator' }, 92 | { 93 | icon: getMenuIcon('search'), 94 | label: t('menu.search'), 95 | accelerator: 'CmdOrCtrl+F', 96 | click: showFindBar, 97 | }, 98 | { 99 | icon: getMenuIcon('minimize'), 100 | label: t('menu.minimize'), 101 | accelerator: 'CmdOrCtrl+W', 102 | // click: server.win.minimize.bind(server.win), 103 | click: () => server.win.minimize(), 104 | }, 105 | { 106 | role: 'reload', 107 | icon: getMenuIcon('reload'), 108 | label: t('menu.reload'), 109 | }, 110 | { 111 | role: 'forceReload', 112 | icon: getMenuIcon('reload'), 113 | label: t('menu.forceReload'), 114 | }, 115 | ], 116 | }); 117 | -------------------------------------------------------------------------------- /src/utils/downloadTpl.ts: -------------------------------------------------------------------------------- 1 | // @TODO: 缓存转换的文件夹, 使用模板的时候直接复制文件夹即可 2 | import { Notification, app, net } from 'electron'; 3 | import path from 'path'; 4 | import fs from 'fs'; 5 | // const { TiddlyWiki } = require('tiddlywiki'); 6 | const tempDir = app.getPath('temp'); // 获取系统的临时目录 7 | const tempHTMLFolder = (template: string) => path.join(tempDir, template); 8 | 9 | import { log } from '@/utils/logger'; 10 | import { shell } from 'electron'; 11 | import { dynamicWiki } from './tiddlywiki'; 12 | const cacheDuration = 7 * 24 * 60 * 60 * 1000; // 24小时 13 | 14 | export const downloadTpl = async ( 15 | tpl: [content: string, label: string], 16 | cbl: Function, 17 | notify: Notification 18 | ) => { 19 | const content = tpl[1]; 20 | const label = tpl[0]; 21 | const filePath = path.join(tempDir, `${label}.html`); 22 | const labelHTMLDir = tempHTMLFolder(label); 23 | 24 | // 检查文件是否存在且是否过期 25 | try { 26 | if (fs.existsSync(filePath)) { 27 | const stats = fs.statSync(filePath); 28 | const lastModified = stats.mtime.getTime(); 29 | const currentTime = Date.now(); 30 | 31 | // 如果文件在24小时内修改过,则直接复用缓存 32 | if (currentTime - lastModified < cacheDuration) { 33 | log.log(`${filePath} template is still valid, using cached version.`); 34 | if (!fs.existsSync(labelHTMLDir)) { 35 | await convertHTML2Folder(label, filePath, notify, cbl); 36 | } else { 37 | cbl(filePath); 38 | } 39 | return; 40 | } else { 41 | log.log(`${filePath} template is outdated, downloading again.`); 42 | } 43 | } 44 | 45 | // 如果文件不存在或过期,下载新的模板 46 | if (content.startsWith('http')) { 47 | const request = net.request(content); 48 | log.log(`${content} template is downloading!`); 49 | 50 | request.on('response', (response) => { 51 | let data = ''; 52 | notify.show(); 53 | 54 | response.on('data', (chunk) => { 55 | data += chunk.toString(); 56 | }); 57 | 58 | response.on('end', () => { 59 | // 使用流式写入避免卡顿 ?? 60 | const writeStream = fs.createWriteStream(filePath, 'utf-8'); 61 | writeStream.write(data); 62 | writeStream.end(); 63 | 64 | writeStream.on('finish', async () => { 65 | await convertHTML2Folder(label, filePath, notify, cbl), 66 | log.log(`${filePath} template has downloaded!`); 67 | }); 68 | 69 | writeStream.on('error', (error) => { 70 | log.error('写入文件时出错:', error); 71 | notify.close(); 72 | }); 73 | }); 74 | }); 75 | 76 | request.on('error', (error) => { 77 | log.error('下载失败:', error); 78 | notify.close(); 79 | }); 80 | 81 | request.end(); 82 | } 83 | } catch (error) { 84 | log.error('下载失败:', error); 85 | } 86 | }; 87 | 88 | export const capitalizeWords = (str: string) => 89 | str.replace(/\b\w/g, (char) => char.toUpperCase()); 90 | 91 | async function convertHTML2Folder( 92 | label: string, 93 | filePath: string, 94 | notify: Notification, 95 | cbl?: Function 96 | ) { 97 | // 转换文件夹 98 | const { boot } = dynamicWiki.TiddlyWiki(); 99 | const labelHTMLDir = tempHTMLFolder(label); 100 | if (fs.existsSync(labelHTMLDir)) { 101 | await shell.trashItem(labelHTMLDir); 102 | } 103 | fs.mkdirSync(labelHTMLDir); 104 | 105 | boot.argv = [ 106 | '--load', 107 | filePath, 108 | '--savewikifolder', 109 | labelHTMLDir, 110 | // '--verbose', 111 | ]; 112 | 113 | boot.boot(() => { 114 | log.info(label, 'convert successfully'); 115 | typeof cbl === 'function' && cbl(filePath); 116 | setTimeout(() => { 117 | notify.close(); 118 | }, 600); 119 | }); 120 | } 121 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TiddlyWiki App 🌟 2 | 3 | [简体中文](https://github.com/oeyoews/tiddlywiki-app/blob/main/README.zh-CN.md) | English 4 | 5 | ![img](https://github.com/oeyoews/tiddlywiki-app/raw/main/banner04.png) 6 | ![img](https://github.com/oeyoews/tiddlywiki-app/raw/main/banner03.png) 7 | 8 | A TiddlyWiki desktop application that provides a smoother desktop experience. 9 | 10 | ## ✨ Features 11 | 12 | - 🔧 System tray support, minimize to tray 13 | - 📂 Template import and single file import support 14 | - 📂 Markdown batch import support 15 | - 🔒 Encrypted HTML build support 16 | - 🚀 Multiple startup modes: 17 | - 💻 Local server mode 18 | - 🌐 Browser opening 19 | - 📄 Sub-Wiki support 20 | - 📝 Wiki management features: 21 | - 📂 Open/switch Wiki 22 | - 🔨 Build static Wiki 23 | - 📁 Open in system file manager 24 | - 🚀 One-click deploy to GitHub Pages 25 | - 🌍 Internationalization support 26 | - 🇨🇳 Simplified Chinese 27 | - 🇺🇸 English 28 | - 🔄 Auto-update functionality 29 | 30 | ## 📖 Usage Guide 31 | 32 | ### 🔰 Installation 33 | 34 | [Download tiddlywiki-app](https://github.com/oeyoews/tiddlywiki-app/releases) 35 | 36 | > For arch(or manjaro) user, you can install `pacman -U tiddlywiki-app-*.pacman`, or use PKGBUILD directly 37 | 38 | 41 | 42 | ### ⚡ Basic Operations 43 | 44 | 1. Use the menu bar or system tray: 45 | - 📋 File menu: 46 | - 📂 Open Wiki: Select other Wiki folders 47 | - 🔨 Build Wiki: Generate static HTML files 48 | - 🌐 Open in browser: Open current Wiki in default browser 49 | - 📁 Open current Wiki folder: View in file manager 50 | - 🔽 System tray: 51 | - 🖱️ Left click: Toggle window show/hide 52 | - 📌 Right-click menu: Quick access to common features 53 | 54 | ### ⌨️ Shortcuts 55 | 56 | - 🔽 Minimize: Window automatically hides to system tray 57 | - ❌ Close button: Defaults to minimize to tray, can fully exit via tray menu 58 | 59 | ## 👨‍💻 Development 60 | 61 | ### 🛠️ Requirements 62 | 63 | - 📦 Node.js 64 | - 📦 npm or yarn 65 | - 📦 git 66 | 67 | ### 🚀 Local Development 68 | 69 | ```bash 70 | # Install dependencies 71 | npm install 72 | 73 | # Start development server 74 | npm run dev 75 | ``` 76 | 77 | ## 🤔 Why create Tiddlywiki APP? 78 | 79 | The primary goal is to solve TiddlyWiki's long-standing save issues. While the community has provided many solutions, what could be simpler than downloading and installing an exe file? 80 | 81 | Secondly, what sets Tiddlywiki APP apart is that it doesn't interfere with users' Wikis or make any modifications - truly plug-and-play. I want it to be as simple as possible, allowing first-time TiddlyWiki users to start without learning additional concepts. 82 | 83 | Regarding the blank version, I want users to experience 100% pure TiddlyWiki when first encountering it, rather than being overwhelmed by plugins. This might be why Jermolene chose to provide a blank version for users' initial TiddlyWiki experience. 84 | 85 | As for concerns about "blank versions scaring away new users," I won't discuss that much here. While I hope more people learn about TiddlyWiki, I won't actively promote it, as currently, TiddlyWiki's ease of use doesn't offer significant promotional advantages. 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | ## 📚 Related Projects 94 | 95 | * [UseWiki2](https://github.com/oeyoews/usewiki2) 96 | 97 | 98 | 99 | --------------------------------------------------------------------------------