├── 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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/assets/menu/folder-opened.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/menu/conf.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/assets/menu/new-folder-template.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/assets/menu/markdown.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/menu/html.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/menu/search.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/assets/menu/module.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/menu/bug.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.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 |
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 |
--------------------------------------------------------------------------------
/assets/menu/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
28 |
--------------------------------------------------------------------------------
/assets/menu/tw-dark.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/menu/stop.svg:
--------------------------------------------------------------------------------
1 |
24 |
25 |
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 |
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 |
--------------------------------------------------------------------------------
/assets/menu/screens.svg:
--------------------------------------------------------------------------------
1 |
24 |
25 |
29 |
--------------------------------------------------------------------------------
/assets/menu/copy.svg:
--------------------------------------------------------------------------------
1 |
24 |
25 |
29 |
--------------------------------------------------------------------------------
/assets/menu/folder-wiki1-dark.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 | 
6 | 
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 | 
6 | 
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 |
--------------------------------------------------------------------------------