├── .github └── workflows │ └── main.yml ├── LICENSE ├── README.md ├── README_zh_CN.md ├── assets ├── AI_OP.gif ├── addPinyin.gif ├── baiduAI1.png ├── bilink1.gif ├── bilink1bottom.gif ├── bilink2.gif ├── bkAndText2ref.gif ├── bookmark.png ├── bottomBK.png ├── cardSusp.gif ├── cardpri1.gif ├── changeBG.gif ├── cleancards.gif ├── cmd.png ├── copAsImg.gif ├── createCard.gif ├── createXmid.gif ├── delCard.gif ├── fastref.gif ├── fixbrokenlnks.gif ├── flagBookmark.gif ├── gotoBlockID.gif ├── iconmenu.png ├── idea1.jpg ├── idea2.jpg ├── ideaconflict.png ├── ideadesktop.gif ├── knowledgeCfg.png ├── knowledgeCreateApp.png ├── knowledgeCreateAppID.png ├── knowledgeCreateKnowledge.png ├── knowledgeCreateKnowledgeID.png ├── knowledgeShow.gif ├── knowledgeShow2.gif ├── knowledgeShow3.png ├── knowledgeToken.png ├── longCopy.gif ├── longMove.gif ├── makeItBlur.gif ├── makeTab.gif ├── menu.png ├── mergeDocs.gif ├── move2dailynote.gif ├── moveDocs.gif ├── nextdailynote.png ├── openRefByClick.gif ├── overlay.gif ├── randVedio.gif ├── readingpoint.gif ├── removeCardsInDoc.gif ├── scheduleCopyID.gif ├── scheduleSetTime.gif ├── scheduleSetTime.png ├── settings.gif ├── statickLnk.gif ├── statustomato.png ├── taskrm.gif ├── text2ref.gif ├── text2refF3.gif ├── tidyAssets.gif ├── tomatoClockCfg.png └── uncorrelated.gif ├── dist ├── README.md ├── README_zh_CN.md ├── i18n │ ├── empty.drawio │ ├── empty.xmind │ ├── en_US.json │ ├── es_ES.json │ ├── fr_FR.json │ ├── ja_JP.json │ ├── zh_CHT.json │ └── zh_CN.json ├── icon.png ├── index.css ├── index.js ├── plugin.json └── preview.png ├── icon.png ├── package.json ├── plugin.json ├── preview.png ├── src ├── AIBox.ts ├── AIBoxMenu.svelte ├── BackLinkBottom.svelte ├── BackLinkBottomAutoRefresh.svelte ├── BackLinkBottomBox.ts ├── BackLinkBottomConTree.svelte ├── BackLinkBottomOnceRefresh.svelte ├── BuyTomato.svelte ├── CardBox.ts ├── CardBoxSettings.svelte ├── CardPriorityBar.svelte ├── CardPriorityBox.ts ├── CommentBox.svelte ├── CommentBox.ts ├── CozeSearchBox.ts ├── CozeSearchBoxMenu.svelte ├── CpBox.ts ├── DailyNoteBox.ts ├── DbBkBox.ts ├── FastNoteBox.ts ├── GraphBox.svelte ├── GraphBox.ts ├── GraphControl.svelte ├── GraphMenu.svelte ├── GraphNode.svelte ├── ImgBox.ts ├── ImgOverlayBox.svelte ├── ImgOverlayBox.ts ├── IndexConf.svelte ├── LinkBox.svelte ├── LinkBox.ts ├── LinkBoxBar.svelte ├── ListBox.ts ├── MindWire.ts ├── MixBox.ts ├── NoteBox.svelte ├── NoteBox.ts ├── NotebookSelect.svelte ├── ReadingPoint.svelte ├── ReadingPointBox.ts ├── Schedule.ts ├── Tag2RefBox.ts ├── TomatoClock.ts ├── TomatoClockVedio.svelte ├── TomatoVIP.svelte ├── ToolbarBox.ts ├── constants.ts ├── cssStyle.ts ├── exportFiles.ts ├── fold.ts ├── i18n │ ├── empty.drawio │ ├── empty.xmind │ ├── en_US.json │ ├── es_ES.json │ ├── fr_FR.json │ ├── ja_JP.json │ ├── zh_CHT.json │ └── zh_CN.json ├── icons.ts ├── index.scss ├── index.ts ├── libs │ ├── BaseTomatoPlugin.ts │ ├── DialogText.ts │ ├── DialogTextSv.svelte │ ├── EnumUtils.ts │ ├── Events.ts │ ├── SelectionML.ts │ ├── bkUtils.ts │ ├── bookmark.ts │ ├── cache.ts │ ├── cardUtils.ts │ ├── cozeAI.ts │ ├── destroyer.ts │ ├── docUtils.ts │ ├── focusUtils.ts │ ├── functional.ts │ ├── gconst.ts │ ├── globalUtils.ts │ ├── hash.ts │ ├── ial.ts │ ├── keyboard.ts │ ├── listUtils.ts │ ├── openAI.ts │ ├── progressive.ts │ ├── search.ts │ ├── stores.ts │ ├── switchDraft.ts │ ├── sydom.ts │ ├── taobaocode.ts │ ├── text1.ts │ ├── text10.ts │ ├── text11.ts │ ├── text2.ts │ ├── text3.ts │ ├── text4.ts │ ├── text5.ts │ ├── text6.ts │ ├── text7.ts │ ├── text8.ts │ ├── text9.ts │ ├── timer.ts │ ├── tools.ts │ ├── types.ts │ ├── ui.ts │ ├── user.ts │ ├── utils.ts │ └── winHotkey.ts ├── tomatoI18n.ts └── types │ ├── baidu.d.ts │ ├── siyuan.d.ts │ └── utils.d.ts ├── svelte.config.js ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Create Release on Tag Push 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | # env: 8 | # PROJ_PATH: sy-tomato-plugin 9 | # YARN_CACHE_DIR_PATH: "" 10 | # jobs: 11 | # build: 12 | # runs-on: ubuntu-latest 13 | # steps: 14 | # - name: Checkout workspace repo # https://github.com/marketplace/actions/checkout#checkout-multiple-repos-side-by-side 15 | # uses: actions/checkout@v4 16 | # with: 17 | # repository: IAliceBobI/sy-plugins 18 | 19 | # - name: Install Node.js 20 | # uses: actions/setup-node@v4 21 | # with: 22 | # node-version: 20 23 | # registry-url: "https://registry.npmjs.org" 24 | 25 | # - name: Install yarn 26 | # run: npm install -g yarn 27 | 28 | # - name: Set yarn cache directory 29 | # shell: bash 30 | # run: echo "YARN_CACHE_DIR_PATH=$(yarn cache dir)" >> $GITHUB_ENV 31 | 32 | # - name: Cache dependencies 33 | # uses: actions/cache@v4 34 | # with: 35 | # path: ${{ env.YARN_CACHE_DIR_PATH }} 36 | # key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 37 | # restore-keys: | 38 | # ${{ runner.os }}-yarn- 39 | 40 | # - name: Install dependencies 41 | # run: | 42 | # cd $PROJ_PATH 43 | # yarn install 44 | 45 | # - name: Build for production 46 | # run: | 47 | # cd $PROJ_PATH 48 | # yarn build 49 | # mv package.zip .. 50 | 51 | # - name: Release 52 | # uses: ncipollo/release-action@v1 53 | # with: 54 | # allowUpdates: true 55 | # artifactErrorsFailBuild: true 56 | # artifacts: "package.zip" 57 | # token: ${{ secrets.GITHUB_TOKEN }} 58 | # prerelease: false 59 | jobs: 60 | build: 61 | runs-on: ubuntu-latest 62 | 63 | steps: 64 | - name: Checkout repository 65 | uses: actions/checkout@v3 66 | 67 | - name: Run Script 68 | run: | 69 | cd dist 70 | zip -r package.zip ./* 71 | mv package.zip .. 72 | 73 | - name: Release 74 | uses: ncipollo/release-action@v1 75 | with: 76 | allowUpdates: true 77 | artifactErrorsFailBuild: true 78 | artifacts: "package.zip" 79 | token: ${{ secrets.GITHUB_TOKEN }} 80 | prerelease: false 81 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 SiYuan sy-tomato-plugin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [OPEN SOURCE 已开源 🔓](https://github.com/IAliceBobI/sy-tomato-plugin) 2 | 3 | # [DOC 帮助文档 📖](https://awx9773btw.feishu.cn/docx/IWPcd438yoL3C6xHC0xcOXDKnmh?from=from_copylink) 4 | 5 | # [ISSUE 反馈 交流 QQ频道 💬](https://pd.qq.com/s/2fh7nh7gz) 6 | 7 | # [CHANGELOG 更新日志 📅](https://awx9773btw.feishu.cn/docx/KekbdZ6Ozo4LLHxAGsncGTKJnff?from=from_copylink) 8 | 9 | # [ACKNOWLEDGMENTS 鸣谢 🙏](https://awx9773btw.feishu.cn/docx/FQ7udC3jeorfDYxI39ict2UNn2g?from=from_copylink) 10 | 11 | # POWERED BY LOVE 为爱发电 💗 12 | 13 |
14 | alipay 15 |
16 |
17 |
18 | wechat 19 |
20 | -------------------------------------------------------------------------------- /README_zh_CN.md: -------------------------------------------------------------------------------- 1 | # [OPEN SOURCE 已开源 🔓](https://github.com/IAliceBobI/sy-tomato-plugin) 2 | 3 | # [DOC 帮助文档 📖](https://awx9773btw.feishu.cn/docx/IWPcd438yoL3C6xHC0xcOXDKnmh?from=from_copylink) 4 | 5 | # [ISSUE 反馈 交流 QQ频道 💬](https://pd.qq.com/s/2fh7nh7gz) 6 | 7 | # [CHANGELOG 更新日志 📅](https://awx9773btw.feishu.cn/docx/KekbdZ6Ozo4LLHxAGsncGTKJnff?from=from_copylink) 8 | 9 | # [ACKNOWLEDGMENTS 鸣谢 🙏](https://awx9773btw.feishu.cn/docx/FQ7udC3jeorfDYxI39ict2UNn2g?from=from_copylink) 10 | 11 | # POWERED BY LOVE 为爱发电 💗 12 | 13 |
14 | alipay 15 |
16 |
17 |
18 | wechat 19 |
20 | -------------------------------------------------------------------------------- /assets/AI_OP.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/AI_OP.gif -------------------------------------------------------------------------------- /assets/addPinyin.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/addPinyin.gif -------------------------------------------------------------------------------- /assets/baiduAI1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/baiduAI1.png -------------------------------------------------------------------------------- /assets/bilink1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/bilink1.gif -------------------------------------------------------------------------------- /assets/bilink1bottom.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/bilink1bottom.gif -------------------------------------------------------------------------------- /assets/bilink2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/bilink2.gif -------------------------------------------------------------------------------- /assets/bkAndText2ref.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/bkAndText2ref.gif -------------------------------------------------------------------------------- /assets/bookmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/bookmark.png -------------------------------------------------------------------------------- /assets/bottomBK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/bottomBK.png -------------------------------------------------------------------------------- /assets/cardSusp.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/cardSusp.gif -------------------------------------------------------------------------------- /assets/cardpri1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/cardpri1.gif -------------------------------------------------------------------------------- /assets/changeBG.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/changeBG.gif -------------------------------------------------------------------------------- /assets/cleancards.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/cleancards.gif -------------------------------------------------------------------------------- /assets/cmd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/cmd.png -------------------------------------------------------------------------------- /assets/copAsImg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/copAsImg.gif -------------------------------------------------------------------------------- /assets/createCard.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/createCard.gif -------------------------------------------------------------------------------- /assets/createXmid.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/createXmid.gif -------------------------------------------------------------------------------- /assets/delCard.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/delCard.gif -------------------------------------------------------------------------------- /assets/fastref.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/fastref.gif -------------------------------------------------------------------------------- /assets/fixbrokenlnks.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/fixbrokenlnks.gif -------------------------------------------------------------------------------- /assets/flagBookmark.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/flagBookmark.gif -------------------------------------------------------------------------------- /assets/gotoBlockID.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/gotoBlockID.gif -------------------------------------------------------------------------------- /assets/iconmenu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/iconmenu.png -------------------------------------------------------------------------------- /assets/idea1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/idea1.jpg -------------------------------------------------------------------------------- /assets/idea2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/idea2.jpg -------------------------------------------------------------------------------- /assets/ideaconflict.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/ideaconflict.png -------------------------------------------------------------------------------- /assets/ideadesktop.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/ideadesktop.gif -------------------------------------------------------------------------------- /assets/knowledgeCfg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/knowledgeCfg.png -------------------------------------------------------------------------------- /assets/knowledgeCreateApp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/knowledgeCreateApp.png -------------------------------------------------------------------------------- /assets/knowledgeCreateAppID.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/knowledgeCreateAppID.png -------------------------------------------------------------------------------- /assets/knowledgeCreateKnowledge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/knowledgeCreateKnowledge.png -------------------------------------------------------------------------------- /assets/knowledgeCreateKnowledgeID.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/knowledgeCreateKnowledgeID.png -------------------------------------------------------------------------------- /assets/knowledgeShow.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/knowledgeShow.gif -------------------------------------------------------------------------------- /assets/knowledgeShow2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/knowledgeShow2.gif -------------------------------------------------------------------------------- /assets/knowledgeShow3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/knowledgeShow3.png -------------------------------------------------------------------------------- /assets/knowledgeToken.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/knowledgeToken.png -------------------------------------------------------------------------------- /assets/longCopy.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/longCopy.gif -------------------------------------------------------------------------------- /assets/longMove.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/longMove.gif -------------------------------------------------------------------------------- /assets/makeItBlur.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/makeItBlur.gif -------------------------------------------------------------------------------- /assets/makeTab.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/makeTab.gif -------------------------------------------------------------------------------- /assets/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/menu.png -------------------------------------------------------------------------------- /assets/mergeDocs.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/mergeDocs.gif -------------------------------------------------------------------------------- /assets/move2dailynote.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/move2dailynote.gif -------------------------------------------------------------------------------- /assets/moveDocs.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/moveDocs.gif -------------------------------------------------------------------------------- /assets/nextdailynote.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/nextdailynote.png -------------------------------------------------------------------------------- /assets/openRefByClick.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/openRefByClick.gif -------------------------------------------------------------------------------- /assets/overlay.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/overlay.gif -------------------------------------------------------------------------------- /assets/randVedio.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/randVedio.gif -------------------------------------------------------------------------------- /assets/readingpoint.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/readingpoint.gif -------------------------------------------------------------------------------- /assets/removeCardsInDoc.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/removeCardsInDoc.gif -------------------------------------------------------------------------------- /assets/scheduleCopyID.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/scheduleCopyID.gif -------------------------------------------------------------------------------- /assets/scheduleSetTime.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/scheduleSetTime.gif -------------------------------------------------------------------------------- /assets/scheduleSetTime.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/scheduleSetTime.png -------------------------------------------------------------------------------- /assets/settings.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/settings.gif -------------------------------------------------------------------------------- /assets/statickLnk.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/statickLnk.gif -------------------------------------------------------------------------------- /assets/statustomato.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/statustomato.png -------------------------------------------------------------------------------- /assets/taskrm.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/taskrm.gif -------------------------------------------------------------------------------- /assets/text2ref.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/text2ref.gif -------------------------------------------------------------------------------- /assets/text2refF3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/text2refF3.gif -------------------------------------------------------------------------------- /assets/tidyAssets.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/tidyAssets.gif -------------------------------------------------------------------------------- /assets/tomatoClockCfg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/tomatoClockCfg.png -------------------------------------------------------------------------------- /assets/uncorrelated.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/assets/uncorrelated.gif -------------------------------------------------------------------------------- /dist/README.md: -------------------------------------------------------------------------------- 1 | # [OPEN SOURCE 已开源 🔓](https://github.com/IAliceBobI/sy-tomato-plugin) 2 | 3 | # [DOC 帮助文档 📖](https://awx9773btw.feishu.cn/docx/IWPcd438yoL3C6xHC0xcOXDKnmh?from=from_copylink) 4 | 5 | # [ISSUE 反馈 交流 QQ频道 💬](https://pd.qq.com/s/2fh7nh7gz) 6 | 7 | # [CHANGELOG 更新日志 📅](https://awx9773btw.feishu.cn/docx/KekbdZ6Ozo4LLHxAGsncGTKJnff?from=from_copylink) 8 | 9 | # [ACKNOWLEDGMENTS 鸣谢 🙏](https://awx9773btw.feishu.cn/docx/FQ7udC3jeorfDYxI39ict2UNn2g?from=from_copylink) 10 | 11 | # POWERED BY LOVE 为爱发电 💗 12 | 13 |
14 | alipay 15 |
16 |
17 |
18 | wechat 19 |
20 | -------------------------------------------------------------------------------- /dist/README_zh_CN.md: -------------------------------------------------------------------------------- 1 | # [OPEN SOURCE 已开源 🔓](https://github.com/IAliceBobI/sy-tomato-plugin) 2 | 3 | # [DOC 帮助文档 📖](https://awx9773btw.feishu.cn/docx/IWPcd438yoL3C6xHC0xcOXDKnmh?from=from_copylink) 4 | 5 | # [ISSUE 反馈 交流 QQ频道 💬](https://pd.qq.com/s/2fh7nh7gz) 6 | 7 | # [CHANGELOG 更新日志 📅](https://awx9773btw.feishu.cn/docx/KekbdZ6Ozo4LLHxAGsncGTKJnff?from=from_copylink) 8 | 9 | # [ACKNOWLEDGMENTS 鸣谢 🙏](https://awx9773btw.feishu.cn/docx/FQ7udC3jeorfDYxI39ict2UNn2g?from=from_copylink) 10 | 11 | # POWERED BY LOVE 为爱发电 💗 12 | 13 |
14 | alipay 15 |
16 |
17 |
18 | wechat 19 |
20 | -------------------------------------------------------------------------------- /dist/i18n/empty.drawio: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /dist/i18n/empty.xmind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/dist/i18n/empty.xmind -------------------------------------------------------------------------------- /dist/i18n/en_US.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Tomato Timer", 3 | "takeARestPlease": "😊 Take a break!", 4 | "hasWorkedMinutes": "Minutes up", 5 | "takeARestAfterMinutes": "Take a break after", 6 | "startCountdown": "Start countdown", 7 | "cancelCountdown": "Cancel countdown", 8 | "cancelLastCountdown": "Cancel last countdown", 9 | "clickOneBlockFirst": "Please click on a block first", 10 | "setDate": "Set time", 11 | "setDateTitle": "Please set time for the following block", 12 | "scheduledAt": "Reminder time", 13 | "remind": "Remind", 14 | "scheduleSetSuccess": "Reminder set successfully", 15 | "schedule": "Set reminder", 16 | "btnAddADay": "+1 Day", 17 | "addBookmark": "Set reading point", 18 | "addBookmarkWithoutENV": "Set reading point (no environment)", 19 | "removeBrokenCards": "Clean all invalid flashcards cleanCards", 20 | "addFlashCard": "Single list, list card making, cancel card making", 21 | "removedBrokenCards": "Deleted invalid flashcards:", 22 | "thereIsNoInvalidCards": "No invalid flashcards!", 23 | "deleteBlocks": "Batch delete large continuous content blocks deleteBlocks", 24 | "moveBlocks": "Batch move large continuous content blocks moveBlocks", 25 | "copyBlocks": "Batch copy large continuous content blocks copyBlocks", 26 | "deleteBlocksHelp": "Batch delete help:

Please wrap the content to be processed with two lines aacc1 and aacc2.

aacc1
Today has good weather1!
Today has good weather2!
...
Today has good weather3!
aacc2
", 27 | "moveBlocksHelp": "Batch move copy help:

Please wrap the content to be processed with two lines aacc1 and aacc2. Then insert a line aacc3 at the target location.

[Document1]
aacc1
Today has good weather1!
Today has good weather2!
...
Today has good weather3!
aacc2
[Document2]
...
aacc3
...
(Document1 and Document2 can be the same document)
", 28 | "topBarTitleShowContents": "Open contents/bookmark page", 29 | "thereIsNoBookmark": "No bookmarks found", 30 | "showBookmarks": "View reading points", 31 | "lookingForTheList": "Searching for the list...", 32 | "reindex": "Unable to find the list to make cards. If you have indeed placed the cursor within a list item and tried multiple times, you can try rebuilding the index.", 33 | "wait4finish": "Please wait for the previous operation to finish!", 34 | "bilink": "Bidirectional link", 35 | "bilinkSelectBlock": "Bidirectional link: Select block", 36 | "bilinkCreateLnk": "Bidirectional link: Create reciprocal link", 37 | "backlink": "Backlink", 38 | "bottombacklink": "Insert bottom backlink #bottomBacklink", 39 | "bottomMention": "Insert bottom mention #bottomMention", 40 | "addPicOverlay": "Add picture overlay", 41 | "delCard": "Delete current flashcard during review", 42 | "skipCard": "Skip current flashcard during review", 43 | "moveBlock2today": "Move content to daily note", 44 | "previousNote": "Previous note", 45 | "nextNote": "Next note", 46 | "cardPrioritySet": "Document and sub-document flashcard priority", 47 | "setCardPriority": "Set flashcard priority", 48 | "uncheckAll": "Uncheck all completed todo tasks in the current document", 49 | "delAllchecked": "Delete all completed todo tasks in the current document", 50 | "refreshVirRef": "Refresh virtual reference", 51 | "removeDocCards": "Remove all flashcards in the current document", 52 | "hotMenu": "Quick menu", 53 | "spaceRepeat": "Spaced repetition", 54 | "locateDoc": "Global locate document", 55 | "baiduAI": "Send selected content to ERNIE Bot 4", 56 | "addTODOBookmark": "Add a 🚩 bookmark", 57 | "deleteAllTODOBookmarks": "Delete all 🚩 bookmarks", 58 | "txt2ref": "All content separated by spaces is converted to references (ignore content after ##)", 59 | "noteBox": "Capture fleeting thoughts", 60 | "noteBoxGlobal": "Capture fleeting thoughts (global)", 61 | "gotoBlockID": "Jump to block ID in clipboard (gotoBlockID)", 62 | "deleteBookmark": "Delete reading point in current document", 63 | "gotoBookmark": "Jump to reading point in current document" 64 | } -------------------------------------------------------------------------------- /dist/i18n/es_ES.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Temporizador de tomate", 3 | "takeARestPlease": "😊 Por favor, descansa un momento!", 4 | "hasWorkedMinutes": "minutos terminados", 5 | "takeARestAfterMinutes": "descansar después de minutos", 6 | "startCountdown": "iniciar cuenta regresiva", 7 | "cancelCountdown": "cancelar cuenta regresiva", 8 | "cancelLastCountdown": "cancelar la cuenta regresiva anterior", 9 | "clickOneBlockFirst": "por favor, haga clic en un bloque de contenido primero", 10 | "setDate": "establecer fecha", 11 | "setDateTitle": "por favor, establezca la hora para el siguiente bloque", 12 | "scheduledAt": "hora programada", 13 | "remind": "recordar", 14 | "scheduleSetSuccess": "recordatorio establecido con éxito", 15 | "schedule": "programar recordatorio", 16 | "btnAddADay": "+1 día", 17 | "addBookmark": "establecer punto de lectura", 18 | "addBookmarkWithoutENV": "establecer punto de lectura (sin entorno)", 19 | "removeBrokenCards": "limpiar todas las tarjetas rotas cleanCards", 20 | "addFlashCard": "lista de elementos individuales, crear tarjeta de lista, cancelar creación de tarjeta", 21 | "removedBrokenCards": "tarjetas rotas eliminadas: ", 22 | "thereIsNoInvalidCards": "¡no hay tarjetas inválidas!", 23 | "deleteBlocks": "eliminar bloques de contenido en masa deleteBlocks", 24 | "moveBlocks": "mover bloques de contenido en masa moveBlocks", 25 | "copyBlocks": "copiar bloques de contenido en masa copyBlocks", 26 | "deleteBlocksHelp": "ayuda para eliminar en masa:

por favor, envuelva el contenido que desea procesar con dos líneas aacc1 y aacc2.

aacc1
¡Hoy hace buen tiempo1!
¡Hoy hace buen tiempo2!
...
¡Hoy hace buen tiempo3!
aacc2
", 27 | "moveBlocksHelp": "ayuda para mover y copiar en masa:

por favor, envuelva el contenido con dos líneas aacc1 y aacc2. Luego inserte una línea aacc3 en la ubicación objetivo.

[Documento1]
aacc1
¡Hoy hace buen tiempo1!
¡Hoy hace buen tiempo2!
...
¡Hoy hace buen tiempo3!
aacc2
[Documento2]
...
aacc3
...
(Documento1 y Documento2 pueden ser el mismo documento)
", 28 | "topBarTitleShowContents": "abrir página de índice/marcadores", 29 | "thereIsNoBookmark": ":no se encontraron marcadores", 30 | "showBookmarks": "ver puntos de lectura", 31 | "lookingForTheList": "buscando la lista...", 32 | "reindex": "no se puede encontrar la lista para crear tarjetas, si realmente colocó el cursor dentro de un elemento de lista y lo intentó varias veces, puede intentar reconstruir el índice.", 33 | "wait4finish": "¡espere a que finalice la operación anterior!", 34 | "bilink": "enlace bidireccional", 35 | "bilinkSelectBlock": "enlace bidireccional: seleccionar bloque", 36 | "bilinkCreateLnk": "enlace bidireccional: crear enlace de ida y vuelta", 37 | "backlink": "enlace inverso backlink", 38 | "bottombacklink": "insertar enlace inverso en la parte inferior #bottomBacklink", 39 | "bottomMention": "insertar mención en la parte inferior #bottomMention", 40 | "addPicOverlay": "agregar capa de imagen", 41 | "delCard": "eliminar la tarjeta actual al repasar delete current card", 42 | "skipCard": "omitir la tarjeta actual al repasar skip current card", 43 | "moveBlock2today": "mover contenido a la nota diaria", 44 | "previousNote": "nota anterior", 45 | "nextNote": "nota siguiente", 46 | "cardPrioritySet": "prioridad de tarjetas para documentos y subdocumentos", 47 | "setCardPriority": "establecer prioridad de tarjetas", 48 | "uncheckAll": "desmarcar todas las tareas de todo completadas en el documento actual", 49 | "delAllchecked": "eliminar todas las tareas de todo completadas en el documento actual", 50 | "refreshVirRef": "actualizar referencias virtuales", 51 | "removeDocCards": "cancelar todas las tarjetas en el documento actual removeCardsInDoc", 52 | "hotMenu": "menú rápido", 53 | "spaceRepeat": "repetición de espacio", 54 | "locateDoc": "localizar documento globalmente", 55 | "baiduAI": "enviar contenido seleccionado a ERNIE 4", 56 | "addTODOBookmark": "agregar un marcador 🚩", 57 | "deleteAllTODOBookmarks": "eliminar todos los marcadores 🚩", 58 | "txt2ref": "convertir todo el contenido separado por espacios en referencias (ignorar el contenido después de ##)", 59 | "noteBox": "captura de ideas al vuelo", 60 | "noteBoxGlobal": "captura de ideas al vuelo (global)", 61 | "gotoBlockID": "ir al ID de bloque en el portapapeles (gotoBlockID)", 62 | "deleteBookmark": "eliminar el punto de lectura del documento actual rp", 63 | "gotoBookmark": "ir al punto de lectura del documento actual rp" 64 | } -------------------------------------------------------------------------------- /dist/i18n/fr_FR.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Chronomètre à tomates", 3 | "takeARestPlease": "😊 Prenez une pause !", 4 | "hasWorkedMinutes": "minutes écoulées", 5 | "takeARestAfterMinutes": "pause après minutes", 6 | "startCountdown": "Démarrer le compte à rebours", 7 | "cancelCountdown": "Annuler le compte à rebours", 8 | "cancelLastCountdown": "Annuler le compte à rebours précédent", 9 | "clickOneBlockFirst": "Veuillez cliquer d'abord sur un bloc de contenu", 10 | "setDate": "Définir l'heure", 11 | "setDateTitle": "Veuillez définir l'heure pour les blocs suivants", 12 | "scheduledAt": "Heure de rappel", 13 | "remind": "Rappeler", 14 | "scheduleSetSuccess": "Rappel configuré avec succès", 15 | "schedule": "Configurer un rappel", 16 | "btnAddADay": "+1 jour", 17 | "addBookmark": "Définir un point de lecture", 18 | "addBookmarkWithoutENV": "Définir un point de lecture (sans environnement)", 19 | "removeBrokenCards": "Nettoyer toutes les cartes à étiquettes invalides cleanCards", 20 | "addFlashCard": "Liste unidirectionnelle, création de cartes, annulation de la création de cartes", 21 | "removedBrokenCards": "Cartes à étiquettes invalides supprimées :", 22 | "thereIsNoInvalidCards": "Aucune carte à étiquettes invalide !", 23 | "deleteBlocks": "Supprimer en masse de gros blocs de contenu continu deleteBlocks", 24 | "moveBlocks": "Déplacer en masse de gros blocs de contenu continu moveBlocks", 25 | "copyBlocks": "Copier en masse de gros blocs de contenu continu copyBlocks", 26 | "deleteBlocksHelp": "Aide à la suppression en masse :

Veuillez entourer le contenu à traiter avec deux lignes aacc1 et aacc2.

aacc1
Il fait beau aujourd'hui 1 !
Il fait beau aujourd'hui 2 !
...
Il fait beau aujourd'hui 3 !
aacc2
", 27 | "moveBlocksHelp": "Aide au déplacement et à la copie en masse :

Entourez le contenu à traiter avec deux lignes aacc1 et aacc2. Insérez ensuite une ligne aacc3 à la destination.

[Document 1]
aacc1
Il fait beau aujourd'hui 1 !
Il fait beau aujourd'hui 2 !
...
Il fait beau aujourd'hui 3 !
aacc2
[Document 2]
...
aacc3
...
(Document 1 et Document 2 peuvent être le même document)
", 28 | "topBarTitleShowContents": "Ouvrir la page de contenu/page de signets", 29 | "thereIsNoBookmark": ":Aucun signet trouvé", 30 | "showBookmarks": "Voir les points de lecture", 31 | "lookingForTheList": "Recherche de la liste…", 32 | "reindex": "Liste pour créer des cartes introuvable, si vous avez vraiment placé le curseur à l'intérieur d'un élément de liste et essayé plusieurs fois, vous pouvez essayer de reconstruire l'index.", 33 | "wait4finish": "Veuillez attendre la fin de l'opération précédente !", 34 | "bilink": "Lien bidirectionnel", 35 | "bilinkSelectBlock": "Lien bidirectionnel : Sélectionner un bloc", 36 | "bilinkCreateLnk": "Lien bidirectionnel : Créer un lien aller-retour", 37 | "backlink": "Retour en arrière backlink", 38 | "bottombacklink": "Insérer un lien arrière en bas #bottomBacklink", 39 | "bottomMention": "Insérer une mention en bas #bottomMention", 40 | "addPicOverlay": "Ajouter un calque de masquage d'image", 41 | "delCard": "Supprimer la carte actuelle lors de la révision delete current card", 42 | "skipCard": "Passer la carte actuelle lors de la révision skip current card", 43 | "moveBlock2today": "Déplacer le contenu vers la note quotidienne", 44 | "previousNote": "Note précédente", 45 | "nextNote": "Note suivante", 46 | "cardPrioritySet": "Priorité des cartes à étiquettes pour le document et les sous-documents", 47 | "setCardPriority": "Définir la priorité des cartes à étiquettes", 48 | "uncheckAll": "Décocher toutes les tâches todo terminées du document actuel", 49 | "delAllchecked": "Supprimer toutes les tâches todo terminées du document actuel", 50 | "refreshVirRef": "Actualiser les références virtuelles", 51 | "removeDocCards": "Annuler toutes les cartes à étiquettes dans le document actuel removeCardsInDoc", 52 | "hotMenu": "Menu rapide", 53 | "spaceRepeat": "Répétition espacée", 54 | "locateDoc": "Localiser un document globalement", 55 | "baiduAI": "Sélectionner le contenu pour le transmettre à Wenxin Yiyan 4", 56 | "addTODOBookmark": "Ajouter un signet 🚩", 57 | "deleteAllTODOBookmarks": "Supprimer tous les signets 🚩", 58 | "txt2ref": "Tout le contenu séparé par des espaces est converti en référence (ignore le contenu après ##)", 59 | "noteBox": "Idée flash photographiée", 60 | "noteBoxGlobal": "Idée flash photographiée (global)", 61 | "gotoBlockID": "Aller au bloc ID dans le presse-papiers (gotoBlockID)", 62 | "deleteBookmark": "Supprimer le point de lecture du document actuel rp", 63 | "gotoBookmark": "Aller au point de lecture du document actuel rp" 64 | } -------------------------------------------------------------------------------- /dist/i18n/ja_JP.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "トマトクロック", 3 | "takeARestPlease": "😊 しばらく休憩しましょう!", 4 | "hasWorkedMinutes": "分間働いた", 5 | "takeARestAfterMinutes": "分後に休憩", 6 | "startCountdown": "カウントダウン開始", 7 | "cancelCountdown": "カウントダウンキャンセル", 8 | "cancelLastCountdown": "前回のカウントダウンをキャンセル", 9 | "clickOneBlockFirst": "まず内容ブロックをクリックしてください", 10 | "setDate": "時間設定", 11 | "setDateTitle": "以下のブロックに時間を設定してください", 12 | "scheduledAt": "リマインダー時間", 13 | "remind": "リマインド", 14 | "scheduleSetSuccess": "リマインダー設定成功", 15 | "schedule": "リマインダー設定", 16 | "btnAddADay": "+1日", 17 | "addBookmark": "読書ポイントを設定", 18 | "addBookmarkWithoutENV": "読書ポイントを設定(環境なし)", 19 | "removeBrokenCards": "無効なスマートカードをすべてクリーンアップ", 20 | "addFlashCard": "単一リスト、リストからカード作成、カード作成キャンセル", 21 | "removedBrokenCards": "削除された無効なスマートカード:", 22 | "thereIsNoInvalidCards": "無効なスマートカードはありません!", 23 | "deleteBlocks": "大量の連続した内容ブロックを一括削除", 24 | "moveBlocks": "大量の連続した内容ブロックを一括移動", 25 | "copyBlocks": "大量の連続した内容ブロックを一括コピー", 26 | "deleteBlocksHelp": "一括削除のヘルプ:

処理する内容をaacc1とaacc2の2行で囲んでください。

aacc1
今日はいい天気ですね1!
今日はいい天気ですね2!
...
今日はいい天気ですね3!
aacc2
", 27 | "moveBlocksHelp": "一括移動コピーのヘルプ:

処理する内容をaacc1とaacc2の2行で囲み、ターゲット位置にaacc3を挿入してください。

[ドキュメント1]
aacc1
今日はいい天気ですね1!
今日はいい天気ですね2!
...
今日はいい天気ですね3!
aacc2
[ドキュメント2]
...
aacc3
...
(ドキュメント1とドキュメント2は同じドキュメントでも可)
", 28 | "topBarTitleShowContents": "コンテンツページ/ブックマークページを開く", 29 | "thereIsNoBookmark": ":ブックマークはありません", 30 | "showBookmarks": "読書ポイントを表示", 31 | "lookingForTheList": "リストを検索中……", 32 | "reindex": "カード作成のためのリストが見つかりませんでした。カーソルをリストの項目内に置いて何度か試した場合は、インデックスを再構築することを試みてください。", 33 | "wait4finish": "前の操作が完了するまで待ってください!", 34 | "bilink": "双方向リンク", 35 | "bilinkSelectBlock": "双方向リンク:ブロックを選択", 36 | "bilinkCreateLnk": "双方向リンク:往復リンクを作成", 37 | "backlink": "バックリンク", 38 | "bottombacklink": "バックリンクをページ下部に挿入#bottomBacklink", 39 | "bottomMention": "ページ下部にメンションを挿入#bottomMention", 40 | "addPicOverlay": "画像オーバーレイを追加", 41 | "delCard": "復習時に現在のスマートカードを削除", 42 | "skipCard": "復習時に現在のスマートカードをスキップ", 43 | "moveBlock2today": "コンテンツをデイリーノートに移動", 44 | "previousNote": "前のノート", 45 | "nextNote": "次のノート", 46 | "cardPrioritySet": "ドキュメントとサブドキュメントのスマートカードの優先順位", 47 | "setCardPriority": "スマートカードの優先順位を設定", 48 | "uncheckAll": "現在のドキュメントで完了したすべてのTODOタスクのチェックを解除", 49 | "delAllchecked": "現在のドキュメントで完了したすべてのTODOタスクを削除", 50 | "refreshVirRef": "仮想リファレンスを更新", 51 | "removeDocCards": "現在のドキュメント内のすべてのスマートカードを削除", 52 | "hotMenu": "ホットメニュー", 53 | "spaceRepeat": "スペースリピート", 54 | "locateDoc": "ドキュメントのグローバルな位置指定", 55 | "baiduAI": "選択した内容を文心一言4に送信", 56 | "addTODOBookmark": "🚩ブックマークを追加", 57 | "deleteAllTODOBookmarks": "すべての🚩ブックマークを削除", 58 | "txt2ref": "スペースで区切られたすべての内容をリファレンスに変換(##の後の内容は無視)", 59 | "noteBox": "写真での閃き", 60 | "noteBoxGlobal": "写真での閃き(グローバル)", 61 | "gotoBlockID": "クリップボードのブロックIDにジャンプ", 62 | "deleteBookmark": "現在のドキュメントの読書ポイントを削除", 63 | "gotoBookmark": "現在のドキュメントの読書ポイントにジャンプ" 64 | } -------------------------------------------------------------------------------- /dist/i18n/zh_CHT.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "番茄鐘", 3 | "takeARestPlease": "😊休息一下吧!", 4 | "hasWorkedMinutes": "分鐘已到", 5 | "takeARestAfterMinutes": "分鐘後休息", 6 | "startCountdown": "開始計時", 7 | "cancelCountdown": "取消計時", 8 | "cancelLastCountdown": "取消上次的計時", 9 | "clickOneBlockFirst": "請先點擊一個內容塊", 10 | "setDate": "設置時間", 11 | "setDateTitle": "請為以下塊設置時間", 12 | "scheduledAt": "提醒時間", 13 | "remind": "提醒", 14 | "scheduleSetSuccess": "提醒設置成功", 15 | "schedule": "設置提醒", 16 | "btnAddADay": "+1日", 17 | "addBookmark": "設置閱讀點", 18 | "addBookmarkWithoutENV": "設置閱讀點(無環境)", 19 | "removeBrokenCards": "清理所有失效閃卡cleanCards", 20 | "addFlashCard": "單項列表、列表制卡、取消制卡", 21 | "removedBrokenCards": "已經刪除的失效閃卡:", 22 | "thereIsNoInvalidCards": "無失效閃卡!", 23 | "deleteBlocks": "批量刪除大量連續內容塊deleteBlocks", 24 | "moveBlocks": "批量移動大量連續內容塊moveBlocks", 25 | "copyBlocks": "批量複製大量連續內容塊copyBlocks", 26 | "deleteBlocksHelp": "批量刪除幫助:

請分別用 aacc1 與 aacc2 兩行把要處理的內容包裹起來。

aacc1
今天有個好天氣1!
今天有個好天氣2!
...
今天有個好天氣3!
aacc2
", 27 | "moveBlocksHelp": "批量移動複製幫助:

請分別用 aacc1 與 aacc2 兩行把要處理的內容包裹起來。再到目標位置插入一行 aacc3。

[文檔1]
aacc1
今天有個好天氣1!
今天有個好天氣2!
...
今天有個好天氣3!
aacc2
[文檔2]
...
aacc3
...
(文檔1與文檔2可以是同一個文檔)
", 28 | "topBarTitleShowContents": "打開目錄頁/書籤頁", 29 | "thereIsNoBookmark": ":找不到任何書籤", 30 | "showBookmarks": "查看閱讀點", 31 | "lookingForTheList": "正在查找列表……", 32 | "reindex": "無法找到要制卡的列表,如果你確實是將光標放到列表的某個列表項內嘗試多次,你可以嘗試重建索引。", 33 | "wait4finish": "請等待上個操作完成!", 34 | "bilink": "雙向互鏈", 35 | "bilinkSelectBlock": "雙向互鏈:選擇塊", 36 | "bilinkCreateLnk": "雙向互鏈:創建往返鏈", 37 | "backlink": "反鏈backlink", 38 | "bottombacklink": "插入底部反鏈#bottomBacklink", 39 | "bottomMention": "插入底部提及#bottomMention", 40 | "addPicOverlay": "添加圖片遮擋層", 41 | "delCard": "複習時刪除當前閃卡delete current card", 42 | "skipCard": "複習時跳過當前閃卡skip current card", 43 | "moveBlock2today": "移動內容到 daily note", 44 | "previousNote": "上一個日誌", 45 | "nextNote": "下一個日誌", 46 | "cardPrioritySet": "文檔與子文檔閃卡優先級", 47 | "setCardPriority": "設置閃卡優先級", 48 | "uncheckAll": "取消勾選當前文檔所有已完成的todo任務", 49 | "delAllchecked": "刪除當前文檔所有已完成的todo任務", 50 | "refreshVirRef": "刷新虛擬引用", 51 | "removeDocCards": "取消當前文檔內所有閃卡removeCardsInDoc", 52 | "hotMenu": "快捷菜單", 53 | "spaceRepeat": "間隔重複", 54 | "locateDoc": "全局定位文檔", 55 | "baiduAI": "選中內容發給文心一言4", 56 | "addTODOBookmark": "添加一個🚩書籤", 57 | "deleteAllTODOBookmarks": "刪除所有🚩書籤", 58 | "txt2ref": "空格隔開的所有內容都轉為引用(忽略##後的內容)", 59 | "noteBox": "拍照閃念", 60 | "noteBoxGlobal": "拍照閃念(全局)", 61 | "gotoBlockID": "跳轉到剪貼板中的塊ID(gotoBlockID)", 62 | "deleteBookmark": "刪除當前文檔的閱讀點rp", 63 | "gotoBookmark": "跳到當前文檔的閱讀點rp" 64 | } -------------------------------------------------------------------------------- /dist/i18n/zh_CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "番茄钟", 3 | "takeARestPlease": "😊休息一会儿吧!", 4 | "hasWorkedMinutes": "分钟已到", 5 | "takeARestAfterMinutes": "分钟后休息", 6 | "startCountdown": "开始计时", 7 | "cancelCountdown": "取消计时", 8 | "cancelLastCountdown": "取消上次的计时", 9 | "clickOneBlockFirst": "请先点击一个内容块", 10 | "setDate": "设置时间", 11 | "setDateTitle": "请为以下块设置时间", 12 | "scheduledAt": "提醒时间", 13 | "remind": "提醒", 14 | "scheduleSetSuccess": "提醒设置成功", 15 | "schedule": "设置提醒", 16 | "btnAddADay": "+1日", 17 | "addBookmark": "设置阅读点", 18 | "addBookmarkWithoutENV": "设置阅读点(无环境)", 19 | "removeBrokenCards": "清理所有失效闪卡cleanCards", 20 | "addFlashCard": "单项列表、列表制卡、取消制卡", 21 | "removedBrokenCards": "已经删除的失效闪卡:", 22 | "thereIsNoInvalidCards": "无失效闪卡!", 23 | "deleteBlocks": "批量删除大量连续内容块deleteBlocks", 24 | "moveBlocks": "批量移动大量连续内容块moveBlocks", 25 | "copyBlocks": "批量复制大量连续内容块copyBlocks", 26 | "deleteBlocksHelp": "批量删除帮助:

请分别用 aacc1 与 aacc2 两行把要处理的内容包裹起来。

aacc1
今天有个好天气1!
今天有个好天气2!
...
今天有个好天气3!
aacc2
", 27 | "moveBlocksHelp": "批量移动复制帮助:

请分别用 aacc1 与 aacc2 两行把要处理的内容包裹起来。再到目标位置插入一行 aacc3。

[文档1]
aacc1
今天有个好天气1!
今天有个好天气2!
...
今天有个好天气3!
aacc2
[文档2]
...
aacc3
...
(文档1与文档2可以是同一个文档)
", 28 | "topBarTitleShowContents": "打开目录页/书签页", 29 | "thereIsNoBookmark": ":找不到任何书签", 30 | "showBookmarks": "查看阅读点", 31 | "lookingForTheList": "正在查找列表……", 32 | "reindex": "无法找到要制卡的列表,如果你确实是将光标放到列表的某个列表项内尝试多次,你可以尝试重建索引。", 33 | "wait4finish": "请等待上个操作完成!", 34 | "bilink": "双向互链", 35 | "bilinkSelectBlock": "双向互链:选择块", 36 | "bilinkCreateLnk": "双向互链:创建往返链", 37 | "backlink": "反链backlink", 38 | "bottombacklink": "插入底部反链#bottomBacklink", 39 | "bottomMention": "插入底部提及#bottomMention", 40 | "addPicOverlay": "添加图片遮挡层", 41 | "delCard": "复习时删除当前闪卡delete current card", 42 | "skipCard": "复习时跳过当前闪卡skip current card", 43 | "moveBlock2today": "移动内容到 daily note", 44 | "previousNote": "上一个日志", 45 | "nextNote": "下一个日志", 46 | "cardPrioritySet": "文档与子文档闪卡优先级", 47 | "setCardPriority": "设置闪卡优先级", 48 | "uncheckAll": "取消勾选当前文档所有已完成的todo任务", 49 | "delAllchecked": "删除当前文档所有已完成的todo任务", 50 | "refreshVirRef": "刷新虚拟引用", 51 | "removeDocCards": "取消当前文档内所有闪卡removeCardsInDoc", 52 | "hotMenu": "快捷菜单", 53 | "spaceRepeat": "间隔重复", 54 | "locateDoc": "全局定位文档", 55 | "baiduAI": "选中内容发给文心一言4", 56 | "addTODOBookmark": "添加一个🚩书签", 57 | "deleteAllTODOBookmarks": "删除所有🚩书签", 58 | "txt2ref": "空格隔开的所有内容都转为引用(忽略##后的内容)", 59 | "noteBox": "拍照闪念", 60 | "noteBoxGlobal": "拍照闪念(全局)", 61 | "gotoBlockID": "跳转到剪贴板中的块ID(gotoBlockID)", 62 | "deleteBookmark": "删除当前文档的阅读点rp", 63 | "gotoBookmark": "跳到当前文档的阅读点rp" 64 | } -------------------------------------------------------------------------------- /dist/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/dist/icon.png -------------------------------------------------------------------------------- /dist/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sy-tomato-plugin", 3 | "author": "IAliceBobI", 4 | "url": "https://github.com/IAliceBobI/sy-tomato-plugin", 5 | "version": "4.0.64", 6 | "minAppVersion": "3.0.3", 7 | "backends": [ 8 | "all" 9 | ], 10 | "frontends": [ 11 | "all" 12 | ], 13 | "displayName": { 14 | "default": "Tomato Toolbox", 15 | "zh_CN": "番茄工具箱" 16 | }, 17 | "description": { 18 | "default": "Status Bar Pomodoro, Annotation, Bottom Backlinks, Sync Blocks, Block Relationship Graph, Text to Reference, Flash Idea via Camera, Reading Points, Two-Way Mutual Links, AI Knowledge Base Q&A, Streaming AI, Database Backlinks, Flashcard Priority, Document Merge, Recurring Reminders, Delete Cards During Review, Flashcard Image Cloze, Move to Today's Notes, Clean Up Invalid Flashcards", 19 | "zh_CN": "【已开源】状态栏番茄钟、移动端多选、增强折叠、批注、底部反链、同步块、思维导线、块关系图、文本转引用、拍照闪念、阅读点、双向互链、AI知识库问答、流式AI、数据库反链、闪卡优先级、文档合并、定期提醒、复习时删卡、闪卡图片挖空、移到今日笔记、清理失效闪卡" 20 | }, 21 | "readme": { 22 | "default": "README.md", 23 | "zh_CN": "README_zh_CN.md" 24 | }, 25 | "funding": { 26 | "openCollective": "", 27 | "patreon": "", 28 | "github": "", 29 | "custom": [ 30 | "https://afdian.com/a/playerv5" 31 | ] 32 | }, 33 | "keywords": [ 34 | "TomatoClock", 35 | "Time management", 36 | "Productivity", 37 | "Pomodoro technique", 38 | "Timer plugin", 39 | "Task scheduling", 40 | "Focus sessions", 41 | "Work intervals", 42 | "Breaks", 43 | "Time tracking", 44 | "Timeboxing", 45 | "Time optimization", 46 | "Efficiency", 47 | "Work-life balance", 48 | "Distraction management", 49 | "Task prioritization", 50 | "Time blocking", 51 | "Time visualization", 52 | "Progress tracking", 53 | "Goal setting" 54 | ] 55 | } 56 | -------------------------------------------------------------------------------- /dist/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/dist/preview.png -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/icon.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sy-tomato-plugin", 3 | "version": "0.0.1", 4 | "description": "", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite build --watch", 8 | "build": "vite build" 9 | }, 10 | "keywords": [ 11 | "TomatoClock", 12 | "Time management", 13 | "Productivity", 14 | "Pomodoro technique", 15 | "Timer plugin", 16 | "Task scheduling", 17 | "Focus sessions", 18 | "Work intervals", 19 | "Breaks", 20 | "Time tracking", 21 | "Timeboxing", 22 | "Time optimization", 23 | "Efficiency", 24 | "Work-life balance", 25 | "Distraction management", 26 | "Task prioritization", 27 | "Time blocking", 28 | "Time visualization", 29 | "Progress tracking", 30 | "Goal setting" 31 | ], 32 | "author": "IAliceBobI", 33 | "license": "MIT", 34 | "devDependencies": { 35 | "@sveltejs/vite-plugin-svelte": "^3.0.0", 36 | "@tsconfig/svelte": "^4.0.1", 37 | "@types/elliptic": "^6.4.18", 38 | "@types/fabric": "^5.3.7", 39 | "@types/node": "^20.12.7", 40 | "fast-glob": "^3.2.12", 41 | "glob": "^11.0.0", 42 | "minimist": "^1.2.8", 43 | "rollup-plugin-livereload": "^2.0.5", 44 | "sass": "^1.75.0", 45 | "siyuan": "*", 46 | "svelte": "^4.2.0", 47 | "ts-node": "^10.9.1", 48 | "typescript": "^5.4.5", 49 | "vite": "^5.0.0", 50 | "vite-plugin-static-copy": "^1.0.2", 51 | "vite-plugin-zip-pack": "^1.2.3" 52 | }, 53 | "dependencies": { 54 | "@dagrejs/dagre": "^1.1.4", 55 | "@types/uuid": "^9.0.8", 56 | "@xyflow/svelte": "^0.1.23", 57 | "elliptic": "^6.5.7", 58 | "fabric": "^5.4.0", 59 | "html2canvas": "^1.4.1", 60 | "moment-timezone": "^0.5.45", 61 | "openai": "^4.49.1", 62 | "pinyin-pro": "^3.20.3", 63 | "terser": "^5.31.6", 64 | "ts-md5": "^1.3.1", 65 | "uuid": "^9.0.1" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sy-tomato-plugin", 3 | "author": "IAliceBobI", 4 | "url": "https://github.com/IAliceBobI/sy-tomato-plugin", 5 | "version": "4.0.64", 6 | "minAppVersion": "3.0.3", 7 | "backends": [ 8 | "all" 9 | ], 10 | "frontends": [ 11 | "all" 12 | ], 13 | "displayName": { 14 | "default": "Tomato Toolbox", 15 | "zh_CN": "番茄工具箱" 16 | }, 17 | "description": { 18 | "default": "Status Bar Pomodoro, Annotation, Bottom Backlinks, Sync Blocks, Block Relationship Graph, Text to Reference, Flash Idea via Camera, Reading Points, Two-Way Mutual Links, AI Knowledge Base Q&A, Streaming AI, Database Backlinks, Flashcard Priority, Document Merge, Recurring Reminders, Delete Cards During Review, Flashcard Image Cloze, Move to Today's Notes, Clean Up Invalid Flashcards", 19 | "zh_CN": "【已开源】状态栏番茄钟、移动端多选、增强折叠、批注、底部反链、同步块、思维导线、块关系图、文本转引用、拍照闪念、阅读点、双向互链、AI知识库问答、流式AI、数据库反链、闪卡优先级、文档合并、定期提醒、复习时删卡、闪卡图片挖空、移到今日笔记、清理失效闪卡" 20 | }, 21 | "readme": { 22 | "default": "README.md", 23 | "zh_CN": "README_zh_CN.md" 24 | }, 25 | "funding": { 26 | "openCollective": "", 27 | "patreon": "", 28 | "github": "", 29 | "custom": [ 30 | "https://afdian.com/a/playerv5" 31 | ] 32 | }, 33 | "keywords": [ 34 | "TomatoClock", 35 | "Time management", 36 | "Productivity", 37 | "Pomodoro technique", 38 | "Timer plugin", 39 | "Task scheduling", 40 | "Focus sessions", 41 | "Work intervals", 42 | "Breaks", 43 | "Time tracking", 44 | "Timeboxing", 45 | "Time optimization", 46 | "Efficiency", 47 | "Work-life balance", 48 | "Distraction management", 49 | "Task prioritization", 50 | "Time blocking", 51 | "Time visualization", 52 | "Progress tracking", 53 | "Goal setting" 54 | ] 55 | } 56 | -------------------------------------------------------------------------------- /preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/preview.png -------------------------------------------------------------------------------- /src/AIBox.ts: -------------------------------------------------------------------------------- 1 | import { Dialog, IEventBusMap } from "siyuan"; 2 | import { getAllText, newID, } from "./libs/utils"; 3 | import { events } from "./libs/Events"; 4 | import { aiBoxCheckbox, aiBoxMenuShow, } from "./libs/stores"; 5 | import AIBoxMenu from "./AIBoxMenu.svelte"; 6 | import { DestroyManager } from "./libs/destroyer"; 7 | import { tomatoI18n } from "./tomatoI18n"; 8 | import { BaseTomatoPlugin } from "./libs/BaseTomatoPlugin"; 9 | import { winHotkey } from "./libs/winHotkey"; 10 | 11 | type TomatoMenu = IEventBusMap["click-blockicon"] & IEventBusMap["open-menu-content"]; 12 | export const AIBoxHotkey = winHotkey("⌥⇧S", "人工智能2024-6-9 01:58:39", "💻", () => tomatoI18n.人工智能) 13 | class AIBox { 14 | private plugin: BaseTomatoPlugin; 15 | private dm: DestroyManager; 16 | 17 | async onload(plugin: BaseTomatoPlugin) { 18 | if (!aiBoxCheckbox.get()) return; 19 | 20 | this.plugin = plugin; 21 | this.plugin.addCommand({ 22 | langKey: AIBoxHotkey.langKey, 23 | langText: AIBoxHotkey.langText(), 24 | hotkey: AIBoxHotkey.m, 25 | editorCallback: async (protyle) => { 26 | const { selected, ids } = await events.selectedDivs(protyle); 27 | const id = ids.pop(); 28 | await this.ai(id, getAllText(selected)); 29 | }, 30 | }); 31 | 32 | if (aiBoxMenuShow.get()) { 33 | this.plugin.eventBus.on("open-menu-content", ({ detail }) => { 34 | this.aiMenu(detail as any); 35 | }); 36 | } 37 | } 38 | 39 | blockIconEvent(detail: IEventBusMap["click-blockicon"]) { 40 | if (!this.plugin) return; 41 | if (aiBoxMenuShow.get()) { 42 | this.aiMenu(detail as any); 43 | } 44 | } 45 | 46 | aiMenu(detail: TomatoMenu) { 47 | const menu = detail.menu; 48 | menu.addItem({ 49 | label: AIBoxHotkey.langText(), 50 | iconHTML: AIBoxHotkey.icon, 51 | accelerator: AIBoxHotkey.m, 52 | click: async () => { 53 | const { selected, ids } = await events.selectedDivs(detail.protyle); 54 | const id = ids.pop(); 55 | await this.ai(id, getAllText(selected)); 56 | }, 57 | }); 58 | } 59 | 60 | private async ai(anchorID: string, text: string) { 61 | if (!anchorID) return; 62 | if (this.dm) { 63 | this.dm.run(); 64 | this.dm.destroyBy(); 65 | } else { 66 | this.dm?.destroyBy(); 67 | this.dm = new DestroyManager(); 68 | const id = newID(); 69 | const dialog = new Dialog({ 70 | title: AIBoxHotkey.langText(), 71 | content: `
`, 72 | width: events.isMobile ? "90vw" : "700px", 73 | height: events.isMobile ? "180svw" : null, 74 | destroyCallback: () => { 75 | this.dm?.destroyBy("1") 76 | }, 77 | }); 78 | const d = new AIBoxMenu({ 79 | target: dialog.element.querySelector("#" + id), 80 | props: { 81 | dm: this.dm, 82 | text, 83 | anchorID, 84 | } 85 | }); 86 | this.dm.add("1", () => { dialog.destroy() }) 87 | this.dm.add("2", () => { d.$destroy() }) 88 | this.dm.add("dm", () => { this.dm = null; }) 89 | } 90 | } 91 | } 92 | 93 | export const aiBox = new AIBox(); 94 | 95 | 96 | -------------------------------------------------------------------------------- /src/AIBoxMenu.svelte: -------------------------------------------------------------------------------- 1 | 96 | 97 | 98 |
99 | 105 |
106 | 111 | 120 |
121 |
122 | 127 | {#each $aiBoxPrompts as item} 128 | 133 | {/each} 134 |
135 | 138 |
139 | 140 | 148 | -------------------------------------------------------------------------------- /src/BackLinkBottomAutoRefresh.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 25 | -------------------------------------------------------------------------------- /src/BackLinkBottomConTree.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 |
16 | {#if trees?.size > 0} 17 | {#each trees as [k, v]} 18 | 27 | 28 | {/each} 29 | {/if} 30 |
31 | 32 | 37 | -------------------------------------------------------------------------------- /src/BackLinkBottomOnceRefresh.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 14 | 15 | 25 | -------------------------------------------------------------------------------- /src/BuyTomato.svelte: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |
27 | {#if Siyuan?.user?.userName} 28 | {Siyuan?.user?.userName} 29 | {/if} 30 |
31 |
32 | {#if Siyuan?.user?.userAvatarURL} 33 | userAvatar 38 | {/if} 39 |
40 |
41 | 42 | 43 | {#if isTomato} 44 | {tomatoI18n.番茄工具箱} 45 | {:else} 46 | {tomatoI18n.渐进学习} 47 | {/if} 48 | 49 | {tomatoI18n.功能特性} 50 | 51 | 52 |
53 | ¥{nextPrice} 54 | {"¥" + price + SPACE} 55 | {tomatoI18n.终身} 56 |
57 | {tomatoI18n.将激活配置中有VIP标志的功能} 58 | {tomatoI18n.一次付费终身使用} 59 | {tomatoI18n.离线可用新功能可用} 60 | {tomatoI18n.vip版本上线前打赏的金额可以双倍抵扣} 61 |

{tomatoI18n.广告语(price)}

62 |
63 | 64 |
65 | {tomatoI18n.点击复制按钮进入店铺将复制的内容发给客服} 66 |
67 | 68 |
69 | {#if $userID} 70 | 76 | {:else} 77 | 78 | {tomatoI18n.如果要激活插件请先登录思源本体的账户} 79 | 80 | {/if} 81 | {tomatoI18n.点击打开商品} 86 |
87 | 88 |
89 | taobao 90 |
91 |
92 | {tomatoI18n.淘宝店二维码} 93 |
94 |
95 | {tomatoI18n.可联系客服获取7天试用激活码} 96 |
97 |
98 | 99 | 160 | -------------------------------------------------------------------------------- /src/CozeSearchBox.ts: -------------------------------------------------------------------------------- 1 | import { Dialog, IEventBusMap } from "siyuan"; 2 | import { DestroyManager } from "./libs/destroyer"; 3 | import { tomatoI18n } from "./tomatoI18n"; 4 | import { events } from "./libs/Events"; 5 | import { getAllText, newID, siyuan, } from "./libs/utils"; 6 | import CozeSearchBoxMenu from "./CozeSearchBoxMenu.svelte" 7 | import { BaseTomatoPlugin } from "./libs/BaseTomatoPlugin"; 8 | import { cozeSearchBoxCheckbox, cozeSearchMenuShow } from "./libs/stores"; 9 | import { winHotkey } from "./libs/winHotkey"; 10 | 11 | type TomatoMenu = IEventBusMap["click-blockicon"] & IEventBusMap["open-menu-content"]; 12 | 13 | export const CozeSearchBoxHotkey = winHotkey("⌘⇧o", "coze 2025-1-6 11:44:34",) 14 | class CozeSearchBox { 15 | private plugin: BaseTomatoPlugin; 16 | 17 | async onload(plugin: BaseTomatoPlugin) { 18 | if (!cozeSearchBoxCheckbox.get()) return; 19 | this.plugin = plugin; 20 | 21 | this.plugin.addCommand({ 22 | langKey: CozeSearchBoxHotkey.langKey, 23 | langText: "coze" + tomatoI18n.知识库问答, 24 | hotkey: CozeSearchBoxHotkey.m, 25 | callback: async () => { 26 | try { 27 | const { selected, ids } = await events.selectedDivs(); 28 | const id = ids.pop(); 29 | await this.semantic(id, getAllText(selected)); 30 | } catch { 31 | await this.semantic("", ""); 32 | } 33 | }, 34 | }); 35 | 36 | if (cozeSearchMenuShow.get()) { 37 | this.plugin.eventBus.on("open-menu-content", ({ detail }) => { 38 | this.aiMenu(detail as any); 39 | }); 40 | } 41 | } 42 | 43 | blockIconEvent(detail: IEventBusMap["click-blockicon"]) { 44 | if (!this.plugin) return; 45 | if (cozeSearchMenuShow.get()) { 46 | this.aiMenu(detail as any); 47 | } 48 | } 49 | 50 | aiMenu(detail: TomatoMenu) { 51 | const menu = detail.menu; 52 | menu.addItem({ 53 | label: "coze" + tomatoI18n.知识库问答, 54 | iconHTML: "🔍", 55 | accelerator: CozeSearchBoxHotkey.m, 56 | click: async () => { 57 | const { selected, ids } = await events.selectedDivs(detail.protyle); 58 | const id = ids.pop(); 59 | await this.semantic(id, getAllText(selected)); 60 | }, 61 | }); 62 | } 63 | 64 | private async semantic(anchorID: string, text: string) { 65 | if (events.isBrowser) { 66 | siyuan.pushMsg("can not run in browser env."); 67 | return; 68 | } 69 | const dm = new DestroyManager(); 70 | const id = newID(); 71 | const dialog = new Dialog({ 72 | title: "coze" + tomatoI18n.知识库问答, 73 | content: `
`, 74 | width: events.isMobile ? "90vw" : "400px", 75 | height: events.isMobile ? "180svw" : null, 76 | destroyCallback: () => { 77 | dm.destroyBy("1") 78 | }, 79 | }); 80 | const d = new CozeSearchBoxMenu({ 81 | target: dialog.element.querySelector("#" + id), 82 | props: { 83 | dm, 84 | text, 85 | anchorID, 86 | plugin: this.plugin, 87 | } 88 | }); 89 | dm.add("1", () => { dialog.destroy() }) 90 | dm.add("2", () => { d.$destroy() }) 91 | } 92 | } 93 | 94 | export const cozeSearchBox = new CozeSearchBox(); 95 | 96 | 97 | -------------------------------------------------------------------------------- /src/CpBox.ts: -------------------------------------------------------------------------------- 1 | import { siyuan, } from "./libs/utils"; 2 | import "./index.scss"; 3 | import { BaseTomatoPlugin } from "./libs/BaseTomatoPlugin"; 4 | import { cpBoxCheckbox } from "./libs/stores"; 5 | import { tomatoI18n } from "./tomatoI18n"; 6 | import { OpenSyFile2 } from "./libs/docUtils"; 7 | import { events } from "./libs/Events"; 8 | import { winHotkey } from "./libs/winHotkey"; 9 | 10 | const LongContentOpsLock = "LongContentOpsLock"; 11 | 12 | export const CpBox批量删除大量连续内容块 = winHotkey("alt+shift+;", "deleteBlocks 2025-5-10 23:28:29") 13 | export const CpBox批量移动大量连续内容块 = winHotkey("alt+shift+'", "moveBlocks 2025-5-10 23:29:52") 14 | export const CpBox批量复制大量连续内容块 = winHotkey("alt+shift+q", "copyBlocks 2025-5-10 23:31:46") 15 | 16 | class CpBox { 17 | private plugin: BaseTomatoPlugin; 18 | 19 | async onload(plugin: BaseTomatoPlugin) { 20 | if (!cpBoxCheckbox.get()) return; 21 | 22 | this.plugin = plugin; 23 | this.plugin.addCommand({ 24 | langKey: CpBox批量删除大量连续内容块.langKey, 25 | langText: tomatoI18n.批量删除大量连续内容块, 26 | hotkey: CpBox批量删除大量连续内容块.m, 27 | callback: async () => { 28 | navigator.locks.request(LongContentOpsLock, { ifAvailable: true }, async (lock) => { 29 | if (lock) { 30 | await this.deleteBlocks(); 31 | } else { 32 | siyuan.pushMsg(this.plugin.i18n.wait4finish); 33 | } 34 | }); 35 | }, 36 | }); 37 | this.plugin.addCommand({ 38 | langKey: CpBox批量移动大量连续内容块.langKey, 39 | langText: tomatoI18n.批量移动大量连续内容块, 40 | hotkey: CpBox批量移动大量连续内容块.m, 41 | callback: async () => { 42 | navigator.locks.request(LongContentOpsLock, { ifAvailable: true }, async (lock) => { 43 | if (lock) { 44 | await this.moveBlocks(false); 45 | } else { 46 | siyuan.pushMsg(this.plugin.i18n.wait4finish); 47 | } 48 | }); 49 | }, 50 | }); 51 | this.plugin.addCommand({ 52 | langKey: CpBox批量复制大量连续内容块.langKey, 53 | langText: tomatoI18n.批量复制大量连续内容块, 54 | hotkey: CpBox批量复制大量连续内容块.m, 55 | callback: async () => { 56 | navigator.locks.request(LongContentOpsLock, { ifAvailable: true }, async (lock) => { 57 | if (lock) { 58 | await this.moveBlocks(true); 59 | } else { 60 | siyuan.pushMsg(this.plugin.i18n.wait4finish); 61 | } 62 | }); 63 | }, 64 | }); 65 | } 66 | 67 | private async deleteBlocks() { 68 | const protyle = events.protyle.protyle; 69 | siyuan.pushMsg(tomatoI18n.批量删除正在检查数据); 70 | await siyuan.deleteBlocksUtil(); 71 | protyle.getInstance().reload(false); 72 | await siyuan.pushMsg("batch deleted!"); 73 | } 74 | 75 | private async moveBlocks(ops: boolean) { 76 | const protyle = events.protyle.protyle; 77 | siyuan.pushMsg(tomatoI18n.批量复制移动正在检查数据); 78 | const blocks = await siyuan.moveBlocksUtil(ops); 79 | if (blocks?.length > 0) { 80 | await OpenSyFile2(this.plugin, blocks[blocks.length - 1].id) 81 | await OpenSyFile2(this.plugin, blocks[0].id) 82 | protyle.getInstance().reload(false); 83 | } 84 | await siyuan.pushMsg("batch moved!"); 85 | } 86 | } 87 | 88 | export const cpBox = new CpBox(); 89 | -------------------------------------------------------------------------------- /src/FastNoteBox.ts: -------------------------------------------------------------------------------- 1 | import { BaseTomatoPlugin } from "./libs/BaseTomatoPlugin"; 2 | import { fastNoteBoxCheckbox } from "./libs/stores"; 3 | import { siyuan, } from "./libs/utils"; 4 | import { tomatoI18n } from "./tomatoI18n"; 5 | import { OpenSyFile2 } from "./libs/docUtils"; 6 | import { events } from "./libs/Events"; 7 | import { createNote, switchDraft } from "./libs/switchDraft"; 8 | import { winHotkey } from "./libs/winHotkey"; 9 | import { verifyKeyTomato } from "./libs/user"; 10 | 11 | export const FastNoteBox创建快速笔记 = winHotkey("shift+alt+n", "创建快速笔记2024-08-06 10:35:21") 12 | export const FastNoteBox打开最后一个笔记 = winHotkey("⌘⌥N", "2024-08-06 10:35:22") 13 | export const FastNoteBox草稿切换 = winHotkey("alt+F4", "草稿切换 2024-9-13 09:32:44", "", () => tomatoI18n.草稿切换 + " · " + tomatoI18n.切换到文档背面, true) 14 | 15 | class FastNoteBox { 16 | plugin: BaseTomatoPlugin; 17 | 18 | async onload(plugin: BaseTomatoPlugin) { 19 | if (!fastNoteBoxCheckbox.get()) return; 20 | await verifyKeyTomato() 21 | this.plugin = plugin; 22 | this.plugin.addCommand({ 23 | langKey: FastNoteBox创建快速笔记.langKey, 24 | langText: tomatoI18n.创建快速笔记, 25 | hotkey: FastNoteBox创建快速笔记.m, 26 | callback: () => { 27 | navigator.locks.request("FastNoteBox2024-08-06 12:38:21", { mode: "exclusive" }, async (lock) => { 28 | if (lock) await createNote(this.plugin, events.protyle?.protyle); 29 | }) 30 | }, 31 | }); 32 | this.plugin.addCommand({ 33 | langKey: FastNoteBox打开最后一个笔记.langKey, 34 | langText: tomatoI18n.打开最后一个笔记, 35 | hotkey: FastNoteBox打开最后一个笔记.m, 36 | callback: () => { 37 | this.openNote() 38 | }, 39 | }); 40 | this.plugin.addCommand({ 41 | langKey: FastNoteBox草稿切换.langKey, 42 | langText: FastNoteBox草稿切换.langText(), 43 | hotkey: FastNoteBox草稿切换.m, 44 | callback: () => { 45 | if (FastNoteBox草稿切换.cmd()) { 46 | switchDraft(this.plugin, events.protyle?.protyle) 47 | } 48 | }, 49 | }); 50 | } 51 | 52 | private async openNote() { 53 | const kName: keyof (AttrType) = "custom-fastnote"; 54 | const rows = await siyuan.sqlAttr(`select root_id from attributes where name="${kName}" order by value desc limit 1`) 55 | if (!rows || rows.length == 0) { 56 | siyuan.pushMsg("cannot find a fastnote") 57 | return 58 | } 59 | OpenSyFile2(this.plugin, rows[0].root_id) 60 | } 61 | } 62 | 63 | export const fastNoteBox = new FastNoteBox(); 64 | -------------------------------------------------------------------------------- /src/GraphControl.svelte: -------------------------------------------------------------------------------- 1 | 36 | -------------------------------------------------------------------------------- /src/GraphMenu.svelte: -------------------------------------------------------------------------------- 1 | 32 | 33 | 34 | 35 | 36 | 37 |
44 | {#if text} 45 |

46 | {text?.slice(0, 10)} 47 |

48 | {/if} 49 | 54 |
55 | 56 | 80 | -------------------------------------------------------------------------------- /src/GraphNode.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
9 | 10 |
11 | {data.text} 12 |
13 | 14 |
15 | 16 | 21 | -------------------------------------------------------------------------------- /src/ImgBox.ts: -------------------------------------------------------------------------------- 1 | import { IEventBusMap } from "siyuan"; 2 | import { events } from "./libs/Events"; 3 | import { CUSTOM_RIFF_DECKS, PROTYLE_WYSIWYG_SELECT } from "./libs/gconst"; 4 | import { imgBoxCheckbox, imgBoxShowMenu } from "./libs/stores"; 5 | import { tomatoI18n } from "./tomatoI18n"; 6 | import html2canvas from 'html2canvas'; 7 | import { getAllText, siyuan, } from "./libs/utils"; 8 | import { BaseTomatoPlugin } from "./libs/BaseTomatoPlugin"; 9 | import { winHotkey } from "./libs/winHotkey"; 10 | 11 | type TomatoMenu = IEventBusMap["click-blockicon"] & IEventBusMap["open-menu-content"]; 12 | 13 | export const ImgBoxHotKey = winHotkey("⌥⇧I", "ImgBox2024-07-03 18:38:32") 14 | class ImgBox { 15 | private plugin: BaseTomatoPlugin; 16 | 17 | async onload(plugin: BaseTomatoPlugin) { 18 | if (!imgBoxCheckbox.get()) return; 19 | this.plugin = plugin; 20 | this.plugin.addCommand({ 21 | langKey: ImgBoxHotKey.langKey, 22 | langText: tomatoI18n.复制为图片, 23 | hotkey: ImgBoxHotKey.m, 24 | callback: async () => { 25 | const { selected } = await events.selectedDivs(); 26 | await this.copyDiv(selected) 27 | }, 28 | }); 29 | this.plugin.eventBus.on("open-menu-content", ({ detail }) => { 30 | this.copyDivMenu(detail as any); 31 | }); 32 | } 33 | 34 | blockIconEvent(detail: IEventBusMap["click-blockicon"]) { 35 | if (!this.plugin) return; 36 | this.copyDivMenu(detail as any); 37 | } 38 | 39 | private async copyDiv(divs: HTMLElement[]) { 40 | const canvases: HTMLCanvasElement[] = []; 41 | 42 | for (const element of divs) { 43 | element.classList.remove(PROTYLE_WYSIWYG_SELECT); 44 | const txt = getAllText([element]) 45 | if (!txt) continue; 46 | 47 | // Save original styles 48 | const originalColor = element.style.color; 49 | const originalBackgroundColor = element.style.backgroundColor; 50 | 51 | // element.style.setProperty('color', '#5D473B', 'important'); 52 | // element.style.setProperty('background-color', '#F5F5DC', 'important'); 53 | element.style.setProperty('color', '#333333', 'important'); 54 | element.style.setProperty('background-color', '#F0F0F0', 'important'); 55 | const custom_riff_decks = element.getAttribute(CUSTOM_RIFF_DECKS) 56 | if (custom_riff_decks) element.removeAttribute(CUSTOM_RIFF_DECKS) 57 | 58 | siyuan.pushMsg(tomatoI18n.正在复制为图片请等待) 59 | const canvas = await html2canvas(element); 60 | canvases.push(canvas); 61 | 62 | // Restore original styles 63 | element.style.color = originalColor; 64 | element.style.backgroundColor = originalBackgroundColor; 65 | if (custom_riff_decks) element.setAttribute(CUSTOM_RIFF_DECKS, custom_riff_decks) 66 | } 67 | 68 | if (canvases.length === 0) { 69 | console.error('No valid div elements found'); 70 | return; 71 | } 72 | 73 | // Create a new canvas to hold the combined image 74 | const combinedCanvas = document.createElement('canvas'); 75 | const ctx = combinedCanvas.getContext('2d'); 76 | 77 | if (!ctx) { 78 | console.error('Could not get 2D context for combined canvas'); 79 | return; 80 | } 81 | 82 | // Calculate the total width and height for the combined canvas 83 | let maxWidth = 0; 84 | let totalHeight = 0; 85 | 86 | for (const canvas of canvases) { 87 | maxWidth = Math.max(maxWidth, canvas.width); 88 | totalHeight += canvas.height; 89 | } 90 | 91 | combinedCanvas.width = maxWidth; 92 | combinedCanvas.height = totalHeight; 93 | 94 | // Draw each canvas onto the combined canvas 95 | let offsetY = 0; 96 | for (const canvas of canvases) { 97 | ctx.drawImage(canvas, 0, offsetY); 98 | offsetY += canvas.height; 99 | } 100 | 101 | // Convert the combined canvas to a Blob and copy it to the clipboard 102 | combinedCanvas.toBlob(async (blob) => { 103 | if (blob) { 104 | await navigator.clipboard.write([ 105 | new ClipboardItem({ 'image/png': blob }) 106 | ]); 107 | siyuan.pushMsg(tomatoI18n.复制完成) 108 | } 109 | }, 'image/png'); 110 | } 111 | 112 | private copyDivMenu(detail: TomatoMenu) { 113 | if (imgBoxShowMenu.get()) { 114 | const menu = detail.menu; 115 | menu.addItem({ 116 | label: tomatoI18n.复制为图片, 117 | iconHTML: "🖼️📋", 118 | accelerator: ImgBoxHotKey.m, 119 | click: async () => { 120 | const { selected } = await events.selectedDivs(detail.protyle); 121 | await this.copyDiv(selected) 122 | }, 123 | }); 124 | } 125 | } 126 | 127 | } 128 | 129 | export const imgBox = new ImgBox(); 130 | -------------------------------------------------------------------------------- /src/LinkBoxBar.svelte: -------------------------------------------------------------------------------- 1 | 84 | 85 | 86 | 87 |
88 | {#if syncVersion} 89 | 90 | {#if cursorPosID === originID} 91 | 92 | {/if} 93 | v{syncVersion} 94 | 95 | {/if} 96 | {#if !$linkBoxSyncBlockAuto} 97 | {tomatoI18n.保存} 100 | {/if} 101 | {tomatoI18n.全部打开} 104 | {#if syncCount < 0} 105 | {tomatoI18n.同步失败} 107 | 108 | {:else} 109 | {tomatoI18n.已在x个地方同步(syncCount)} 111 | 112 | {/if} 113 |
114 | 115 | 133 | -------------------------------------------------------------------------------- /src/NotebookSelect.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | {#if notebooksPromise} 16 | {#await notebooksPromise} 17 |

...waiting

18 | {:then notebooks} 19 | 38 | {/await} 39 | {/if} 40 | -------------------------------------------------------------------------------- /src/ReadingPoint.svelte: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 |
21 | {#each doms as { dom, row, line }} 22 | {#if row} 23 | 30 | {:else} 31 | {@html dom} 32 | {/if} 33 | {/each} 34 |
35 | -------------------------------------------------------------------------------- /src/Schedule.ts: -------------------------------------------------------------------------------- 1 | import { siyuan, } from "./libs/utils"; 2 | import "./index.scss"; 3 | import { events } from "./libs/Events"; 4 | import { DATA_NODE_ID } from "./libs/gconst"; 5 | import { findListTypeByElement } from "./libs/listUtils"; 6 | import { BaseTomatoPlugin } from "./libs/BaseTomatoPlugin"; 7 | import { tomatoI18n } from "./tomatoI18n"; 8 | import { winHotkey } from "./libs/winHotkey"; 9 | 10 | export const ScheduleCopyID = winHotkey("shift+alt+3", "copy id 2025-5-12 18:46:16", "", () => tomatoI18n.复制ID) 11 | 12 | class Schedule { 13 | private plugin: BaseTomatoPlugin; 14 | 15 | async onload(plugin: BaseTomatoPlugin) { 16 | this.plugin = plugin; 17 | this.plugin.addCommand({ 18 | langKey: ScheduleCopyID.langKey, 19 | langText: ScheduleCopyID.langText(), 20 | hotkey: ScheduleCopyID.m, 21 | callback: () => { 22 | this.showScheduleDialog(events.lastBlockID); 23 | }, 24 | }); 25 | } 26 | 27 | private async showScheduleDialog(blockID: string) { 28 | if (!blockID) return; 29 | const { id: listID, found } = findListTypeByElement(document.querySelector(`div[data-node-id="${blockID}"]`)) 30 | siyuan.getDocIDByBlockID(blockID).then((id) => { 31 | console.info(`DocID: ${id}`) 32 | console.info(`BlockID: ${blockID}`) 33 | if (listID) console.info(`ListID: ${listID}`) 34 | }) 35 | await copyID(blockID); 36 | const all = document.querySelectorAll(`div[${DATA_NODE_ID}="${blockID}"]`); 37 | if (all?.length == 1) console.info(all[0]) 38 | else console.info(all) 39 | if (found) console.info(found) 40 | } 41 | } 42 | 43 | export async function copyID(idMsg: string) { 44 | await navigator.clipboard.writeText(idMsg); 45 | await siyuan.pushMsg(`copy ID ${idMsg}`); 46 | } 47 | 48 | export const schedule = new Schedule(); 49 | -------------------------------------------------------------------------------- /src/TomatoClockVedio.svelte: -------------------------------------------------------------------------------- 1 | 48 | 49 | 50 |
51 | {#if !vedioID} 52 |
53 |
{tomatoClock.plugin.i18n.takeARestPlease}
54 |
55 | {:else} 56 |
57 | {/if} 58 |
59 | 60 | 72 | -------------------------------------------------------------------------------- /src/TomatoVIP.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | {#if codeValid} 9 | 10 | {@html icon("TomatoVIP", ICONS_SIZE)} 12 | {:else} 13 | 14 | {@html icon("VIP", ICONS_SIZE)} 16 | {/if} 17 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | // export const STORAGE_Prog_SETTINGS = "ProgressiveLearning.json"; 2 | export const STORAGE_Prog_SETTINGS = "tomato-settings.json"; 3 | export const STORAGE_SETTINGS = "tomato-settings.json"; // plugin switch 4 | export const STORAGE_SCHEDULE = "schedule.json"; 5 | export const STORAGE_TOMATO_TIME = "tomato-time.json"; 6 | export const STORAGE_AUTO_BK = "auto_bk"; 7 | export const STORAGE_INSERT_HEADING = "insert_heading"; 8 | export const ATTR_PIC_OVERLAY = "custom-attr-pic-overlay"; 9 | export const OVERLAY_DIV = "overlayDiv"; 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/i18n/empty.drawio: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/i18n/empty.xmind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IAliceBobI/sy-tomato-plugin/b7e107023b808fa16e9233e40f40696c2ba11c20/src/i18n/empty.xmind -------------------------------------------------------------------------------- /src/i18n/en_US.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Tomato Timer", 3 | "takeARestPlease": "😊 Take a break!", 4 | "hasWorkedMinutes": "Minutes up", 5 | "takeARestAfterMinutes": "Take a break after", 6 | "startCountdown": "Start countdown", 7 | "cancelCountdown": "Cancel countdown", 8 | "cancelLastCountdown": "Cancel last countdown", 9 | "clickOneBlockFirst": "Please click on a block first", 10 | "setDate": "Set time", 11 | "setDateTitle": "Please set time for the following block", 12 | "scheduledAt": "Reminder time", 13 | "remind": "Remind", 14 | "scheduleSetSuccess": "Reminder set successfully", 15 | "schedule": "Set reminder", 16 | "btnAddADay": "+1 Day", 17 | "addBookmark": "Set reading point", 18 | "addBookmarkWithoutENV": "Set reading point (no environment)", 19 | "removeBrokenCards": "Clean all invalid flashcards cleanCards", 20 | "addFlashCard": "Single list, list card making, cancel card making", 21 | "removedBrokenCards": "Deleted invalid flashcards:", 22 | "thereIsNoInvalidCards": "No invalid flashcards!", 23 | "deleteBlocks": "Batch delete large continuous content blocks deleteBlocks", 24 | "moveBlocks": "Batch move large continuous content blocks moveBlocks", 25 | "copyBlocks": "Batch copy large continuous content blocks copyBlocks", 26 | "deleteBlocksHelp": "Batch delete help:

Please wrap the content to be processed with two lines aacc1 and aacc2.

aacc1
Today has good weather1!
Today has good weather2!
...
Today has good weather3!
aacc2
", 27 | "moveBlocksHelp": "Batch move copy help:

Please wrap the content to be processed with two lines aacc1 and aacc2. Then insert a line aacc3 at the target location.

[Document1]
aacc1
Today has good weather1!
Today has good weather2!
...
Today has good weather3!
aacc2
[Document2]
...
aacc3
...
(Document1 and Document2 can be the same document)
", 28 | "topBarTitleShowContents": "Open contents/bookmark page", 29 | "thereIsNoBookmark": "No bookmarks found", 30 | "showBookmarks": "View reading points", 31 | "lookingForTheList": "Searching for the list...", 32 | "reindex": "Unable to find the list to make cards. If you have indeed placed the cursor within a list item and tried multiple times, you can try rebuilding the index.", 33 | "wait4finish": "Please wait for the previous operation to finish!", 34 | "bilink": "Bidirectional link", 35 | "bilinkSelectBlock": "Bidirectional link: Select block", 36 | "bilinkCreateLnk": "Bidirectional link: Create reciprocal link", 37 | "backlink": "Backlink", 38 | "bottombacklink": "Insert bottom backlink #bottomBacklink", 39 | "bottomMention": "Insert bottom mention #bottomMention", 40 | "addPicOverlay": "Add picture overlay", 41 | "delCard": "Delete current flashcard during review", 42 | "skipCard": "Skip current flashcard during review", 43 | "moveBlock2today": "Move content to daily note", 44 | "previousNote": "Previous note", 45 | "nextNote": "Next note", 46 | "cardPrioritySet": "Document and sub-document flashcard priority", 47 | "setCardPriority": "Set flashcard priority", 48 | "uncheckAll": "Uncheck all completed todo tasks in the current document", 49 | "delAllchecked": "Delete all completed todo tasks in the current document", 50 | "refreshVirRef": "Refresh virtual reference", 51 | "removeDocCards": "Remove all flashcards in the current document", 52 | "hotMenu": "Quick menu", 53 | "spaceRepeat": "Spaced repetition", 54 | "locateDoc": "Global locate document", 55 | "baiduAI": "Send selected content to ERNIE Bot 4", 56 | "addTODOBookmark": "Add a 🚩 bookmark", 57 | "deleteAllTODOBookmarks": "Delete all 🚩 bookmarks", 58 | "txt2ref": "All content separated by spaces is converted to references (ignore content after ##)", 59 | "noteBox": "Capture fleeting thoughts", 60 | "noteBoxGlobal": "Capture fleeting thoughts (global)", 61 | "gotoBlockID": "Jump to block ID in clipboard (gotoBlockID)", 62 | "deleteBookmark": "Delete reading point in current document", 63 | "gotoBookmark": "Jump to reading point in current document" 64 | } -------------------------------------------------------------------------------- /src/i18n/es_ES.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Temporizador de tomate", 3 | "takeARestPlease": "😊 Por favor, descansa un momento!", 4 | "hasWorkedMinutes": "minutos terminados", 5 | "takeARestAfterMinutes": "descansar después de minutos", 6 | "startCountdown": "iniciar cuenta regresiva", 7 | "cancelCountdown": "cancelar cuenta regresiva", 8 | "cancelLastCountdown": "cancelar la cuenta regresiva anterior", 9 | "clickOneBlockFirst": "por favor, haga clic en un bloque de contenido primero", 10 | "setDate": "establecer fecha", 11 | "setDateTitle": "por favor, establezca la hora para el siguiente bloque", 12 | "scheduledAt": "hora programada", 13 | "remind": "recordar", 14 | "scheduleSetSuccess": "recordatorio establecido con éxito", 15 | "schedule": "programar recordatorio", 16 | "btnAddADay": "+1 día", 17 | "addBookmark": "establecer punto de lectura", 18 | "addBookmarkWithoutENV": "establecer punto de lectura (sin entorno)", 19 | "removeBrokenCards": "limpiar todas las tarjetas rotas cleanCards", 20 | "addFlashCard": "lista de elementos individuales, crear tarjeta de lista, cancelar creación de tarjeta", 21 | "removedBrokenCards": "tarjetas rotas eliminadas: ", 22 | "thereIsNoInvalidCards": "¡no hay tarjetas inválidas!", 23 | "deleteBlocks": "eliminar bloques de contenido en masa deleteBlocks", 24 | "moveBlocks": "mover bloques de contenido en masa moveBlocks", 25 | "copyBlocks": "copiar bloques de contenido en masa copyBlocks", 26 | "deleteBlocksHelp": "ayuda para eliminar en masa:

por favor, envuelva el contenido que desea procesar con dos líneas aacc1 y aacc2.

aacc1
¡Hoy hace buen tiempo1!
¡Hoy hace buen tiempo2!
...
¡Hoy hace buen tiempo3!
aacc2
", 27 | "moveBlocksHelp": "ayuda para mover y copiar en masa:

por favor, envuelva el contenido con dos líneas aacc1 y aacc2. Luego inserte una línea aacc3 en la ubicación objetivo.

[Documento1]
aacc1
¡Hoy hace buen tiempo1!
¡Hoy hace buen tiempo2!
...
¡Hoy hace buen tiempo3!
aacc2
[Documento2]
...
aacc3
...
(Documento1 y Documento2 pueden ser el mismo documento)
", 28 | "topBarTitleShowContents": "abrir página de índice/marcadores", 29 | "thereIsNoBookmark": ":no se encontraron marcadores", 30 | "showBookmarks": "ver puntos de lectura", 31 | "lookingForTheList": "buscando la lista...", 32 | "reindex": "no se puede encontrar la lista para crear tarjetas, si realmente colocó el cursor dentro de un elemento de lista y lo intentó varias veces, puede intentar reconstruir el índice.", 33 | "wait4finish": "¡espere a que finalice la operación anterior!", 34 | "bilink": "enlace bidireccional", 35 | "bilinkSelectBlock": "enlace bidireccional: seleccionar bloque", 36 | "bilinkCreateLnk": "enlace bidireccional: crear enlace de ida y vuelta", 37 | "backlink": "enlace inverso backlink", 38 | "bottombacklink": "insertar enlace inverso en la parte inferior #bottomBacklink", 39 | "bottomMention": "insertar mención en la parte inferior #bottomMention", 40 | "addPicOverlay": "agregar capa de imagen", 41 | "delCard": "eliminar la tarjeta actual al repasar delete current card", 42 | "skipCard": "omitir la tarjeta actual al repasar skip current card", 43 | "moveBlock2today": "mover contenido a la nota diaria", 44 | "previousNote": "nota anterior", 45 | "nextNote": "nota siguiente", 46 | "cardPrioritySet": "prioridad de tarjetas para documentos y subdocumentos", 47 | "setCardPriority": "establecer prioridad de tarjetas", 48 | "uncheckAll": "desmarcar todas las tareas de todo completadas en el documento actual", 49 | "delAllchecked": "eliminar todas las tareas de todo completadas en el documento actual", 50 | "refreshVirRef": "actualizar referencias virtuales", 51 | "removeDocCards": "cancelar todas las tarjetas en el documento actual removeCardsInDoc", 52 | "hotMenu": "menú rápido", 53 | "spaceRepeat": "repetición de espacio", 54 | "locateDoc": "localizar documento globalmente", 55 | "baiduAI": "enviar contenido seleccionado a ERNIE 4", 56 | "addTODOBookmark": "agregar un marcador 🚩", 57 | "deleteAllTODOBookmarks": "eliminar todos los marcadores 🚩", 58 | "txt2ref": "convertir todo el contenido separado por espacios en referencias (ignorar el contenido después de ##)", 59 | "noteBox": "captura de ideas al vuelo", 60 | "noteBoxGlobal": "captura de ideas al vuelo (global)", 61 | "gotoBlockID": "ir al ID de bloque en el portapapeles (gotoBlockID)", 62 | "deleteBookmark": "eliminar el punto de lectura del documento actual rp", 63 | "gotoBookmark": "ir al punto de lectura del documento actual rp" 64 | } -------------------------------------------------------------------------------- /src/i18n/fr_FR.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Chronomètre à tomates", 3 | "takeARestPlease": "😊 Prenez une pause !", 4 | "hasWorkedMinutes": "minutes écoulées", 5 | "takeARestAfterMinutes": "pause après minutes", 6 | "startCountdown": "Démarrer le compte à rebours", 7 | "cancelCountdown": "Annuler le compte à rebours", 8 | "cancelLastCountdown": "Annuler le compte à rebours précédent", 9 | "clickOneBlockFirst": "Veuillez cliquer d'abord sur un bloc de contenu", 10 | "setDate": "Définir l'heure", 11 | "setDateTitle": "Veuillez définir l'heure pour les blocs suivants", 12 | "scheduledAt": "Heure de rappel", 13 | "remind": "Rappeler", 14 | "scheduleSetSuccess": "Rappel configuré avec succès", 15 | "schedule": "Configurer un rappel", 16 | "btnAddADay": "+1 jour", 17 | "addBookmark": "Définir un point de lecture", 18 | "addBookmarkWithoutENV": "Définir un point de lecture (sans environnement)", 19 | "removeBrokenCards": "Nettoyer toutes les cartes à étiquettes invalides cleanCards", 20 | "addFlashCard": "Liste unidirectionnelle, création de cartes, annulation de la création de cartes", 21 | "removedBrokenCards": "Cartes à étiquettes invalides supprimées :", 22 | "thereIsNoInvalidCards": "Aucune carte à étiquettes invalide !", 23 | "deleteBlocks": "Supprimer en masse de gros blocs de contenu continu deleteBlocks", 24 | "moveBlocks": "Déplacer en masse de gros blocs de contenu continu moveBlocks", 25 | "copyBlocks": "Copier en masse de gros blocs de contenu continu copyBlocks", 26 | "deleteBlocksHelp": "Aide à la suppression en masse :

Veuillez entourer le contenu à traiter avec deux lignes aacc1 et aacc2.

aacc1
Il fait beau aujourd'hui 1 !
Il fait beau aujourd'hui 2 !
...
Il fait beau aujourd'hui 3 !
aacc2
", 27 | "moveBlocksHelp": "Aide au déplacement et à la copie en masse :

Entourez le contenu à traiter avec deux lignes aacc1 et aacc2. Insérez ensuite une ligne aacc3 à la destination.

[Document 1]
aacc1
Il fait beau aujourd'hui 1 !
Il fait beau aujourd'hui 2 !
...
Il fait beau aujourd'hui 3 !
aacc2
[Document 2]
...
aacc3
...
(Document 1 et Document 2 peuvent être le même document)
", 28 | "topBarTitleShowContents": "Ouvrir la page de contenu/page de signets", 29 | "thereIsNoBookmark": ":Aucun signet trouvé", 30 | "showBookmarks": "Voir les points de lecture", 31 | "lookingForTheList": "Recherche de la liste…", 32 | "reindex": "Liste pour créer des cartes introuvable, si vous avez vraiment placé le curseur à l'intérieur d'un élément de liste et essayé plusieurs fois, vous pouvez essayer de reconstruire l'index.", 33 | "wait4finish": "Veuillez attendre la fin de l'opération précédente !", 34 | "bilink": "Lien bidirectionnel", 35 | "bilinkSelectBlock": "Lien bidirectionnel : Sélectionner un bloc", 36 | "bilinkCreateLnk": "Lien bidirectionnel : Créer un lien aller-retour", 37 | "backlink": "Retour en arrière backlink", 38 | "bottombacklink": "Insérer un lien arrière en bas #bottomBacklink", 39 | "bottomMention": "Insérer une mention en bas #bottomMention", 40 | "addPicOverlay": "Ajouter un calque de masquage d'image", 41 | "delCard": "Supprimer la carte actuelle lors de la révision delete current card", 42 | "skipCard": "Passer la carte actuelle lors de la révision skip current card", 43 | "moveBlock2today": "Déplacer le contenu vers la note quotidienne", 44 | "previousNote": "Note précédente", 45 | "nextNote": "Note suivante", 46 | "cardPrioritySet": "Priorité des cartes à étiquettes pour le document et les sous-documents", 47 | "setCardPriority": "Définir la priorité des cartes à étiquettes", 48 | "uncheckAll": "Décocher toutes les tâches todo terminées du document actuel", 49 | "delAllchecked": "Supprimer toutes les tâches todo terminées du document actuel", 50 | "refreshVirRef": "Actualiser les références virtuelles", 51 | "removeDocCards": "Annuler toutes les cartes à étiquettes dans le document actuel removeCardsInDoc", 52 | "hotMenu": "Menu rapide", 53 | "spaceRepeat": "Répétition espacée", 54 | "locateDoc": "Localiser un document globalement", 55 | "baiduAI": "Sélectionner le contenu pour le transmettre à Wenxin Yiyan 4", 56 | "addTODOBookmark": "Ajouter un signet 🚩", 57 | "deleteAllTODOBookmarks": "Supprimer tous les signets 🚩", 58 | "txt2ref": "Tout le contenu séparé par des espaces est converti en référence (ignore le contenu après ##)", 59 | "noteBox": "Idée flash photographiée", 60 | "noteBoxGlobal": "Idée flash photographiée (global)", 61 | "gotoBlockID": "Aller au bloc ID dans le presse-papiers (gotoBlockID)", 62 | "deleteBookmark": "Supprimer le point de lecture du document actuel rp", 63 | "gotoBookmark": "Aller au point de lecture du document actuel rp" 64 | } -------------------------------------------------------------------------------- /src/i18n/ja_JP.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "トマトクロック", 3 | "takeARestPlease": "😊 しばらく休憩しましょう!", 4 | "hasWorkedMinutes": "分間働いた", 5 | "takeARestAfterMinutes": "分後に休憩", 6 | "startCountdown": "カウントダウン開始", 7 | "cancelCountdown": "カウントダウンキャンセル", 8 | "cancelLastCountdown": "前回のカウントダウンをキャンセル", 9 | "clickOneBlockFirst": "まず内容ブロックをクリックしてください", 10 | "setDate": "時間設定", 11 | "setDateTitle": "以下のブロックに時間を設定してください", 12 | "scheduledAt": "リマインダー時間", 13 | "remind": "リマインド", 14 | "scheduleSetSuccess": "リマインダー設定成功", 15 | "schedule": "リマインダー設定", 16 | "btnAddADay": "+1日", 17 | "addBookmark": "読書ポイントを設定", 18 | "addBookmarkWithoutENV": "読書ポイントを設定(環境なし)", 19 | "removeBrokenCards": "無効なスマートカードをすべてクリーンアップ", 20 | "addFlashCard": "単一リスト、リストからカード作成、カード作成キャンセル", 21 | "removedBrokenCards": "削除された無効なスマートカード:", 22 | "thereIsNoInvalidCards": "無効なスマートカードはありません!", 23 | "deleteBlocks": "大量の連続した内容ブロックを一括削除", 24 | "moveBlocks": "大量の連続した内容ブロックを一括移動", 25 | "copyBlocks": "大量の連続した内容ブロックを一括コピー", 26 | "deleteBlocksHelp": "一括削除のヘルプ:

処理する内容をaacc1とaacc2の2行で囲んでください。

aacc1
今日はいい天気ですね1!
今日はいい天気ですね2!
...
今日はいい天気ですね3!
aacc2
", 27 | "moveBlocksHelp": "一括移動コピーのヘルプ:

処理する内容をaacc1とaacc2の2行で囲み、ターゲット位置にaacc3を挿入してください。

[ドキュメント1]
aacc1
今日はいい天気ですね1!
今日はいい天気ですね2!
...
今日はいい天気ですね3!
aacc2
[ドキュメント2]
...
aacc3
...
(ドキュメント1とドキュメント2は同じドキュメントでも可)
", 28 | "topBarTitleShowContents": "コンテンツページ/ブックマークページを開く", 29 | "thereIsNoBookmark": ":ブックマークはありません", 30 | "showBookmarks": "読書ポイントを表示", 31 | "lookingForTheList": "リストを検索中……", 32 | "reindex": "カード作成のためのリストが見つかりませんでした。カーソルをリストの項目内に置いて何度か試した場合は、インデックスを再構築することを試みてください。", 33 | "wait4finish": "前の操作が完了するまで待ってください!", 34 | "bilink": "双方向リンク", 35 | "bilinkSelectBlock": "双方向リンク:ブロックを選択", 36 | "bilinkCreateLnk": "双方向リンク:往復リンクを作成", 37 | "backlink": "バックリンク", 38 | "bottombacklink": "バックリンクをページ下部に挿入#bottomBacklink", 39 | "bottomMention": "ページ下部にメンションを挿入#bottomMention", 40 | "addPicOverlay": "画像オーバーレイを追加", 41 | "delCard": "復習時に現在のスマートカードを削除", 42 | "skipCard": "復習時に現在のスマートカードをスキップ", 43 | "moveBlock2today": "コンテンツをデイリーノートに移動", 44 | "previousNote": "前のノート", 45 | "nextNote": "次のノート", 46 | "cardPrioritySet": "ドキュメントとサブドキュメントのスマートカードの優先順位", 47 | "setCardPriority": "スマートカードの優先順位を設定", 48 | "uncheckAll": "現在のドキュメントで完了したすべてのTODOタスクのチェックを解除", 49 | "delAllchecked": "現在のドキュメントで完了したすべてのTODOタスクを削除", 50 | "refreshVirRef": "仮想リファレンスを更新", 51 | "removeDocCards": "現在のドキュメント内のすべてのスマートカードを削除", 52 | "hotMenu": "ホットメニュー", 53 | "spaceRepeat": "スペースリピート", 54 | "locateDoc": "ドキュメントのグローバルな位置指定", 55 | "baiduAI": "選択した内容を文心一言4に送信", 56 | "addTODOBookmark": "🚩ブックマークを追加", 57 | "deleteAllTODOBookmarks": "すべての🚩ブックマークを削除", 58 | "txt2ref": "スペースで区切られたすべての内容をリファレンスに変換(##の後の内容は無視)", 59 | "noteBox": "写真での閃き", 60 | "noteBoxGlobal": "写真での閃き(グローバル)", 61 | "gotoBlockID": "クリップボードのブロックIDにジャンプ", 62 | "deleteBookmark": "現在のドキュメントの読書ポイントを削除", 63 | "gotoBookmark": "現在のドキュメントの読書ポイントにジャンプ" 64 | } -------------------------------------------------------------------------------- /src/i18n/zh_CHT.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "番茄鐘", 3 | "takeARestPlease": "😊休息一下吧!", 4 | "hasWorkedMinutes": "分鐘已到", 5 | "takeARestAfterMinutes": "分鐘後休息", 6 | "startCountdown": "開始計時", 7 | "cancelCountdown": "取消計時", 8 | "cancelLastCountdown": "取消上次的計時", 9 | "clickOneBlockFirst": "請先點擊一個內容塊", 10 | "setDate": "設置時間", 11 | "setDateTitle": "請為以下塊設置時間", 12 | "scheduledAt": "提醒時間", 13 | "remind": "提醒", 14 | "scheduleSetSuccess": "提醒設置成功", 15 | "schedule": "設置提醒", 16 | "btnAddADay": "+1日", 17 | "addBookmark": "設置閱讀點", 18 | "addBookmarkWithoutENV": "設置閱讀點(無環境)", 19 | "removeBrokenCards": "清理所有失效閃卡cleanCards", 20 | "addFlashCard": "單項列表、列表制卡、取消制卡", 21 | "removedBrokenCards": "已經刪除的失效閃卡:", 22 | "thereIsNoInvalidCards": "無失效閃卡!", 23 | "deleteBlocks": "批量刪除大量連續內容塊deleteBlocks", 24 | "moveBlocks": "批量移動大量連續內容塊moveBlocks", 25 | "copyBlocks": "批量複製大量連續內容塊copyBlocks", 26 | "deleteBlocksHelp": "批量刪除幫助:

請分別用 aacc1 與 aacc2 兩行把要處理的內容包裹起來。

aacc1
今天有個好天氣1!
今天有個好天氣2!
...
今天有個好天氣3!
aacc2
", 27 | "moveBlocksHelp": "批量移動複製幫助:

請分別用 aacc1 與 aacc2 兩行把要處理的內容包裹起來。再到目標位置插入一行 aacc3。

[文檔1]
aacc1
今天有個好天氣1!
今天有個好天氣2!
...
今天有個好天氣3!
aacc2
[文檔2]
...
aacc3
...
(文檔1與文檔2可以是同一個文檔)
", 28 | "topBarTitleShowContents": "打開目錄頁/書籤頁", 29 | "thereIsNoBookmark": ":找不到任何書籤", 30 | "showBookmarks": "查看閱讀點", 31 | "lookingForTheList": "正在查找列表……", 32 | "reindex": "無法找到要制卡的列表,如果你確實是將光標放到列表的某個列表項內嘗試多次,你可以嘗試重建索引。", 33 | "wait4finish": "請等待上個操作完成!", 34 | "bilink": "雙向互鏈", 35 | "bilinkSelectBlock": "雙向互鏈:選擇塊", 36 | "bilinkCreateLnk": "雙向互鏈:創建往返鏈", 37 | "backlink": "反鏈backlink", 38 | "bottombacklink": "插入底部反鏈#bottomBacklink", 39 | "bottomMention": "插入底部提及#bottomMention", 40 | "addPicOverlay": "添加圖片遮擋層", 41 | "delCard": "複習時刪除當前閃卡delete current card", 42 | "skipCard": "複習時跳過當前閃卡skip current card", 43 | "moveBlock2today": "移動內容到 daily note", 44 | "previousNote": "上一個日誌", 45 | "nextNote": "下一個日誌", 46 | "cardPrioritySet": "文檔與子文檔閃卡優先級", 47 | "setCardPriority": "設置閃卡優先級", 48 | "uncheckAll": "取消勾選當前文檔所有已完成的todo任務", 49 | "delAllchecked": "刪除當前文檔所有已完成的todo任務", 50 | "refreshVirRef": "刷新虛擬引用", 51 | "removeDocCards": "取消當前文檔內所有閃卡removeCardsInDoc", 52 | "hotMenu": "快捷菜單", 53 | "spaceRepeat": "間隔重複", 54 | "locateDoc": "全局定位文檔", 55 | "baiduAI": "選中內容發給文心一言4", 56 | "addTODOBookmark": "添加一個🚩書籤", 57 | "deleteAllTODOBookmarks": "刪除所有🚩書籤", 58 | "txt2ref": "空格隔開的所有內容都轉為引用(忽略##後的內容)", 59 | "noteBox": "拍照閃念", 60 | "noteBoxGlobal": "拍照閃念(全局)", 61 | "gotoBlockID": "跳轉到剪貼板中的塊ID(gotoBlockID)", 62 | "deleteBookmark": "刪除當前文檔的閱讀點rp", 63 | "gotoBookmark": "跳到當前文檔的閱讀點rp" 64 | } -------------------------------------------------------------------------------- /src/i18n/zh_CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "番茄钟", 3 | "takeARestPlease": "😊休息一会儿吧!", 4 | "hasWorkedMinutes": "分钟已到", 5 | "takeARestAfterMinutes": "分钟后休息", 6 | "startCountdown": "开始计时", 7 | "cancelCountdown": "取消计时", 8 | "cancelLastCountdown": "取消上次的计时", 9 | "clickOneBlockFirst": "请先点击一个内容块", 10 | "setDate": "设置时间", 11 | "setDateTitle": "请为以下块设置时间", 12 | "scheduledAt": "提醒时间", 13 | "remind": "提醒", 14 | "scheduleSetSuccess": "提醒设置成功", 15 | "schedule": "设置提醒", 16 | "btnAddADay": "+1日", 17 | "addBookmark": "设置阅读点", 18 | "addBookmarkWithoutENV": "设置阅读点(无环境)", 19 | "removeBrokenCards": "清理所有失效闪卡cleanCards", 20 | "addFlashCard": "单项列表、列表制卡、取消制卡", 21 | "removedBrokenCards": "已经删除的失效闪卡:", 22 | "thereIsNoInvalidCards": "无失效闪卡!", 23 | "deleteBlocks": "批量删除大量连续内容块deleteBlocks", 24 | "moveBlocks": "批量移动大量连续内容块moveBlocks", 25 | "copyBlocks": "批量复制大量连续内容块copyBlocks", 26 | "deleteBlocksHelp": "批量删除帮助:

请分别用 aacc1 与 aacc2 两行把要处理的内容包裹起来。

aacc1
今天有个好天气1!
今天有个好天气2!
...
今天有个好天气3!
aacc2
", 27 | "moveBlocksHelp": "批量移动复制帮助:

请分别用 aacc1 与 aacc2 两行把要处理的内容包裹起来。再到目标位置插入一行 aacc3。

[文档1]
aacc1
今天有个好天气1!
今天有个好天气2!
...
今天有个好天气3!
aacc2
[文档2]
...
aacc3
...
(文档1与文档2可以是同一个文档)
", 28 | "topBarTitleShowContents": "打开目录页/书签页", 29 | "thereIsNoBookmark": ":找不到任何书签", 30 | "showBookmarks": "查看阅读点", 31 | "lookingForTheList": "正在查找列表……", 32 | "reindex": "无法找到要制卡的列表,如果你确实是将光标放到列表的某个列表项内尝试多次,你可以尝试重建索引。", 33 | "wait4finish": "请等待上个操作完成!", 34 | "bilink": "双向互链", 35 | "bilinkSelectBlock": "双向互链:选择块", 36 | "bilinkCreateLnk": "双向互链:创建往返链", 37 | "backlink": "反链backlink", 38 | "bottombacklink": "插入底部反链#bottomBacklink", 39 | "bottomMention": "插入底部提及#bottomMention", 40 | "addPicOverlay": "添加图片遮挡层", 41 | "delCard": "复习时删除当前闪卡delete current card", 42 | "skipCard": "复习时跳过当前闪卡skip current card", 43 | "moveBlock2today": "移动内容到 daily note", 44 | "previousNote": "上一个日志", 45 | "nextNote": "下一个日志", 46 | "cardPrioritySet": "文档与子文档闪卡优先级", 47 | "setCardPriority": "设置闪卡优先级", 48 | "uncheckAll": "取消勾选当前文档所有已完成的todo任务", 49 | "delAllchecked": "删除当前文档所有已完成的todo任务", 50 | "refreshVirRef": "刷新虚拟引用", 51 | "removeDocCards": "取消当前文档内所有闪卡removeCardsInDoc", 52 | "hotMenu": "快捷菜单", 53 | "spaceRepeat": "间隔重复", 54 | "locateDoc": "全局定位文档", 55 | "baiduAI": "选中内容发给文心一言4", 56 | "addTODOBookmark": "添加一个🚩书签", 57 | "deleteAllTODOBookmarks": "删除所有🚩书签", 58 | "txt2ref": "空格隔开的所有内容都转为引用(忽略##后的内容)", 59 | "noteBox": "拍照闪念", 60 | "noteBoxGlobal": "拍照闪念(全局)", 61 | "gotoBlockID": "跳转到剪贴板中的块ID(gotoBlockID)", 62 | "deleteBookmark": "删除当前文档的阅读点rp", 63 | "gotoBookmark": "跳到当前文档的阅读点rp" 64 | } -------------------------------------------------------------------------------- /src/icons.ts: -------------------------------------------------------------------------------- 1 | export const ICONS = ` 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | `; 33 | -------------------------------------------------------------------------------- /src/index.scss: -------------------------------------------------------------------------------- 1 | .protyle-wysiwyg div[custom-staticlink], 2 | .protyle-wysiwyg div[custom-tomato-readingpoint] { 3 | opacity: 0.65; 4 | // transform: scale(0.65); 5 | // transform-origin: 0 0; 6 | color: var(--b3-font-color8); 7 | background-color: var(--b3-font-background5); 8 | } 9 | 10 | .protyle-wysiwyg div[data-subtype="h6"][fold="1"][custom-comment-heading="1"] { 11 | opacity: 0.7 !important; 12 | font-size: 0.7em !important; 13 | } 14 | 15 | .protyle-wysiwyg div[custom-card-priority-stop] { 16 | opacity: 0.3; 17 | } 18 | 19 | .protyle-wysiwyg div[custom-card-priority-stop]::after { 20 | content: attr(custom-card-priority-stop); 21 | color: var(--b3-font-color5); 22 | } 23 | 24 | // ai response 25 | .protyle-wysiwyg div[custom-ai-response] { 26 | color: var(--b3-font-color5); 27 | background-color: var(--b3-font-background5); 28 | } 29 | 30 | // super list 31 | .protyle-wysiwyg div[custom-super-list] { 32 | border-bottom: 1px groove var(--b3-font-color5); 33 | } 34 | 35 | // bottom link 36 | .protyle-wysiwyg div[custom-lnk-bottom] { 37 | border: 1px solid var(--b3-font-color5); 38 | box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1); 39 | } 40 | 41 | .buttom-bk-borderedDiv { 42 | border: 1px solid var(--b3-theme-surface-lighter); 43 | } 44 | 45 | .protyle-wysiwyg div[custom-sync-block-id] { 46 | border: 1px dashed var(--b3-font-color5); 47 | } 48 | 49 | // readonly 50 | .protyle-wysiwyg div[custom-tomato-readonly]::before { 51 | content: attr(memo); 52 | } 53 | 54 | // settings search 55 | .tomato-highlight { 56 | box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); 57 | font-size: x-large; 58 | color: var(--b3-font-color2); 59 | } 60 | 61 | .protyle-wysiwyg div[custom-tomato-readonly] { 62 | color: var(--b3-font-color4); 63 | background-color: var(--b3-font-background4); 64 | 65 | div:not(.protyle-attr) { 66 | display: none; 67 | } 68 | 69 | div.protyle-attr::after { 70 | content: "🔒"; 71 | } 72 | } 73 | 74 | .protyle-wysiwyg div[custom-tomato-line-blur] { 75 | filter: blur(5px); 76 | opacity: 0.7; 77 | } 78 | 79 | .protyle-wysiwyg div[custom-tomato-line-blur]:hover { 80 | filter: none; 81 | opacity: 1; 82 | } 83 | 84 | // bi ref links 85 | .protyle-wysiwyg { 86 | div[custom-tomato-reflink]::after { 87 | content: attr(custom-tomato-reflink); 88 | font-size: small; 89 | } 90 | 91 | div[custom-tomato-reflink] { 92 | display: flex; 93 | } 94 | } 95 | 96 | // super block as card 97 | .protyle-wysiwyg div[custom-super-card-box] { 98 | border: 1px solid var(--b3-font-color5); 99 | } 100 | 101 | // @keyframes tomatoFadeIn { 102 | // from { 103 | // opacity: 0.1; 104 | // color: var(--b3-font-color2); 105 | // background-color: var(--b3-font-background1); 106 | // } 107 | // to { 108 | // opacity: 1; 109 | // } 110 | // } 111 | // .tomato-cmd-box { 112 | // animation: tomatoFadeIn 0.3s forwards 3; 113 | // } 114 | 115 | .tomato-style { 116 | &__bkContainer { 117 | font-size: larger; 118 | } 119 | 120 | &__bkContent { 121 | padding-left: 30px; 122 | 123 | ul { 124 | padding-left: 20px; 125 | } 126 | 127 | ul ul { 128 | padding-left: 40px; 129 | } 130 | 131 | ul ul ul { 132 | padding-left: 60px; 133 | } 134 | } 135 | 136 | &__container { 137 | display: flex; 138 | justify-content: center; 139 | align-items: center; 140 | height: 100vh; 141 | } 142 | 143 | &__centered-text { 144 | text-align: center; 145 | font-size: 30px; 146 | } 147 | 148 | &__code { 149 | background-color: var(--b3-protyle-code-linenumber-hl); 150 | color: var(--b3-font-color5); 151 | font-size: small; 152 | padding: 2px 4px; 153 | border-radius: 4px; 154 | font-family: "Courier New", monospace; 155 | vertical-align: top; 156 | } 157 | } 158 | 159 | .tomato-mind-wire-content { 160 | border: 0.3px dashed var(--b3-font-color5); 161 | } 162 | 163 | #tomato-mind-wire-svg-container { 164 | position: absolute; 165 | top: 0; 166 | left: 0; 167 | width: 100%; 168 | height: 100%; 169 | pointer-events: none; 170 | z-index: 1; 171 | } 172 | 173 | .tomato-mind-wire-connector { 174 | stroke-width: 0.7; 175 | stroke-dasharray: 10; 176 | } 177 | 178 | .tomato-mind-wire-connector-animation { 179 | stroke-width: 0.7; 180 | stroke-dasharray: 10; 181 | animation: tomato-mind-wire-flow 2s linear infinite; 182 | } 183 | 184 | @keyframes tomato-mind-wire-flow { 185 | from { 186 | stroke-dashoffset: 10; 187 | } 188 | 189 | to { 190 | stroke-dashoffset: 0; 191 | } 192 | } -------------------------------------------------------------------------------- /src/libs/BaseTomatoPlugin.ts: -------------------------------------------------------------------------------- 1 | import { Plugin } from "siyuan"; 2 | import { newID } from "./utils"; 3 | 4 | export class BaseTomatoPlugin extends Plugin { 5 | constructor(options: any) { 6 | super(options) 7 | } 8 | loadProgStore: (p: BaseTomatoPlugin) => void; 9 | loadStore: (p: BaseTomatoPlugin) => void; 10 | id = newID(); 11 | taskCfg: Promise; 12 | settingCfg: TomatoSettings; 13 | pluginSpec: PluginSpec; 14 | readonly global: TomatoGlobal = globalThis as any; 15 | initCfg() { 16 | const cfg = this.global?.tomato_zZmqus5PtYRi?.pluginConfig; 17 | if (cfg != null) { 18 | console.debug("load cfg from global: pluginID: " + this.id); 19 | this.settingCfg = cfg; 20 | if (this.loadStore) this.loadStore(this); 21 | return true; 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/libs/DialogText.ts: -------------------------------------------------------------------------------- 1 | import { newID } from "./utils"; 2 | import DialogTextSv from "./DialogTextSv.svelte"; 3 | import { Dialog } from "siyuan"; 4 | import { DestroyManager } from "./destroyer"; 5 | import { events } from "./Events"; 6 | 7 | export class DialogTextArea { 8 | private title: string; 9 | private defaultValue: string; 10 | 11 | constructor(title: string, defaultValue: string, callback: (s: string) => Promise) { 12 | this.title = title; 13 | this.defaultValue = defaultValue; 14 | this.open(callback); 15 | } 16 | 17 | private open(callback: Func) { 18 | const id = newID(); 19 | const dm = new DestroyManager(); 20 | 21 | const dialog = new Dialog({ 22 | title: this.title, 23 | content: `
`, 24 | width: events.isMobile ? "90vw" : null, 25 | height: events.isMobile ? null : null, 26 | destroyCallback() { 27 | dm.destroyBy("dialog"); 28 | }, 29 | }); 30 | dm.add("dialog", () => dialog.destroy()); 31 | 32 | const svelte = new DialogTextSv({ 33 | target: dialog.element.querySelector("#" + id), 34 | props: { 35 | dm, 36 | callback, 37 | defaultValue: this.defaultValue, 38 | alwaysConfirm: true, 39 | useTextArea: true, 40 | } 41 | }); 42 | dm.add("svelte", () => svelte.$destroy()); 43 | } 44 | } 45 | 46 | export class DialogText { 47 | private title: string; 48 | private defaultValue: string; 49 | private description: string; 50 | private alwaysConfirm: boolean; 51 | 52 | constructor(title: string, defaultValue: string, callback: Func, alwaysConfirm = false, description = "") { 53 | this.alwaysConfirm = alwaysConfirm; 54 | this.title = title; 55 | this.defaultValue = defaultValue; 56 | this.description = description; 57 | this.open(callback); 58 | } 59 | 60 | private open(callback: Func) { 61 | const id = newID(); 62 | const dm = new DestroyManager(); 63 | 64 | const dialog = new Dialog({ 65 | title: this.title, 66 | content: `
`, 67 | width: events.isMobile ? "90vw" : null, 68 | height: events.isMobile ? null : null, 69 | destroyCallback() { 70 | dm.destroyBy("dialog"); 71 | }, 72 | }); 73 | dm.add("dialog", () => dialog.destroy()); 74 | 75 | const svelte = new DialogTextSv({ 76 | target: dialog.element.querySelector("#" + id), 77 | props: { 78 | dm, 79 | callback, 80 | defaultValue: this.defaultValue, 81 | alwaysConfirm: this.alwaysConfirm, 82 | description: this.description, 83 | } 84 | }); 85 | dm.add("svelte", () => svelte.$destroy()); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/libs/DialogTextSv.svelte: -------------------------------------------------------------------------------- 1 | 47 | 48 | 49 |
50 |
51 |
52 | {description} 53 |
54 | {#if useTextArea} 55 | 74 | {:else} 75 | input.select()} 78 | placeholder={defaultValue} 79 | type="text" 80 | class="b3-text-field" 81 | bind:value={inputText} 82 | on:keypress={(event) => { 83 | if (event instanceof KeyboardEvent) { 84 | if (event.key === "Enter") { 85 | btnClick(); 86 | } 87 | } 88 | }} 89 | /> 90 | 91 | {/if} 92 |
93 |
94 | 95 | 105 | -------------------------------------------------------------------------------- /src/libs/EnumUtils.ts: -------------------------------------------------------------------------------- 1 | export type EnumItem = { key: string, value: string, idx: number, text: string } 2 | /** 3 | * enum must be like: 4 | * enum InsertPlace { 5 | here = "0#当前位置", 6 | dailynote = "1#今日笔记", 7 | subdoc = "2#子文档", 8 | } 9 | */ 10 | export class EnumUtils { 11 | private enumType: any; 12 | private _map: Map; 13 | public get map(): Map { 14 | return this._map; 15 | } 16 | private set map(value: Map) { 17 | this._map = value; 18 | } 19 | constructor(enumType: any) { 20 | this.enumType = enumType; 21 | this.map = Object.keys(this.enumType).map(key => { 22 | const value: string = enumType[key]; 23 | const parts = value.split("#"); 24 | return { 25 | key, 26 | value, 27 | idx: Number(parts[0]), 28 | text: parts[1], 29 | }; 30 | }).reduce((m, i) => { 31 | m.set(i.idx, i); 32 | return m; 33 | }, new Map()); 34 | } 35 | 36 | getItem(enumValue: string) { 37 | const parts = enumValue.split("#"); 38 | return this.map.get(Number(parts[0])); 39 | } 40 | } 41 | 42 | export function getAllEnumValues(enumObj: T): T[keyof T][] { 43 | return Object.keys(enumObj) 44 | .map(key => enumObj[key as keyof typeof enumObj]); 45 | } -------------------------------------------------------------------------------- /src/libs/SelectionML.ts: -------------------------------------------------------------------------------- 1 | import { events } from "./Events"; 2 | import { PROTYLE_WYSIWYG_SELECT } from "./gconst"; 3 | import { getAttribute } from "./utils"; 4 | 5 | export class SelectionML { 6 | private selected: HTMLElement[] = []; 7 | private topElement: HTMLElement; 8 | private bottomElement: HTMLElement; 9 | private firstElement: HTMLElement; 10 | private trace: string[] = []; 11 | 12 | constructor(s: Awaited>) { 13 | this.selected = s.selected; 14 | this.firstElement = this.selected.at(0); 15 | this.topElement = this.selected.at(0); 16 | this.bottomElement = this.selected.at(-1); 17 | } 18 | 19 | private traceElement(e: Element) { 20 | e.classList.add(PROTYLE_WYSIWYG_SELECT); 21 | const id: string = getAttribute(e, "data-node-id"); 22 | if (!this.trace.includes(id)) this.trace.push(id); 23 | } 24 | 25 | private untraceElement(e: Element) { 26 | const id: string = getAttribute(e, "data-node-id"); 27 | const idx = this.trace.findIndex(i => i == id) 28 | if (idx >= 0) { 29 | this.trace.splice(idx, 1) 30 | } 31 | } 32 | 33 | private unselect(d: Element) { 34 | d.querySelectorAll("." + PROTYLE_WYSIWYG_SELECT).forEach((e) => { 35 | e.classList.remove(PROTYLE_WYSIWYG_SELECT); 36 | this.untraceElement(e); 37 | }); 38 | } 39 | 40 | private scrollIntoView(d: Element) { 41 | d.scrollIntoView({ 42 | behavior: "smooth", 43 | block: "center", 44 | }); 45 | } 46 | 47 | private selectDivUp(d: Element) { 48 | const id = getAttribute(d, "data-node-id"); 49 | if (id) { 50 | this.scrollIntoView(d); 51 | this.topElement = d as HTMLElement; 52 | this.traceElement(this.topElement) 53 | return true; 54 | } 55 | } 56 | 57 | selectUp() { 58 | if (this.trace.length == 0) this.traceElement(this.topElement) 59 | if (!this.selectDivUp(this.topElement.previousElementSibling)) { 60 | if (this.selectDivUp(this.topElement.parentElement)) { 61 | this.bottomElement = this.topElement; 62 | this.unselect(this.topElement.parentElement); 63 | this.traceElement(this.topElement) 64 | } 65 | } 66 | } 67 | 68 | private selectDivDown(d: Element) { 69 | const id = getAttribute(d, "data-node-id"); 70 | if (id) { 71 | this.scrollIntoView(d); 72 | this.bottomElement = d as HTMLElement; 73 | this.traceElement(this.bottomElement) 74 | return true; 75 | } 76 | } 77 | 78 | selectDown() { 79 | if (this.trace.length == 0) this.traceElement(this.bottomElement) 80 | if (!this.selectDivDown(this.bottomElement.nextElementSibling)) { 81 | if (this.selectDivDown(this.bottomElement.parentElement)) { 82 | this.topElement = this.bottomElement; 83 | this.unselect(this.bottomElement.parentElement); 84 | this.traceElement(this.bottomElement) 85 | } 86 | } 87 | } 88 | 89 | cancelLast() { 90 | const id = this.trace.pop(); 91 | if (id) { 92 | const e = document.querySelector(`div[data-node-id="${id}"]`) 93 | if (e) { 94 | this.scrollIntoView(e); 95 | e.classList.remove(PROTYLE_WYSIWYG_SELECT) 96 | } 97 | } 98 | 99 | if (this.trace.length == 0) { 100 | this.topElement = this.firstElement 101 | this.bottomElement = this.firstElement 102 | this.scrollIntoView(this.firstElement); 103 | this.traceElement(this.firstElement); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/libs/bookmark.ts: -------------------------------------------------------------------------------- 1 | import { Plugin } from "siyuan"; 2 | import { siyuan } from "./utils"; 3 | import { READINGPOINT } from "./gconst"; 4 | import { tomatoI18n } from "../tomatoI18n"; 5 | import { OpenSyFile2 } from "./docUtils"; 6 | 7 | export async function gotoBookmark(docID: string, plugin: Plugin) { 8 | const rows = await siyuan.sqlAttr(`select * from attributes where name='${READINGPOINT}' and root_id='${docID}'`); 9 | for (const row of rows) { 10 | await OpenSyFile2(plugin, row.block_id); 11 | break; 12 | } 13 | if (rows.length == 0) await siyuan.pushMsg(tomatoI18n.当前文档无书签, 2000); 14 | } 15 | 16 | export async function removeReadingPoint(docID: string) { 17 | const rows = await siyuan.sqlAttr(`select * from attributes where name='${READINGPOINT}' and root_id='${docID}'`); 18 | await siyuan.deleteBlocks(rows.map(row => row.block_id)); 19 | await siyuan.removeRiffCards(rows.map(row => row.block_id)); 20 | } 21 | 22 | export async function rmTodoBookmark(docID: string) { 23 | const rows = await siyuan.sqlAttr(`select * from attributes where name='bookmark' and value='🚩' and root_id='${docID}'`); 24 | await siyuan.batchSetBlockAttrs(rows.map(row => { 25 | return { id: row.block_id, attrs: { bookmark: "" } as AttrType }; 26 | })); 27 | } 28 | 29 | export async function addTodoBookmark(ids: string[]) { 30 | for (const id of ids) { 31 | const attr = await siyuan.getBlockAttrs(id); 32 | if (attr.bookmark == "🚩") 33 | await siyuan.setBlockAttrs(id, { bookmark: "" } as AttrType); 34 | else if (!attr.bookmark) 35 | await siyuan.setBlockAttrs(id, { bookmark: "🚩" } as AttrType); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/libs/cache.ts: -------------------------------------------------------------------------------- 1 | export class MaxCache { 2 | private cache: Map = new Map(); 3 | private _maxSize: number; 4 | private deleteCB?: (t: T) => void; 5 | public get maxSize(): number { 6 | return this._maxSize; 7 | } 8 | public set maxSize(value: number) { 9 | this._maxSize = value; 10 | } 11 | constructor(maxSize: number, deleteCB?: (t: T) => void) { 12 | this.maxSize = maxSize; 13 | this.deleteCB = deleteCB; 14 | } 15 | public entries() { 16 | return this.cache.entries(); 17 | } 18 | /** 19 | * add `obj` to cache, drop the old one without return it. 20 | * @param key 21 | * @param obj 22 | * @returns return the new value NOT the old one 23 | */ 24 | public add(key: string, obj: T): T { 25 | if (obj == null) return obj; 26 | if (this.cache.size > this.maxSize) { 27 | [...this.cache.entries()].sort((e1, e2) => { 28 | return e1[1].timestamp - e2[1].timestamp; 29 | }).slice(0, this.cache.size / 2).forEach(e => { 30 | if (this.deleteCB) this.deleteCB(e[1].obj); 31 | this.cache.delete(e[0]); 32 | }); 33 | } 34 | 35 | const e = this.get(key); 36 | if (e != null) 37 | if (this.deleteCB) this.deleteCB(e); 38 | this.cache.set(key, { obj: obj, timestamp: new Date().getTime() }); 39 | return obj; 40 | } 41 | /** 42 | * get or save 43 | * @param key 44 | * @param defaultValue 45 | * @returns 46 | */ 47 | public get(key: string, defaultValue?: T): T { 48 | const v = this.cache.get(key); 49 | if (v == null) { 50 | return this.add(key, defaultValue); 51 | } 52 | return v.obj; 53 | } 54 | /** 55 | * get or save 56 | * @param key 57 | * @param createValue 58 | * @returns 59 | */ 60 | public getOrElse(key: string, createValue: () => T): T { 61 | const v = this.cache.get(key); 62 | if (v == null) { 63 | return this.add(key, createValue()); 64 | } 65 | return v.obj; 66 | } 67 | public delete(key: string) { 68 | const e = this.get(key); 69 | if (e == null) return; 70 | if (this.deleteCB) this.deleteCB(e); 71 | return this.cache.delete(key); 72 | } 73 | } 74 | 75 | type DelCB = (k: string, o: any) => void; 76 | 77 | export class WeakCache { 78 | private cache: Map>; 79 | private registry: FinalizationRegistry; 80 | private delCB?: DelCB; 81 | constructor(delCB?: DelCB) { 82 | this.delCB = delCB; 83 | this.cache = new Map(); 84 | this.registry = new FinalizationRegistry((key: string) => { 85 | const value = this.cache.get(key); 86 | if (value != null) { 87 | const o = value.deref(); 88 | this.delete(key, o); 89 | } 90 | }); 91 | } 92 | set(key: string, o: T) { 93 | if (o == null) return; 94 | this.cache.set(key, new WeakRef(o)); 95 | this.registry.register(o, key); 96 | } 97 | get(key: string, miss?: T): T { 98 | const value = this.cache.get(key); 99 | if (value != null) { 100 | const o = value.deref(); 101 | if (o != null) return o; 102 | this.delete(key, null); 103 | } 104 | this.set(key, miss); 105 | return miss; 106 | } 107 | size() { 108 | return this.cache.size; 109 | } 110 | toKVList() { 111 | return [...this.entries()]; 112 | } 113 | entries() { 114 | return this.cache.entries(); 115 | } 116 | delete(key: string, v: T) { 117 | if (this.cache.delete(key)) { 118 | if (this.delCB) this.delCB(key, v); 119 | } 120 | } 121 | purge() { 122 | for (const [k, v] of this.entries()) { 123 | if (v?.deref() == null) this.delete(k, null); 124 | } 125 | } 126 | clear() { 127 | for (const [k, v] of this.entries()) { 128 | this.delete(k, v?.deref()); 129 | } 130 | } 131 | } 132 | 133 | /// Example usage 134 | /// const myMap = new DefaultMap(0); // Default value is 0 135 | export class DefaultMap extends Map { 136 | private dv: V | (() => V); 137 | 138 | constructor(dv: V | (() => V), entries?: readonly (readonly [K, V])[] | null) { 139 | super(entries); 140 | this.dv = dv; 141 | } 142 | 143 | get(key: K): V { 144 | return this.savingGet(key, this.dv); 145 | } 146 | 147 | protected savingGet(key: K, defaultValue: V | (() => V)): V { 148 | const v = super.get(key); 149 | if (v == null) { 150 | let newValue: V; 151 | if (typeof defaultValue === 'function') { 152 | newValue = (defaultValue as () => V)(); 153 | } else { 154 | newValue = defaultValue; 155 | } 156 | this.set(key, newValue); 157 | return newValue; 158 | } else { 159 | return v; 160 | } 161 | } 162 | } -------------------------------------------------------------------------------- /src/libs/cardUtils.ts: -------------------------------------------------------------------------------- 1 | import { tomatoI18n } from "../tomatoI18n"; 2 | import { events } from "./Events"; 3 | import { CUSTOM_RIFF_DECKS } from "./gconst"; 4 | import { getAttribute, isValidNumber, siyuan, timeUtil } from "./utils"; 5 | 6 | export async function removeDocCards(docID: string) { 7 | if (!docID) return; 8 | const ids = (await siyuan.sql(`select block_id as id from attributes 9 | where name="${CUSTOM_RIFF_DECKS}" 10 | and root_id="${docID}" 11 | limit 30000 12 | `)).map(row => row.id); 13 | await siyuan.removeRiffCards(ids); 14 | } 15 | 16 | export async function doStopCards(days: string, blocks: GetCardRetBlock[], spread = false) { 17 | if (!(blocks && blocks.length)) return; 18 | const numDays = Number(days); 19 | if (isValidNumber(numDays)) { 20 | function spreadByDays(idx: number) { 21 | return (idx + 1) * (numDays / blocks.length) 22 | } 23 | await siyuan.pushMsg(tomatoI18n.开始执行) 24 | await siyuan.batchSetBlockAttrs(blocks.map((b, idx) => { 25 | const newAttrs = {} as AttrType; 26 | if (numDays <= 0) { 27 | newAttrs["custom-card-priority-stop"] = ""; 28 | newAttrs.bookmark = ""; 29 | } else { 30 | let datetimeStr: string; 31 | if (spread) { 32 | datetimeStr = timeUtil.dateFormat(timeUtil.now(spreadByDays(idx) * 24 * 60 * 60)); 33 | } else { 34 | datetimeStr = timeUtil.dateFormat(timeUtil.now(numDays * 24 * 60 * 60)); 35 | } 36 | newAttrs["custom-card-priority-stop"] = datetimeStr; 37 | newAttrs.bookmark = "🛑 Suspended Cards"; 38 | } 39 | newAttrs["custom-card-priority-id"] = b.ial.id; 40 | return { id: b.ial.id, attrs: newAttrs }; 41 | })); 42 | 43 | await siyuan.batchSetRiffCardsDueTimeByBlockID(blocks.map((b, idx) => { 44 | let due: string; 45 | if (spread) { 46 | due = timeUtil.getYYYYMMDDHHmmss(timeUtil.nowts(spreadByDays(idx) * 24 * 60 * 60)); 47 | } else { 48 | due = timeUtil.getYYYYMMDDHHmmss(timeUtil.nowts(numDays * 24 * 60 * 60)); 49 | } 50 | return { 51 | id: b.ial.id, 52 | due, 53 | }; 54 | })); 55 | 56 | setTimeout(() => { 57 | events.protyleReload(); 58 | }, 500); 59 | await siyuan.pushMsg(tomatoI18n.推迟x个闪卡y天(blocks.length, days)); 60 | } 61 | } 62 | 63 | export function showCardAnswer() { 64 | const btnSpace = document.body.querySelector('div.card__action:not(.fn__none) > button[data-type="-1"]') as HTMLButtonElement; 65 | if (btnSpace) { 66 | btnSpace.click(); 67 | return true; 68 | } 69 | return false; 70 | } 71 | 72 | export function pressSkip() { 73 | const btnSkip = document.body.querySelector('button[data-type="-3"]') as HTMLButtonElement; 74 | if (btnSkip) { 75 | btnSkip.click(); 76 | return true; 77 | } 78 | return false; 79 | } 80 | 81 | export async function getIDFromCard() { 82 | const blockInDocCard = document.querySelector(`div.card__main div[data-doc-type="NodeDocument"][custom-riff-decks] > div[data-node-id]`); 83 | const subBlockID = getAttribute(blockInDocCard as any, "data-node-id"); 84 | let cardID = await siyuan.getDocIDByBlockID(subBlockID); 85 | 86 | if (!cardID) { 87 | const card = document.querySelector(`div.card__main div[data-node-id][custom-riff-decks]`); 88 | cardID = getAttribute(card as any, "data-node-id"); 89 | } 90 | return cardID; 91 | } 92 | 93 | -------------------------------------------------------------------------------- /src/libs/cozeAI.ts: -------------------------------------------------------------------------------- 1 | import { tomatoI18n } from "../tomatoI18n"; 2 | import { cozeSearchKnowledgeID, cozeSearchOauthTokenID } from "./stores"; 3 | import { chunks, getMarkdownsByTrees, siyuan } from "./utils"; 4 | 5 | async function postData(url: string, data = {}, headers = {}) { 6 | return fetch('https://api.coze.cn' + url, { 7 | method: 'POST', 8 | headers: { 9 | 'Content-Type': 'application/json', 10 | 'Authorization': 'Bearer ' + cozeSearchOauthTokenID.get(), 11 | 'Agw-Js-Conv': 'str', 12 | ...headers, 13 | }, 14 | body: JSON.stringify(data), 15 | }); 16 | } 17 | 18 | async function postGetJson(url: string, data = {}, headers = {}) { 19 | return postData(url, data, headers).then(r => r.json()) 20 | } 21 | 22 | export async function cozeAddDocTree(docID: string, boxID = "") { 23 | const rows = await getMarkdownsByTrees([docID], boxID) 24 | let name: string; 25 | if (!boxID) { 26 | for (const r of rows) { 27 | if (r.id === docID) { 28 | name = getCozeName(r.content, r.id) 29 | break 30 | } 31 | } 32 | } else { 33 | const nb = await siyuan.getNotebookConf(boxID) 34 | name = getCozeName(nb.name, boxID) 35 | } 36 | await coze_big_file(rows, name); 37 | siyuan.pushMsg(tomatoI18n.更新所有文档成功) 38 | } 39 | 40 | export function getCozeName(name: string, id: string) { 41 | return name?.replaceAll(".", "")?.slice(0, 20) + "." + id 42 | } 43 | 44 | async function coze_big_file(rows: Block[], name: string) { 45 | await cozeDeleteFromKnowledgeByName(name); 46 | const payload: any = { 47 | dataset_id: cozeSearchKnowledgeID.get(), 48 | chunk_strategy: { 49 | remove_extra_spaces: true, 50 | remove_urls_emails: true, 51 | chunk_type: 0 52 | }, 53 | format_type: 0, 54 | document_bases: [ 55 | { 56 | source_info: { 57 | file_type: "md", 58 | file_base64: encodeToBase64(rows.map(r => r.markdown).join("\n\n")), 59 | }, 60 | name, 61 | } 62 | ] 63 | }; 64 | await postGetJson("/open_api/knowledge/document/create", payload); 65 | } 66 | 67 | export async function cozeDeleteFromKnowledgeByName(name: string) { 68 | const all = await coze_get_all_files() 69 | for (const f of all) { 70 | if (f.name === name) { 71 | await postGetJson("/open_api/knowledge/document/delete", { document_ids: [f.document_id] }); 72 | } 73 | } 74 | } 75 | 76 | export async function cleanCozeDocs() { 77 | const document_ids = await coze_get_all_files().then(fs => fs.map(f => f.document_id)) 78 | for (const ids of chunks(document_ids, 10)) { 79 | await postGetJson("/open_api/knowledge/document/delete", { document_ids: ids }); 80 | } 81 | } 82 | 83 | // https://app.quicktype.io/?l=ts 84 | export async function coze_get_all_files(size = 100) { 85 | const all = [] as CozeDocumentInfo[] 86 | let page = 1; 87 | while (true) { 88 | const ret = await postGetJson("/open_api/knowledge/document/list", { 89 | "dataset_id": cozeSearchKnowledgeID.get(), 90 | "page": page++, 91 | size, 92 | }) as CozeListDoc; 93 | if (ret.document_infos) all.push(...ret.document_infos) 94 | if (all.length >= ret.total) break; 95 | if (ret.code != 0) break; 96 | } 97 | return all; 98 | } 99 | 100 | function encodeToBase64(str: string): string { 101 | const utf8Bytes = new TextEncoder().encode(str); 102 | const binaryArray = []; 103 | for (const byte of utf8Bytes) { 104 | binaryArray.push(String.fromCharCode(byte)); 105 | } 106 | const binaryString = binaryArray.join(''); 107 | return btoa(binaryString); 108 | } 109 | -------------------------------------------------------------------------------- /src/libs/destroyer.ts: -------------------------------------------------------------------------------- 1 | export class DestroyManager { 2 | private destroied = false; 3 | private cbs = new Map(); 4 | private actions: Func[] = []; 5 | private data = new Map(); 6 | private showMsg: boolean; 7 | private prefix: string; 8 | constructor(showMsg = false, prefix: string = "DestroyManager") { 9 | this.prefix = prefix; 10 | this.showMsg = showMsg; 11 | } 12 | setData(key: string, value: any) { 13 | this.data.set(key, value); 14 | } 15 | getData(key: string) { 16 | return this.data.get(key) as T; 17 | } 18 | action(cb: Func) { 19 | this.actions.push(cb); 20 | } 21 | run() { 22 | this.actions.forEach(i => i()); 23 | } 24 | add(name: string, cb: Func) { 25 | this.cbs.set(name.trim(), cb); 26 | } 27 | destroyBy(name: string = null) { 28 | if (!this.destroied) { 29 | this.destroied = true; 30 | const lst = [...this.cbs.entries()]; 31 | if (name == null) { 32 | lst.forEach(([k, v]) => { 33 | if (this.showMsg) console.info(`[${this.prefix}] DESTROY [${k}] BY NONE`); 34 | v(); 35 | }); 36 | } else { 37 | name = name.trim(); 38 | lst.filter(([k]) => k !== name).forEach(([k, v]) => { 39 | if (this.showMsg) console.info(`[${this.prefix}] DESTROY [${k}] BY [${name}]`); 40 | v(); 41 | }); 42 | } 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /src/libs/focusUtils.ts: -------------------------------------------------------------------------------- 1 | import { events } from "./Events"; 2 | import { awaysExitFocusStore } from "./stores"; 3 | import { isCardByUpLook } from "./utils"; 4 | 5 | export function autoExitFocus() { 6 | if (awaysExitFocusStore.get() && !events.isMobile) { 7 | setTimeout(() => { 8 | findExitFocus(); 9 | }, 1200); 10 | } 11 | } 12 | 13 | function findExitFocus() { 14 | const elements = document.querySelectorAll(`button[data-type="exit-focus"]`) as unknown as HTMLElement[]; 15 | for (const e of elements) { 16 | if (!e.classList.contains("fn__none") && !isCardByUpLook(e)) { 17 | e.click(); 18 | setTimeout(() => { 19 | doUnpin(e); 20 | }, 1000); 21 | } 22 | } 23 | } 24 | 25 | function doUnpin(e: HTMLElement) { 26 | const popover = findUpuntilPopover(e); 27 | const pinElement = popover?.querySelector("span[data-type='pin']"); 28 | if (pinElement instanceof HTMLElement) { 29 | pinElement.click(); 30 | } 31 | } 32 | 33 | function findUpuntilPopover(e: HTMLElement) { 34 | while (e) { 35 | if (e.classList.contains("block__popover--open")) { 36 | return e; 37 | } 38 | e = e.parentElement as HTMLElement; 39 | } 40 | } -------------------------------------------------------------------------------- /src/libs/functional.ts: -------------------------------------------------------------------------------- 1 | export function zipNways(...arrays: T): Array<{ [K in keyof T]: T[K][number] }> { 2 | // if T[K] is string[], then T[K][number] would be string 3 | // this is equivalent to saying "give me the type of the elements inside each of the arrays in T 4 | const maxLength = Math.max(...arrays.map(arr => { 5 | if (!arr) arr = [] 6 | return arr.length 7 | })); 8 | const zipped = Array.from({ length: maxLength }, (_, rowIdx) => ( 9 | arrays.map((_, arrIdx) => arrays[arrIdx][rowIdx]) as { [K in keyof T]: T[K][number] })); 10 | return zipped; 11 | } 12 | 13 | export function flatChunkMap(array: any[], num: number, map: (ts: any[]) => M) { 14 | array = array.flat(); 15 | const newArr: M[] = []; 16 | for (let i = 0; i < array.length; i += num) { 17 | const part = array.slice(i, i + num); 18 | if (part.length > 0) { 19 | newArr.push(map(part)); 20 | } 21 | } 22 | return newArr; 23 | } 24 | 25 | export async function aFlatChunkMap(array: any[], num: number, map: (ts: any[]) => M) { 26 | return flatChunkMap(await Promise.all(array.flat()), num, map); 27 | } 28 | 29 | export function isIterable(obj: any): boolean { 30 | if (obj == null) return obj; 31 | 32 | // Check if the object has the Symbol.iterator property 33 | if (typeof obj[Symbol.iterator] === "function") { 34 | return true; 35 | } 36 | 37 | // Check if the object has the @@iterator method 38 | if (typeof obj["@@iterator"] === "function") { 39 | return true; 40 | } 41 | 42 | // If neither is present, the object is not iterable 43 | return false; 44 | } -------------------------------------------------------------------------------- /src/libs/globalUtils.ts: -------------------------------------------------------------------------------- 1 | 2 | const prefix = "DaUaeWLNBpAf5CWCSZwZ4eEt3D78" 3 | 4 | export function getGlobal(key: string): T { 5 | return globalThis[prefix + key] 6 | } 7 | 8 | export function setGlobal(key: string, v: T): T { 9 | const old = globalThis[prefix + key]; 10 | globalThis[prefix + key] = v 11 | return old; 12 | } 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/libs/hash.ts: -------------------------------------------------------------------------------- 1 | export async function sha1(message: string) { 2 | const msgBuffer = new TextEncoder().encode(message); // 编码为UTF-8 3 | const hashBuffer = await crypto.subtle.digest('SHA-1', msgBuffer); // 哈希 4 | const hashArray = Array.from(new Uint8Array(hashBuffer)); // 转换为字节数组 5 | const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); // 转换为十六进制 6 | return hashHex; 7 | } 8 | 9 | /** 10 | * 左移循环(32位) 11 | */ 12 | function rotl32(x: number, r: number): number { 13 | return (x << r) | (x >>> (32 - r)); 14 | } 15 | 16 | /** 17 | * 最终混音步骤 18 | */ 19 | function mixmur3(h: number): number { 20 | h = h ^ (h >>> 16); 21 | h = (h * 0x85ebca6b) >>> 0; 22 | h = h ^ (h >>> 13); 23 | h = (h * 0xc2b2ae35) >>> 0; 24 | h = h ^ (h >>> 16); 25 | return h; 26 | } 27 | 28 | /** 29 | * MurmurHash3 32位版本简化实现 30 | * @param input 字符串或字节数组 31 | * @param seed 初始种子值,默认为 0x1BD11DBA 32 | * @returns 32位无符号整数哈希值 33 | */ 34 | export function murmurHash3(input: string | Uint8Array, seed: number = 0x1BD11DBA): number { 35 | let data: Uint8Array; 36 | 37 | // 将字符串转换为 UTF-8 编码的 Uint8Array 38 | if (typeof input === 'string') { 39 | data = new TextEncoder().encode(input); 40 | } else { 41 | data = input; 42 | } 43 | 44 | let h1 = seed; 45 | const len = data.length; 46 | const nblocks = Math.floor(len / 4); // 每 4 字节为一块 47 | 48 | const c1 = 0xcc9e2d51; 49 | const c2 = 0x1b873593; 50 | 51 | // 处理每一块(4 字节) 52 | for (let i = 0; i < nblocks; i++) { 53 | const i4 = i * 4; 54 | let k1 = data[i4] | (data[i4 + 1] << 8) | (data[i4 + 2] << 16) | (data[i4 + 3] << 24); 55 | 56 | k1 = (k1 * c1) >>> 0; 57 | k1 = rotl32(k1, 15); 58 | k1 = (k1 * c2) >>> 0; 59 | 60 | h1 ^= k1; 61 | h1 = rotl32(h1, 13); 62 | h1 = (h1 * 5 + 0xe6546b64) >>> 0; 63 | } 64 | 65 | // 处理剩余部分(不足 4 字节) 66 | const remaining = len % 4; 67 | let k1 = 0; 68 | 69 | if (remaining > 0) { 70 | switch (remaining) { 71 | case 3: 72 | k1 ^= (data[len - 3] << 16) >>> 0; 73 | k1 ^= (data[len - 2] << 8) >>> 0; 74 | k1 ^= data[len - 1]; 75 | break; 76 | case 2: 77 | k1 ^= (data[len - 2] << 8) >>> 0; 78 | k1 ^= data[len - 1]; 79 | break; 80 | case 1: 81 | k1 ^= data[len - 1]; 82 | break; 83 | } 84 | 85 | k1 = (k1 * c1) >>> 0; 86 | k1 = rotl32(k1, 15); 87 | k1 = (k1 * c2) >>> 0; 88 | 89 | h1 ^= k1; 90 | } 91 | 92 | // 最终混音 93 | h1 ^= len; 94 | h1 = mixmur3(h1); 95 | 96 | return h1 >>> 0; // 返回 32 位无符号整数 97 | } -------------------------------------------------------------------------------- /src/libs/ial.ts: -------------------------------------------------------------------------------- 1 | enum ParseState { 2 | Start, // 等待开始标记 3 | InTag, // 已进入标签 4 | AttrKey, // 正在读取属性名 5 | PreValue, // 等待等号后的引号 6 | AttrValue, // 正在读取属性值 7 | AfterValue // 值读取完成后的状态 8 | } 9 | 10 | interface ParseResult { 11 | [key: string]: string; 12 | } 13 | 14 | export function parseCustomTag(s: string): ParseResult { 15 | let state: ParseState = ParseState.Start; 16 | const result: ParseResult = {}; 17 | let currentKey = ''; 18 | let currentValue = ''; 19 | let quoteChar: '"' | "'" | null = null; 20 | const stack: string[] = []; // 用于检测标签边界 21 | 22 | for (const c of s) { 23 | switch (state) { 24 | case ParseState.Start: 25 | if (c === '{') { 26 | stack.push(c); 27 | } else if (stack.length > 0 && c === ':' && stack[stack.length - 1] === '{') { 28 | state = ParseState.InTag; 29 | stack.length = 0; // 清空栈 30 | } 31 | break; 32 | 33 | case ParseState.InTag: 34 | if (c === '}') { 35 | if (stack.length > 0 && stack[stack.length - 1] === ':') { 36 | stack.pop(); 37 | return result; // 提前结束解析 38 | } 39 | } else if (c === ':') { 40 | stack.push(c); 41 | } else if (!/\s/.test(c)) { // 非空白字符开始新属性 42 | state = ParseState.AttrKey; 43 | currentKey = c; 44 | } 45 | break; 46 | 47 | case ParseState.AttrKey: 48 | if (c === '=' || /\s/.test(c)) { 49 | state = ParseState.PreValue; 50 | } else { 51 | currentKey += c; 52 | } 53 | break; 54 | 55 | case ParseState.PreValue: 56 | if (c === '"' || c === "'") { 57 | quoteChar = c; 58 | state = ParseState.AttrValue; 59 | currentValue = ''; 60 | } 61 | break; 62 | 63 | case ParseState.AttrValue: 64 | if (c === quoteChar) { 65 | result[currentKey.trim()] = currentValue; 66 | currentKey = ''; 67 | state = ParseState.AfterValue; 68 | } else { 69 | currentValue += c; 70 | } 71 | break; 72 | 73 | case ParseState.AfterValue: 74 | if (c === '}') { 75 | if (stack.length > 0 && stack[stack.length - 1] === ':') { 76 | return result; 77 | } 78 | } else if (/\s/.test(c)) { 79 | state = ParseState.InTag; // 准备读取下一个属性 80 | } else if (c === ':') { 81 | stack.push(c); 82 | } 83 | break; 84 | } 85 | } 86 | 87 | return result; 88 | } 89 | 90 | 91 | // 测试用例 92 | // const testStr = '{: custom-tomatoUpdated="1748308949028" id="20250526082838-qq7xnww" title="2025-05-26-周一" }'; 93 | // console.log(parseCustomTag(testStr)); 94 | /* 输出: 95 | { 96 | "custom-tomatoUpdated": "1748308949028", 97 | "id": "20250526082838-qq7xnww", 98 | "title": "2025-05-26-周一" 99 | } 100 | */ 101 | -------------------------------------------------------------------------------- /src/libs/keyboard.ts: -------------------------------------------------------------------------------- 1 | export function escOnElement(e: HTMLElement) { 2 | if (e) { 3 | const escEvent = new KeyboardEvent("keydown", { 4 | key: "Escape", 5 | code: "Escape", 6 | charCode: 27, 7 | keyCode: 27, 8 | view: window, 9 | bubbles: true, 10 | }); 11 | e.dispatchEvent(escEvent); 12 | } 13 | } 14 | 15 | export function closeAllDialog() { 16 | document.querySelectorAll(`svg.b3-dialog__close`).forEach(e => click(e)); 17 | } 18 | 19 | export function click(e: Node) { 20 | if (!e?.dispatchEvent) return; 21 | e.dispatchEvent(new MouseEvent('click', { 22 | bubbles: true, 23 | cancelable: true, 24 | view: window 25 | })); 26 | } 27 | 28 | export function preventKeyboard(event: KeyboardEvent) { 29 | // $autoRefreshChecked = false; 30 | if (event.defaultPrevented) { 31 | return; 32 | } 33 | if (event.ctrlKey && event.key == "c") { 34 | return; 35 | } 36 | event.preventDefault(); 37 | } -------------------------------------------------------------------------------- /src/libs/openAI.ts: -------------------------------------------------------------------------------- 1 | import OpenAI from 'openai'; 2 | import { cancelSuperBlock, NewNodeID, Siyuan, siyuan, } from './utils'; 3 | import { Stream } from 'openai/streaming'; 4 | import { ChatCompletionChunk, ChatCompletionMessageParam } from 'openai/resources/chat/completions'; 5 | 6 | export class OpenAIClient { 7 | private openai: OpenAI; 8 | constructor(apiKey: string, baseURL: string) { 9 | this.openai = new OpenAI({ 10 | apiKey, 11 | baseURL, 12 | dangerouslyAllowBrowser: true, 13 | }); 14 | } 15 | 16 | static async getModel(key: string, baseURL: string, model: string, noSup = false) { 17 | const openAI = new OpenAIClient(key, baseURL); 18 | return (prompt: string, anchorID = "") => { 19 | return openAI.do_completions(model, prompt, anchorID, noSup) 20 | }; 21 | } 22 | 23 | static getOfficalModel(noSup = false) { 24 | const aiCfg = Siyuan.config?.ai?.openAI; 25 | if (aiCfg.apiBaseURL && aiCfg.apiModel) { 26 | const openAI = new OpenAIClient(aiCfg.apiKey, aiCfg.apiBaseURL); 27 | return (prompt: string, anchorID = "") => { 28 | return openAI.do_completions(Siyuan.config.ai.openAI.apiModel, prompt, anchorID, noSup) 29 | }; 30 | } 31 | } 32 | 33 | private async do_completions(model: string, useInputTxt: string, anchorID: string, noSup: boolean) { 34 | let aiRespTxt: string; 35 | let texts: string[] = []; 36 | let reasoning_texts: string[] = []; 37 | let stream: Stream; 38 | const messages: ChatCompletionMessageParam[] = [{ role: "user", content: useInputTxt }] 39 | try { 40 | stream = await this.openai.chat.completions.create({ 41 | model, 42 | messages, 43 | stream: true, 44 | }); 45 | } catch (e) { 46 | console.error(e, messages) 47 | return; 48 | } 49 | 50 | let targetID = ""; 51 | if (anchorID) { 52 | targetID = NewNodeID(); 53 | await siyuan.insertBlockAfter(`{: id="${targetID}"}`, anchorID); 54 | } 55 | 56 | const write = () => { 57 | if (targetID) { 58 | return siyuan.safeUpdateBlock( 59 | targetID, 60 | `{{{row\n\n${aiRespTxt}\n\n}}}\n{: id="${targetID}" custom-ai-response="1"}`, 61 | ); 62 | } 63 | } 64 | 65 | let i = 0; 66 | for await (const chunk of stream) { 67 | const delta = chunk.choices?.at(0)?.delta; 68 | texts.push(delta?.content ?? ""); 69 | reasoning_texts.push((delta as any)?.reasoning_content ?? ""); 70 | aiRespTxt = texts.join("").trim(); 71 | if (!aiRespTxt) { 72 | const reasoning = reasoning_texts.join("").trim(); 73 | if (reasoning) { 74 | aiRespTxt = reasoning; 75 | } else { 76 | aiRespTxt = `thinking ${i}...` 77 | } 78 | } 79 | if (i++ % 50 === 0) await write(); 80 | } 81 | 82 | if (aiRespTxt.startsWith("")) { 83 | const div = document.createElement("div") 84 | div.innerHTML = aiRespTxt; 85 | const t = div.querySelector("think") 86 | div.removeChild(t); 87 | aiRespTxt = div.innerHTML; 88 | } 89 | 90 | await write(); 91 | if (noSup && targetID) { 92 | await cancelSuperBlock(targetID); 93 | } 94 | return { targetID, aiRespTxt }; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/libs/progressive.ts: -------------------------------------------------------------------------------- 1 | import { TEMP_CONTENT } from "./gconst"; 2 | import { siyuan } from "./utils"; 3 | 4 | export async function getBookIDByBlock(blockID: string) { 5 | const docRow = await siyuan.getDocRowByBlockID(blockID); 6 | return getBookID(docRow?.id); 7 | } 8 | 9 | export async function getBookID(docID: string): Promise<{ bookID: string, pieceNum: number }> { 10 | const ret = { bookID: "", pieceNum: NaN } as Awaited>; 11 | if (docID) { 12 | const attrs = await siyuan.getBlockAttrs(docID); 13 | let mark = attrs["custom-progmark"]; 14 | const writing = attrs["custom-book-writing"] 15 | if (mark) { 16 | mark = mark.split(TEMP_CONTENT).pop(); 17 | const last = mark.split("#").pop(); 18 | const parts = last.split(","); 19 | ret.bookID = parts[0]; 20 | ret.pieceNum = Number(parts[1]); 21 | } else if (writing) { 22 | const parts = writing.split("#"); 23 | ret.bookID = parts[0] 24 | ret.pieceNum = parseInt(parts.pop(), 10); 25 | } 26 | } 27 | return ret; 28 | } -------------------------------------------------------------------------------- /src/libs/search.ts: -------------------------------------------------------------------------------- 1 | enum SearchEngineConditionTypeOr { 2 | include = "inc", 3 | exclude = "exc", 4 | } 5 | enum SearchEngineConditionType { 6 | include = "inc", 7 | or = "or", 8 | exclude = "exc", 9 | } 10 | 11 | type SearchEngineConditionOr = { type: SearchEngineConditionTypeOr, value: string } 12 | type SearchEngineCondition = { type: SearchEngineConditionType, value: string, values: SearchEngineConditionOr[] } 13 | 14 | export class SearchEngine { 15 | private conditions: SearchEngineCondition[] = []; 16 | private isCaseInsensitive: boolean; 17 | 18 | constructor(isCaseInsensitive: boolean) { 19 | this.isCaseInsensitive = isCaseInsensitive; 20 | } 21 | 22 | setQuery(query: string) { 23 | if (this.isCaseInsensitive) 24 | query = query.toLowerCase(); 25 | this.conditions = query.trim().replace(/[!!]+/g, "!").replace(/\s+/, " ") 26 | .replace(/ ?\| ?/g, "|").split(" ").filter(c => c.length > 0) 27 | .filter(c => c != "!").filter(c => c != "|").map(c => { 28 | const con = c.split("|").map(c => c.trim()).filter(c => c.length > 0); 29 | const ret = {} as SearchEngineCondition; 30 | if (con.length == 1) { 31 | const s = con[0]; 32 | if (s[0] == "!") { 33 | ret.type = SearchEngineConditionType.exclude; 34 | ret.value = s.slice(1); 35 | } else { 36 | ret.type = SearchEngineConditionType.include; 37 | ret.value = s; 38 | } 39 | } else { 40 | ret.type = SearchEngineConditionType.or; 41 | ret.values = con.map(c => { 42 | const ret = {} as SearchEngineConditionOr; 43 | if (c[0] == "!") { 44 | ret.type = SearchEngineConditionTypeOr.exclude; 45 | ret.value = c.slice(1); 46 | } else { 47 | ret.type = SearchEngineConditionTypeOr.include; 48 | ret.value = c; 49 | } 50 | return ret; 51 | }); 52 | } 53 | return ret; 54 | }).filter(c => { 55 | if (c.type == SearchEngineConditionType.or) { 56 | if (c.values.length == 0) return false; 57 | } 58 | return true; 59 | }); 60 | } 61 | 62 | jsonCon() { 63 | return JSON.stringify(this.conditions); 64 | } 65 | 66 | match(text: string): boolean { 67 | if (this.isCaseInsensitive) 68 | text = text.toLowerCase(); 69 | for (const con of this.conditions) { 70 | if (con.type == SearchEngineConditionType.include) { 71 | if (!text.includes(con.value)) return false; 72 | } else if (con.type == SearchEngineConditionType.exclude) { 73 | if (text.includes(con.value)) return false; 74 | } else if (con.type == SearchEngineConditionType.or) { 75 | let flag = false; 76 | for (const subCon of con.values) { 77 | if (subCon.type == SearchEngineConditionTypeOr.exclude) { 78 | if (!text.includes(subCon.value)) { 79 | flag = true; 80 | break; 81 | } 82 | } else if (subCon.type == SearchEngineConditionTypeOr.include) { 83 | if (text.includes(subCon.value)) { 84 | flag = true; 85 | break; 86 | } 87 | } 88 | } 89 | if (!flag) return false; 90 | } 91 | } 92 | return true; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/libs/switchDraft.ts: -------------------------------------------------------------------------------- 1 | import { IProtyle, Plugin } from "siyuan"; 2 | import { OpenSyFile2, isReadonly } from "./docUtils"; 3 | import { events } from "./Events"; 4 | import { storeNoteBox_fastnote, fastNoteBoxDelAfterCreating, fastNoteBoxAdd2Flashcard } from "./stores"; 5 | import { verifyKeyTomato } from "./user"; 6 | import { siyuan, getContextPath, cloneCleanDiv, NewLute, NewNodeID, timeUtil } from "./utils"; 7 | 8 | export async function switchDraft(plugin: Plugin, protyle: IProtyle) { 9 | const docID = protyle?.block?.rootID; 10 | if (!docID) return; 11 | const title = protyle.title?.editElement?.textContent 12 | const bt = `backside-${title}`; 13 | const attrs = await siyuan.getBlockAttrs(docID); 14 | let draftID = attrs["custom-fastdraft"]; 15 | let isFastNote = !!attrs["custom-fastnote"]; 16 | if (await siyuan.checkBlockExist(draftID)) { 17 | await OpenSyFile2(plugin, draftID); 18 | if (isFastNote) { 19 | //close docID 20 | // document.querySelectorAll("span.item__text").forEach((e: HTMLElement) => { 21 | // if (e.textContent === title) { 22 | // const s = e.nextElementSibling as HTMLButtonElement; 23 | // if (s?.click) s.click(); 24 | // } 25 | // }); 26 | } else { 27 | await siyuan.setBlockAttrs(draftID, { title: bt }) 28 | } 29 | } else { 30 | const newID = await createNote(plugin, protyle, false, { 31 | "custom-fastdraft": docID, 32 | "custom-off-tomatobacklink": "1", 33 | }, bt) 34 | await siyuan.setBlockAttrs(docID, { "custom-fastdraft": newID }); 35 | } 36 | } 37 | 38 | 39 | export async function createNote(plugin: Plugin, protyle: IProtyle, allowFlashcard = true, attrs: AttrType = {}, title = "") { 40 | let boxID = storeNoteBox_fastnote.getOr(); 41 | if (!boxID || !protyle) return; 42 | const { selected, ids, cursorOnly } = await events.selectedDivs(protyle); 43 | if (ids.length <= 0) return; 44 | 45 | const { getPathMd } = await getContextPath(ids[0]); 46 | const path = `${getPathMd()}\n{: id="${NewNodeID()}"}\n` 47 | const lute = NewLute(); 48 | const content = selected.map(d => { 49 | d = cloneCleanDiv(d).div 50 | return lute.BlockDOM2Md(d.outerHTML); 51 | }); 52 | const taskRo = isReadonly(protyle) 53 | const id = await createAndOpenFastNote(boxID, plugin, attrs, title, path + content.join("\n")); 54 | if (await verifyKeyTomato() && fastNoteBoxDelAfterCreating.get() && await taskRo === "false" && !cursorOnly) await siyuan.transactions(siyuan.transDeleteBlocks(ids)); 55 | if (fastNoteBoxAdd2Flashcard.get() && allowFlashcard) { 56 | setTimeout(() => { 57 | siyuan.addRiffCards([id]) 58 | }, 800); 59 | } 60 | return id; 61 | } 62 | 63 | export async function createAndOpenFastNote(boxID: string, plugin: Plugin, attrs: AttrType = {}, title: string = "", md = "") { 64 | const { y, M, d, h, m, s } = timeUtil.nowYMDStrPad(); 65 | if (!title) title = `f${y}-${M}-${d} ${h}:${m}:${s}`; 66 | const hpath = `/fast note/f${y}/f${y}-${M}/${title}`; 67 | const id = await siyuan.createDocWithMdIfNotExists(boxID, hpath, md, { ...attrs, "custom-fastnote": y + M + d + h + m + s }); 68 | await OpenSyFile2(plugin, id); 69 | return id; 70 | } 71 | -------------------------------------------------------------------------------- /src/libs/taobaocode.ts: -------------------------------------------------------------------------------- 1 | export const taobaoStore = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAAB4CAYAAAA5ZDbSAAAAAXNSR0IArs4c6QAADvBJREFUeF7tXWmMHMUV/rp7dmZ2uWwckE2yIQ6xsAyYM2DFsRCxIQQcJSjhMBEmUjgFwWQxAsPigGOOSPgIBMUEnMgYiA1IEK6EwwERGywOoQRbNhaBYDtgRb6AsLtzdUczszPbO1tV71V1z87OUPPL2u5+VfW+9753VHXbCYIggP21rAYcC3DLYltamAW4tfEdDLDjOLEst8L6YXnhSFD5O/dv1KREcqhnRNepaMXVD7X+ytgi/XDHoNZXnUOYomMXHjIYLpgmYJk8YwGmTERxnbJg68Fl5TXUgym6qsVXRsciOxAxhcgoKs9S3k+NoStbZrsinXCNVUXL4fF09MjBYFCSFbdwmaIswGXNqAxGdl1FrEJGkMVgldWHB6HoWOVduiwhs3TRfCiv5kYgLghU/sLVkwltq2STHhzXxEXgWICH9pgswP2WIjM8ldFQxsrNrOMGgYrB1Lwb5sEmdTClZJPFqujYpMSinlEqXNJrMHmmNgkd9hhsAR5oHFE5TVMCrEM9uqUMFb8pT4/bq+vBPJRRNNyDLcADSRRlcNaDa5Ir68ED7hMpyeLWi1STRNXciMvTdTpQ3M6SST1NGZ9usyIKBmQdHEU41ZgwaSJw45JJ84MLDJduubqjDJwrRyuL5gpVJRcmWTTVd1b1fotz4cYy3fuKsrl9Z937ZPOOBYNm2C7kgm4BFsRlC7C6VuUaV5T7hs2Do1CCzrPc3SQqRnPH5LYvo8TguADmrol7X0POZFmAy/BQlQcXRGVWTp2qHK7GuipZo7JxUcZMZbpxKE9HBrVdKErMRPJV95FZtEqgTpygFlMZhwLBhKJNyigdoEzvpXRiAe7XLNcoqPtMgTJ9bkQAzO02cRcZNcbo0pGsVuUyRuU+brJF6YEKb1ETM9X4VYOiyqQoi7UA168Eo4zLAqzQUBSjliWEJhRNde2GxYOjxDpKkXEsUIe2qQSNW0+rqF6mL13Zw+bBFuChNe2IBZiqNymaqU1mZFbJbXRwFUUlK6okiyrFuNcpQ6fkiNbKTXqVyais0cEFQTVxC/CAy1iA+99D17XacANGJ96asIOICai4zR1n2D2YCuK113VqPpWiqDBBKYKbrKiAoZiH640iHXITS5n+uWMLyyRdUE1LAi7AUeYz3M+uX78e3d3dWLNmjXLohgJMNSZ06ZPyhFpNTJ8+HS+++OJwYxP7eNy+MjUwlbjVshm52dBogF977TVMmTKFWveIv24BJl7fGPEIEhNsKoApmuDWxhRoxXGo+ETJaIbrMpZUde24iVV4/WQvWjf9p+idUj4b4CCA/+46BJ/thL/57/D3fEyJjnTdHT0O7sRpcPb7EtzDpxaPYUSS11CAuTPngkndV8sOpAf3fILMgu+gsG0DkM9ypxvPfYkkvM4jkJr/EtBxgFImxXqqUo/yVu0ykDqyI1oJBRy3DNIB2N/4EjILT0XgF+IBzFCK43pIdb8A94hTpBIswP2q4QIc9HyCvp+NaTi4VcN1PaSX74Ij8eSmAFi3F03VyCadrpJCgwCZecej8MHbhj5Xn8e88ccidftbwpismzAVZ8g1Cm16j2uzoV4A+5vXou9X04c/5lJ2kUgifdMauBO/PeTOpgCYWp/qumqBqudECUThjSeQufOsKNOp27OpuY/D++YPWQBHmQTFfiLZsfeiw4PECXBu5TXIPb04in7q9mzbzC60XbDIAsyNMSIPztx1Pgrr/lQ3kKII9qbOQuqqh1sPYIoyqFpORSm1177IAJsyYTk3LX8+wujdpJEIsLPvGMAvIOjZW7UR58BOuIdOhr/lVQSf74nirMJn6+3BFuB+tSeveADesWeUSqr804uR+/PtpSvpOzfA7ZyE4NNdCHZtQ9/1xw0Fyu//k6uPf9MBTNViuhsLMnkyCjeJwc6oQ9C+bHuoHg3Qc44Lt/NIpBe9U0atWEvfPRvuQZ1Ax2h4k06G87Wj4bSlBqEa7N2B/NqHUVi3Cv77b5CI6wDMLSPDOlDhQd1HbjYoU2/Jh75rnxkWgA8Yh/bfbQW8RHn4Qh49s5JI3fAsvGNOH7qMwEfwv92lutrfugn+xr8BHfsjMfVcOKPGAf2gZ++7BPk195eMQ/ZraoBNEiXS5BU3yJroZJLlJpD47s+R/Gm5lMo+MBeFtQ+i/e73gVTHoBHzz92DwltPw3/vdQSf7x4ym8QZVyN54ZKy03++F31zJpR2rUwBDj8Xlz5VcoTXqM8JhydJUXhDAHZcwPWAfA5wAGf/g5FesgnOvgcCuQwKG1+GO+EkOPuMKk0vt6obucdvlU61Y/WAx/bdNA3+lrUW4CjAVp419WDngLFI//pN9F4+HghySF37BLwTflBm61dXI7viGrQv2wo4LvLrViO7dJacdj0gDHD++WXILr+8dQAWrYTyWu7+JEVXMi0qKTq9L9rv+bDkrcHOrcjeewn8f7+NxMyuksfmHrkZ6UUb4Ow3hmWDfTdOQfrW9dV7/R3/Qt+cb2gDzBpM4xMO3ARNpGOyDh7RAANILVgH77DjgUQ5I84sORuF9Y+V/p046yYkz1uAYPd/0Hvl15G6/hl4k2eUY+zOrcg9ObjNWFi/Gu2/31HVU9DXg94L92kdgKNs5FPND8qqTSm6eITGnTAFydlL4U44EcW9496LDwHyPWg77za0nXk1eudORvDfD5Ca9xd4R59amkqxDOqbd+KQaYUpGr6PnlmeMcCUPimdqY5NRS6TuNSrqo0p7+fQNplFV4QkO9C+eBOcg76K3KMLUMyYUzc8AwT5IhkCbgLO2Alw2vcvP1Esl7KZQeBlbj4Z6dtfH/S3nnPlZ7BkZVJFgAU4pEpjDw7JSF6wqBR/cw/NQ/6VlWi/d7uaOGqO//R1T0X6toEYDD9Azyx5i6vpAA5rQ/dEh0iTVKODsvBSTOXuJnkJtC/dAufg8ei9aCyczklwDvxK2Vm3/hP+tg1DKfqGk5CY+hO4x5yOwssrUNjwwqAsGple9MweXEuH10kBbKIT6hluPU1uNjQbwIlps5G8ckWp79x72Tiku9fAnTStpK/sQ9ch/9QiYQxOXfskvBO+X6LsnlkJdKyqNKeBYO9H6L30y8YxmAKLE6KK91COoMRquBodlOVFoejE9EuRvOi3pTibe+rOUjMjPf9luIeXX4PJrpyL/DNLawB+Hdk/diHV9Sic0eNK92XuOLOUaVd+hbf/iswd34sF4Cg7Q5QhtDTA7lEzkOp6rHrCse/GE+G/9wZS1z0F77iZJd34H/4DuVXz0XbOLXDHHzMIsGDPx1WA88/ehcQZV1WvZ/9wFfLP3W0BVmcy5av18ODieajUnFXVDYLSAb1flmnZO+kcpLpWK6dWbGNmF/8I6WLs3m8MsssuRvKy+6rP9M45HMGOLa0DMBUz4qjLwmPUUovudmERGHfchLLIQg69VxyKokdWft7U8+EddVrZQ/08gj07EOzeBuQzyL/yYPnfANzOyfA/3ozEaVcgeWF50yL4dCf6uiYi+GyXNsBRTlWqylPZRNgU3WwAo60D7b95F86osSi88wIyd8wsJUusn9eGjoez8Ldvgv/em0h868dAsr1CN8i/tBzZey9WijLZLpQBKDIK1jokZ6rJLLoiPK5ui8hza7sxuh5cdr8E3IknA54H/53nuTqBN+VspH7xyND7i+8/3X8566BfPcokSt/cBbYOwNwVi+4r7gzuMxruwYchCAoIPtoM5HrZEi3AktMQshhl5MFsOOK/sekA5m5LiRrd3OZ3Uc068aZVDr5Ta6Z0zzFPIb3rfG2Wmx1Sk6UWG15Mq7y6Qq2Z0lnLAvxFfvlMt2QiPZiyEp3tP5UsWT0tXFATvz4a1oEuWLJQxmXRahWk84Z/QwAuNh1G4Avg7ct3ST/lEAfd1gVg7q4FZY3cxjolJ+wBzfgJB532LBWjVYzI7mSNZICLCyx6cvaWU1DYvnH4XwgvfYTlSKTmFw/K8z7CYgFWvDUgtdgm+IySyhtlTjRiPVhJD5JXXOKKUVRS2Kjr3PVROU2U827CVqUJRVuAh5rRiAVYtCFAeQH39KWoZKAMihq7ma5TiafKW0W4cDuH0oPvFH1UBo0b4Hw+j0Si/03BZkKQmKsFuD/hmjFjBhYuXNgSnxXW8byGeTBVq3LjjcjAuc8KW3BEAkeVKCIGouZDeSGneycLg9z5quYtlEF9CM0CPPA6aUsBzO2ccGMwZaH1oihZy6/aqyU+D8xOZgSMQjGPSMfUVqwuE5JvF1ICLcD9nyuyAA/91gVl4Sqv53qW9WAqyApcmAsMVWpRtK2bUITBpOiPS9HcSs2EWqP0HdgY6GwXipQSRzuNWqiO8lRGxTUoLqii5g3FGDLZlDPUGntkgHUHpGI15VHclqeJHNUz3NJIBqaKCSjDZIPE/GyVEAOqTKKsmZtkmQAjYgcTORZgIs6qQLYAl7XDZQJZcsgNdbop06AyiWr+69KobDKqhoFOdhx3okSxAzcHMZFDMWXtdYr+q3ON+n6wikYtwAOwxJ3gWYB1XUJyPwWMSZ4QB/MYARyTTqpiqEycG0+42SYnFtauUUe2LkVT66NCou54okzfqFXJNQQLsPxLtbXGyE1WdRlDmmRxQYwroVBlkeExTHZ0uBkqd80mtE0Bw52jtiFQ7yZxFy2iBxEwMnkWYL1NC5EeheHGAqz/v4m2hAdTCYJuslJPJuAqnG31Bsd9qUYHd2yT+0Rhy6gOVoFEZaMW4KEaiJJFU3kJG2BuJkwBTHmZiQFUnhku2UpPifgfR+uun1pzNaehOlkWYPWZLFGtqguWyf0W4Jg8it0ximk8LtgjAmBZmRRHSdSIPje37x6FykUGRYGurKHrSdEWYPU5NKpJRIXH2hxEqw6mOi8s4RFpi9u1oSxclIypvEzHMFWyRc0fCjQ29XJPcdbTg6nFUMBYgMsa4lYo1oNDL5xbD9Z8+55btFNea9LJoWTqMgnVxTPxKF0qD3sudz5CeqcomlKeqA6kJsSVGZdsC3C/JnUVIQMqrkRBZQiUEVGMEtdauQkc1avmhgyK4YYkv43YTeLufVqAeVynMp6GbPhbgMvAcbtkFMxsgClB9nrzaaCuZ7KaTx2tN+P/A6SnKqeIy5s+AAAAAElFTkSuQmCC"; 2 | 3 | -------------------------------------------------------------------------------- /src/libs/text11.ts: -------------------------------------------------------------------------------- 1 | import { TomatoI18nABCMAX } from "./gconst"; 2 | 3 | export abstract class TomatoI18nABC11 extends TomatoI18nABCMAX { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/libs/timer.ts: -------------------------------------------------------------------------------- 1 | export function measureTime(func: Func, name = "Function"): number { 2 | const start = performance.now(); 3 | func(); 4 | const end = performance.now(); 5 | const elapsedTime = end - start 6 | console.info(`${name} took ${elapsedTime} milliseconds to run.`); 7 | return elapsedTime; 8 | } 9 | 10 | // console.time('myFunction'); 11 | // myFunction(); 12 | // console.timeEnd('myFunction'); 13 | 14 | /** 15 | const timer = new Timer(); 16 | myFunction(); 17 | const elapsedTime = timer.stop(); 18 | */ 19 | export class Timer { 20 | private startTime: number; 21 | private name: string; 22 | 23 | constructor(name = "Function") { 24 | this.startTime = performance.now(); 25 | this.name = name; 26 | } 27 | 28 | public stop(): number { 29 | const endTime = performance.now(); 30 | const elapsedTime = endTime - this.startTime 31 | console.info(`${this.name} took ${elapsedTime} milliseconds to run.`); 32 | return elapsedTime; 33 | } 34 | } -------------------------------------------------------------------------------- /src/libs/tools.ts: -------------------------------------------------------------------------------- 1 | import { siyuan } from "./utils"; 2 | 3 | export function calcAge(age: string, blockID: string) { 4 | const { years, months, days } = calculateAge(age); 5 | siyuan.insertBlockAfter(`${years}岁${months}月${days}天`, blockID) 6 | } 7 | 8 | function calculateAge(birthday: string): { years: number, months: number, days: number } { 9 | // Parse the birthday string into Date object 10 | const birthDate = new Date(`${birthday.substring(0, 4)}-${birthday.substring(4, 6)}-${birthday.substring(6, 8)}`); 11 | 12 | // Check if the birthDate is valid 13 | if (isNaN(birthDate.getTime())) { 14 | throw new Error('Invalid birthday format'); 15 | } 16 | 17 | // Get today's date 18 | const today = new Date(); 19 | 20 | // Calculate the difference in years, months, and days 21 | let years = today.getFullYear() - birthDate.getFullYear(); 22 | let months = today.getMonth() - birthDate.getMonth(); 23 | let days = today.getDate() - birthDate.getDate(); 24 | 25 | // Adjust the values if necessary 26 | if (days < 0) { 27 | // Borrow a month 28 | months -= 1; 29 | // Get the last day of the previous month 30 | const lastDayOfPrevMonth = new Date(today.getFullYear(), today.getMonth(), 0).getDate(); 31 | days += lastDayOfPrevMonth; 32 | } 33 | 34 | if (months < 0) { 35 | // Borrow a year 36 | years -= 1; 37 | months += 12; 38 | } 39 | 40 | return { years, months, days }; 41 | } 42 | 43 | export function getDirectTextContent(element: Element) { 44 | const text = new Str(); 45 | for (let child of element.childNodes) { 46 | if (child.nodeType === Node.TEXT_NODE) { 47 | text.add(child.textContent) 48 | } 49 | } 50 | return text.join() 51 | } 52 | 53 | export class Str { 54 | private data: T[]; 55 | constructor(data: T[] = []) { 56 | this.data = data; 57 | } 58 | add(...items: T[]): void { 59 | this.data.push(...items); 60 | } 61 | join(separator: string = ''): string { 62 | return this.data.join(separator); 63 | } 64 | static safeJoin(arr: T[] | null | undefined, separator: string = ''): string { 65 | if (arr == null) return ''; 66 | return Array.isArray(arr) ? arr.join(separator) : ''; 67 | } 68 | } -------------------------------------------------------------------------------- /src/libs/types.ts: -------------------------------------------------------------------------------- 1 | export function isSortType(value: any): value is SortType { 2 | return Object.values(SortType).includes(value); 3 | } 4 | 5 | export enum SortType { 6 | NameASC = "0", // 0:文件名字母升序 7 | NameDESC = "1", // 1:文件名字母降序 8 | UpdatedASC = "2", // 2:文件更新时间升序 9 | UpdatedDESC = "3", // 3:文件更新时间降序 10 | AlphanumASC = "4", // 4:文件名自然数升序 11 | AlphanumDESC = "5", // 5:文件名自然数降序 12 | Custom = "6", // 6:自定义排序 13 | RefCountASC = "7", // 7:引用数升序 14 | RefCountDESC = "8", // 8:引用数降序 15 | CreatedASC = "9", // 9:文件创建时间升序 16 | CreatedDESC = "10", // 10:文件创建时间降序 17 | SizeASC = "11", // 11:文件大小升序 18 | SizeDESC = "12", // 12:文件大小降序 19 | SubDocCountASC = "13", // 13:子文档数升序 20 | SubDocCountDESC = "14", // 14:子文档数降序 21 | FileTree = "15", // 15:使用文档树排序规则 22 | Unassigned = "256", // 256:未指定排序规则,按照笔记本优先于文档树获取排序规则 23 | } 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/libs/ui.ts: -------------------------------------------------------------------------------- 1 | import { Plugin } from "siyuan" 2 | import { getChildElements } from "./utils"; 3 | import { getDirectTextContent } from "./tools"; 4 | 5 | export function removeStatusBar(removeTabBar = true) { 6 | const status = document.getElementById("status"); 7 | status.style.display = "none"; 8 | 9 | document.querySelectorAll('span[data-type="new"]').forEach((e: HTMLElement) => { 10 | if (e.style) e.style.display = "none"; 11 | }); 12 | document.querySelectorAll('span[data-type="more"]').forEach((e: HTMLElement) => { 13 | if (e.style) e.style.display = "none"; 14 | }); 15 | document.querySelectorAll("ul.layout-tab-bar.layout-tab-bar--readonly.fn__flex-1").forEach((e: HTMLElement) => { 16 | if (e.style) { 17 | e.style.paddingRight = "70px"; 18 | // e.parentElement.style.height = "30px" 19 | } 20 | }); 21 | document.getElementById("minWindow").style.display = "none"; 22 | document.getElementById("maxWindow").style.display = "none"; 23 | // document.getElementById("closeWindow").style.display = "none"; 24 | if (removeTabBar) { 25 | document.querySelectorAll('.fn__flex.layout-tab-bar').forEach((e: HTMLElement) => { 26 | if (e.style) e.style.display = "none"; 27 | }); 28 | } 29 | } 30 | 31 | export function isPinned() { 32 | const div = document.getElementById("pinWindow"); 33 | if (!div) return false; 34 | // iconUnpin for pinned status 35 | return div.querySelector('use')?.getAttribute("xlink:href") === "#iconUnpin"; 36 | } 37 | 38 | export function addIcon(plugin: Plugin, minute: number | string) { 39 | const id = "iconTomato" + minute; 40 | plugin.addIcons(` 41 | ${minute} 42 | `); 43 | return id; 44 | } 45 | 46 | export function createNumIcon(num: number) { 47 | if (num >= 0 && num <= 9) { 48 | return ` 49 | ${num} 50 | `; 51 | } 52 | if (num >= 10) { 53 | return ` 54 | 9+ 55 | `; 56 | } 57 | if (num < 0 && num >= -9) { 58 | return ` 59 | ${num} 60 | `; 61 | } 62 | return ` 63 | -n 64 | `; 65 | } 66 | 67 | export function searchSettings(settingsDiv: HTMLElement, searchKey: string) { 68 | const sk = searchKey.toLocaleLowerCase(); 69 | getChildElements(settingsDiv).forEach((e) => { 70 | e.style.display = ""; 71 | }); 72 | settingsDiv.querySelectorAll(".tomato-highlight").forEach(e => { 73 | e.classList.remove("tomato-highlight") 74 | }) 75 | if (sk) { 76 | getChildElements(settingsDiv).forEach((e) => { 77 | if (e.hasAttribute("data-search")) return; 78 | if (e.hasAttribute("data-hide")) { 79 | e.style.display = "none"; 80 | return; 81 | } 82 | if (!e.textContent.toLocaleLowerCase().includes(sk)) { 83 | e.style.display = "none"; 84 | return; 85 | } 86 | }); 87 | settingsDiv.querySelectorAll("div,strong").forEach(e => { 88 | if (getDirectTextContent(e).toLocaleLowerCase().includes(sk)) { 89 | e.classList.add("tomato-highlight") 90 | } 91 | }) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/libs/user.ts: -------------------------------------------------------------------------------- 1 | import { ec as EC } from 'elliptic'; 2 | import { getMd5, siyuan, Siyuan, timeUtil } from './utils'; 3 | import { userID, userToken, writableWithGet } from './stores'; 4 | 5 | const MY_PUBKEY = "044ad3bfb46f3b89979dd551a5dada23f8502f8a0c54d247e1f8d31e5d7705a978df1ef30ba5a4b5206f0b0f573c8f76feada715f949430187f62f5640ca144aa7"; 6 | const ec = new EC('secp256k1'); 7 | const keyPair = ec.keyFromPublic(MY_PUBKEY, 'hex') 8 | let _isValid: boolean = null; 9 | 10 | export function lastVerifyResult(): boolean { 11 | return _isValid; 12 | } 13 | 14 | export const expStore = writableWithGet("") 15 | 16 | export function getUserCodeExp(_exp: string) { 17 | if (!_exp) return "" 18 | return `${_exp.slice(0, 4)} / ${_exp.slice(4, 6)} / ${_exp.slice(6)}` 19 | } 20 | 21 | export function isMe() { 22 | return Siyuan?.user?.userId === "1656951563417"; 23 | } 24 | 25 | export function resetKey() { 26 | _isValid = null; 27 | } 28 | 29 | export async function verifyKeyTomato() { 30 | return verifyKey("_siyuanTomatoCode_") 31 | } 32 | 33 | export async function verifyKeyProgressive() { 34 | return verifyKey("_siyuanProgressiveCode_") 35 | } 36 | 37 | async function verifyKey(included: string) { 38 | if (_isValid != null) return _isValid; 39 | 40 | let v = await verifyUserSign(userToken.get(), included); 41 | if (!v.valid) { 42 | const FREE_KEY = "freeze7XSGUQr_20250531_name_siyuanTomatoCode_30450221009c482f5a144f605dca52c5c9991501eb98d281755435d6316e666820c718f7ad02207e175822e94fc88fd397bd14f497d123815b91c7d43935a683e26d6bc99c1f76"; 43 | userToken.set(FREE_KEY); 44 | v = await verifyUserSign(FREE_KEY, "_siyuanTomatoCode_"); 45 | } 46 | _isValid = v.valid; 47 | return _isValid; 48 | } 49 | 50 | async function verifyUserSign(tokenSign: string, included: string) { 51 | let signValid = false; 52 | let userPart = ""; 53 | let userPartShort = ""; 54 | let exp = ""; 55 | let ldID = ""; 56 | let name = ""; 57 | { 58 | // 1656951563417_22240101_ldID_siyuanTomatoCode_30qqqqqqqqqqqqqq.. 59 | const parts = tokenSign?.split(included); 60 | if (parts?.length === 2) { 61 | userPartShort = parts[0]; 62 | userPart = userPartShort + included; 63 | const sign = parts[1]; 64 | try { 65 | const msgHash = getMd5(userPart) 66 | signValid = keyPair.verify(msgHash, sign); 67 | } catch { 68 | signValid = false; 69 | } 70 | } 71 | } 72 | { 73 | // freecbly0fNG4_20241206_name 74 | // 1656951563417_22240101_ldID 75 | const ps = userPartShort.split("_") 76 | if (ps.length === 3) { 77 | exp = ps[1]; 78 | if (ps[2] === "ldID") { 79 | ldID = ps[0]; 80 | } else if (ps[2] === "name") { 81 | name = ps[0]; 82 | } 83 | } 84 | } 85 | if (signValid) { 86 | signValid = await checkUserID(ldID, name, exp); 87 | } 88 | 89 | if ([ 90 | "", 91 | // "402f82671ae66425e07eaa0a47c5128723383ff0", 92 | // "9d07cd325209e7739fe04da41b2b7887e097b268", 93 | // "5f6df12238cfea797f8c73309dfcf3445fd99d28", 94 | // "1e4b90d2e70cc02b29ad5232094d6ca6a4853366", 95 | ].includes(getMd5(userPartShort))) signValid = false; 96 | 97 | if (included && !tokenSign.includes(included)) signValid = false; 98 | return { exp, valid: signValid }; 99 | } 100 | 101 | async function checkUserID(ldID: string, name: string, exp: string) { 102 | const ms = await siyuan.currentTimeMs(); 103 | const { y, M, d } = timeUtil.nowYMDStrPad(new Date(ms)); 104 | const nowStr = y + M + d; 105 | expStore.set(getUserCodeExp(exp)); 106 | if (nowStr <= exp) { 107 | if (ldID) { 108 | return ldID === userID.get(); 109 | } else if (name) { 110 | return true; 111 | } 112 | } 113 | return false; 114 | } 115 | -------------------------------------------------------------------------------- /src/libs/winHotkey.ts: -------------------------------------------------------------------------------- 1 | import { lastVerifyResult } from "./user"; 2 | import { Siyuan } from "./utils"; 3 | 4 | 5 | 6 | const officalHotkeys = new Map(); 7 | 8 | getAllHotkeys 9 | function getAllHotkeys(obj: any) { 10 | const visited = new Set(); 11 | const queue = [{ k: "keymap", v: obj }]; 12 | while (queue.length) { 13 | const { k, v } = queue.shift(); 14 | if (!v || typeof v !== 'object' || visited.has(v)) continue; 15 | visited.add(v); 16 | if (v.custom) { 17 | officalHotkeys.set(v.custom, k); 18 | } else { 19 | for (const [k, va] of Object.entries(v)) { 20 | if (k == "plugin") continue 21 | queue.push({ k, v: va }); 22 | } 23 | } 24 | } 25 | } 26 | 27 | function toWin(k: string, mac = true, win = true) { 28 | const w = k 29 | .replaceAll("⌘", "Ctrl+") 30 | .replaceAll("⇧", "Shift+") 31 | .replaceAll("⌥", "Alt+") 32 | .replaceAll("⇥", "Tab") 33 | .replaceAll("⌫", "Backspace") 34 | .replaceAll("⌦", "Delete") 35 | .replaceAll("↩", "Enter"); 36 | if (mac && win) { 37 | return `(${k})(${w})` 38 | } 39 | if (mac) { 40 | return `(${k})` 41 | } 42 | return `(${w})` 43 | } 44 | 45 | interface Get { 46 | get(p?: any): boolean, 47 | } 48 | 49 | export function winHotkey(m: string, langKey: string, icon?: string, langText?: () => string, vip?: boolean, store?: Get) { 50 | if (!m) throw Error("null hotkey") 51 | if (!langKey) throw Error("null langKey") 52 | m = m.toLocaleUpperCase() 53 | .replaceAll("CTRL+", "⌘") 54 | .replaceAll("SHIFT+", "⇧") 55 | .replaceAll("ALT+", "⌥") 56 | .replaceAll("TAB", "⇥") 57 | .replaceAll("BACKSPACE", "⌫") 58 | .replaceAll("DELETE", "⌦") 59 | .replaceAll("ENTER", "↩") 60 | .replaceAll("LEFT", "←") 61 | .replaceAll("RIGHT", "→") 62 | .replaceAll("UP", "↑") 63 | .replaceAll("DOWN", "↓") 64 | 65 | const alt = m.includes("⌥") 66 | const shift = m.includes("⇧") 67 | const ctrl = m.includes("⌘") 68 | m = m.replaceAll("⌥", "").replaceAll("⇧", "").replaceAll("⌘", "") 69 | if (ctrl) m = "⌘" + m 70 | if (shift) m = "⇧" + m 71 | if (alt) m = "⌥" + m 72 | 73 | // if (officalHotkeys.size == 0) getAllHotkeys(Siyuan?.config?.keymap); 74 | // if (!globalThis.wieyqstvaPUaBkyoBGpsBztqoIZPplSyMWEETBcF) globalThis.wieyqstvaPUaBkyoBGpsBztqoIZPplSyMWEETBcF = new Map(); 75 | // const hotkeySet: Map = globalThis.wieyqstvaPUaBkyoBGpsBztqoIZPplSyMWEETBcF 76 | // if (hotkeySet.has(m)) console.warn("发现重复的hotkey:", m, langKey, "--------", hotkeySet.get(m)) 77 | // if (hotkeySet.has(langKey)) console.warn("发现重复的langKey:", m, langKey, "--------", hotkeySet.get(langKey)) 78 | // if (officalHotkeys.has(m)) console.warn("发现与官方重复的langKey:", m, langKey, "--------", officalHotkeys.get(m)) 79 | // hotkeySet.set(m, langKey); 80 | // hotkeySet.set(langKey, m); 81 | 82 | const w = (mac = true, win = true) => { 83 | const a = Siyuan?.config?.keymap?.plugin?.['sy-tomato-plugin']?.[langKey]?.custom 84 | if (a) return toWin(a, mac, win); 85 | 86 | const b = Siyuan?.config?.keymap?.plugin?.['sy-progressive-plugin']?.[langKey]?.custom 87 | if (b) return toWin(b, mac, win); 88 | 89 | const c = Siyuan?.config?.keymap?.plugin?.['sy-my-plugin']?.[langKey]?.custom 90 | if (c) return toWin(c, mac, win); 91 | 92 | const a1 = Siyuan?.config?.keymap?.plugin?.['sy-tomato-plugin']?.[langKey]?.default 93 | const b1 = Siyuan?.config?.keymap?.plugin?.['sy-progressive-plugin']?.[langKey]?.default 94 | const c1 = Siyuan?.config?.keymap?.plugin?.['sy-my-plugin']?.[langKey]?.default 95 | const invalid = !!a1 || !!b1 || !!c1; 96 | 97 | return toWin(m, mac, win) + (invalid ? "🚫" : ""); 98 | } 99 | 100 | const menu = () => { 101 | let ac = true; 102 | if (store) { 103 | ac = store.get() 104 | } 105 | if (vip && !lastVerifyResult()) { 106 | ac = false; 107 | } 108 | return ac; 109 | } 110 | const cmd = () => { 111 | let ac = true; 112 | if (vip && !lastVerifyResult()) { 113 | ac = false; 114 | } 115 | return ac; 116 | } 117 | 118 | return { m, w, langKey, icon, langText, menu, cmd }; 119 | } 120 | 121 | -------------------------------------------------------------------------------- /src/types/baidu.d.ts: -------------------------------------------------------------------------------- 1 | // https://app.quicktype.io/?l=ts 2 | 3 | interface AIResponse { 4 | id: string; 5 | object: string; 6 | created: number; 7 | result: string; 8 | sentence_id: number; 9 | is_end: boolean; 10 | is_truncated: boolean; 11 | need_clear_history: boolean; 12 | finish_reason: string; 13 | usage: Usage; 14 | } 15 | 16 | interface Usage { 17 | prompt_tokens: number; 18 | completion_tokens: number; 19 | total_tokens: number; 20 | } 21 | 22 | interface CalcTokensResponse { 23 | id: string; 24 | object: string; 25 | created: number; 26 | usage: CalcTokenUsage; 27 | } 28 | 29 | interface CalcTokenUsage { 30 | prompt_tokens: number; 31 | total_tokens: number; 32 | } 33 | 34 | type ChatRole = "user" | "assistant" 35 | type Chat = { role: ChatRole, content: string, tokens: number } 36 | 37 | interface AppConversationRunsResp { 38 | request_id: string; 39 | date: Date; 40 | answer: string; 41 | conversation_id: string; 42 | message_id: string; 43 | is_completion: null; 44 | content: AppConversationRunsContent[]; 45 | } 46 | 47 | interface AppConversationRunsContent { 48 | result_type: string; 49 | event_code: number; 50 | event_message: string; 51 | event_type: string; 52 | event_id: string; 53 | event_status: string; 54 | content_type: string; 55 | visible_scope: string; 56 | outputs: AppConversationRunsContentOutputs; 57 | usage?: AppConversationRunsContentUsage; 58 | } 59 | 60 | interface AppConversationRunsContentOutputs { 61 | text?: AppConversationRunsContentTextClass | string; 62 | name_cn?: string; 63 | references?: AppConversationRunsContentReference[]; 64 | } 65 | 66 | interface AppConversationRunsContentReference { 67 | id: string; 68 | content: string; 69 | type: string; 70 | from: string; 71 | title: string; 72 | segment_id: string; 73 | document_id: string; 74 | document_name: string; 75 | dataset_name: string; 76 | dataset_id: string; 77 | } 78 | 79 | interface AppConversationRunsContentTextClass { 80 | arguments: AppConversationRunsContentArguments; 81 | component_code: string; 82 | component_name: string; 83 | } 84 | 85 | interface AppConversationRunsContentArguments { 86 | origin_query: string; 87 | } 88 | 89 | interface AppConversationRunsContentUsage { 90 | prompt_tokens: number; 91 | completion_tokens: number; 92 | total_tokens: number; 93 | name: string; 94 | type: string; 95 | } 96 | 97 | interface DescribeDocumentsResp { 98 | requestId: string; 99 | marker: string; 100 | isTruncated: boolean; 101 | nextMarker: string; 102 | maxKeys: number; 103 | data: DescribeDocumentsDocument[]; 104 | } 105 | 106 | interface DescribeDocumentsDocument { 107 | id: string; 108 | name: string; 109 | sy_doc_id: string; 110 | sy_exists: boolean; 111 | createdAt: number; 112 | wordCount: number; 113 | enabled: boolean; 114 | meta: DescribeDocumentsMeta; 115 | } 116 | 117 | interface DescribeDocumentsMeta { 118 | source: string; 119 | fileId: string; 120 | } -------------------------------------------------------------------------------- /svelte.config.js: -------------------------------------------------------------------------------- 1 | import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; 2 | 3 | const NoWarns = new Set([ 4 | "a11y-click-events-have-key-events", 5 | "a11y-no-static-element-interactions", 6 | "a11y-no-noninteractive-element-interactions" 7 | ]); 8 | 9 | export default { 10 | // Consult https://svelte.dev/docs#compile-time-svelte-preprocess 11 | // for more information about preprocessors 12 | preprocess: vitePreprocess(), 13 | onwarn: (warning, handler) => { 14 | // suppress warnings on `vite dev` and `vite build`; but even without this, things still work 15 | if (NoWarns.has(warning.code)) return; 16 | handler(warning); 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": [ 7 | "ES2022", 8 | "DOM", 9 | "DOM.Iterable" 10 | ], 11 | "skipLibCheck": true, 12 | /* Bundler mode */ 13 | "moduleResolution": "Node", 14 | // "allowImportingTsExtensions": true, 15 | "allowSyntheticDefaultImports": true, 16 | "resolveJsonModule": true, 17 | "isolatedModules": true, 18 | "noEmit": true, 19 | "jsx": "preserve", 20 | /* Linting */ 21 | "strict": false, 22 | "noUnusedLocals": true, 23 | "noUnusedParameters": true, 24 | "noFallthroughCasesInSwitch": true, 25 | /* Svelte */ 26 | /** 27 | * Typecheck JS in `.svelte` and `.js` files by default. 28 | * Disable checkJs if you'd like to use dynamic types in JS. 29 | * Note that setting allowJs false does not prevent the use 30 | * of JS in `.svelte` files. 31 | */ 32 | "allowJs": true, 33 | "checkJs": true, 34 | "types": [ 35 | "node", 36 | // "vite/client", 37 | // "svelte" 38 | ], 39 | // "baseUrl": "./src", 40 | // "paths": { 41 | // "@/*": ["./src/*"], 42 | // "@/libs/*": ["./src/libs/*"], 43 | // }, 44 | "typeRoots": [ 45 | "./src/types", 46 | "./node_modules/@types" 47 | ], 48 | }, 49 | "include": [ 50 | "src/**/*.ts", 51 | "src/**/*.d.ts", 52 | "src/**/*.svelte", 53 | ], 54 | "exclude": [ 55 | "node_modules" 56 | ], 57 | "references": [ 58 | { 59 | "path": "./tsconfig.node.json" 60 | } 61 | ], 62 | "root": "." 63 | } -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "Node", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": [ 10 | "vite.config.ts" 11 | ] 12 | } -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from "path"; 2 | import { defineConfig } from "vite"; 3 | import minimist from "minimist"; 4 | import { viteStaticCopy } from "vite-plugin-static-copy"; 5 | import livereload from "rollup-plugin-livereload"; 6 | import { svelte } from "@sveltejs/vite-plugin-svelte"; 7 | import zipPack from "vite-plugin-zip-pack"; 8 | import fg from "fast-glob"; 9 | 10 | const args = minimist(process.argv.slice(2)); 11 | const isWatch = args.watch || args.w || false; 12 | // const devDistDir = "./dev"; 13 | // const distDir = isWatch ? devDistDir : "./dist"; 14 | // console.info("isWatch=>", isWatch); 15 | // console.info("distDir=>", distDir); 16 | const devDistDir = process.env.SYPLUGINDIR ? process.env.SYPLUGINDIR + "/sy-tomato-plugin" : "build"; 17 | const distDir = devDistDir; 18 | 19 | export default defineConfig({ 20 | resolve: { 21 | alias: { 22 | "@": resolve(__dirname, "src"), 23 | }, 24 | }, 25 | 26 | plugins: [ 27 | svelte(), 28 | viteStaticCopy({ 29 | targets: [ 30 | { 31 | src: "./README*.md", 32 | dest: "./", 33 | }, 34 | { 35 | src: "./icon.png", 36 | dest: "./", 37 | }, 38 | { 39 | src: "./preview.png", 40 | dest: "./", 41 | }, 42 | { 43 | src: "./plugin.json", 44 | dest: "./", 45 | }, 46 | { 47 | src: "./src/i18n/**", 48 | dest: "./i18n/", 49 | }, 50 | ], 51 | }), 52 | ], 53 | 54 | // https://github.com/vitejs/vite/issues/1930 55 | // https://vitejs.dev/guide/env-and-mode.html#env-files 56 | // https://github.com/vitejs/vite/discussions/3058#discussioncomment-2115319 57 | // 在这里自定义变量 58 | define: { 59 | "process.env.DEV_MODE": `"${isWatch}"`, 60 | "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV), 61 | }, 62 | 63 | build: { 64 | // 输出路径 65 | outDir: distDir, 66 | emptyOutDir: false, 67 | 68 | // 构建后是否生成 source map 文件 69 | sourcemap: false, 70 | 71 | // 设置为 false 可以禁用最小化混淆 72 | // 或是用来指定是应用哪种混淆器 73 | // boolean | 'terser' | 'esbuild' 74 | minify: isWatch ? false : "esbuild", 75 | 76 | lib: { 77 | // Could also be a dictionary or array of multiple entry points 78 | entry: resolve(__dirname, "src/index.ts"), 79 | // the proper extensions will be added 80 | fileName: "index", 81 | formats: ["cjs"], 82 | }, 83 | 84 | terserOptions: { 85 | compress: true, // 启用压缩 86 | mangle: true, // 启用变量名混淆 87 | output: { 88 | beautify: false, // 禁用美化输出 89 | comments: false // 移除注释 90 | } 91 | }, 92 | 93 | rollupOptions: { 94 | plugins: [ 95 | ...(isWatch 96 | ? [ 97 | livereload(devDistDir), 98 | { 99 | //监听静态资源文件 100 | name: "watch-external", 101 | async buildStart() { 102 | const files = await fg([ 103 | "src/i18n/*.json", 104 | // "./README*.md", 105 | "./plugin.json", 106 | ]); 107 | for (const file of files) { 108 | this.addWatchFile(file); 109 | } 110 | }, 111 | }, 112 | ] 113 | : [ 114 | // zipPack({ 115 | // inDir: "./dist", 116 | // outDir: "./", 117 | // outFileName: "package.zip", 118 | // }), 119 | ]), 120 | ], 121 | 122 | // make sure to externalize deps that shouldn't be bundled 123 | // into your library 124 | external: ["siyuan", "process"], 125 | 126 | output: { 127 | entryFileNames: "[name].js", 128 | assetFileNames: (assetInfo) => { 129 | if (assetInfo.name === "style.css") { 130 | return "index.css"; 131 | } 132 | return assetInfo.name; 133 | }, 134 | }, 135 | }, 136 | }, 137 | }); 138 | --------------------------------------------------------------------------------