├── .gitignore ├── icon.png ├── preview.png ├── CHANGELOG.md ├── .release.py ├── plugin.json ├── i18n ├── en_US.json └── zh_CN.json ├── README_zh_CN.md ├── .github └── workflows │ └── publish.yml ├── README.md ├── LICENSE └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | listChildDocs-dev/ 3 | listChildDocs/ 4 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpaqueGlass/syplugin-fileLostAlarmer/main/icon.png -------------------------------------------------------------------------------- /preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpaqueGlass/syplugin-fileLostAlarmer/main/preview.png -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 更新日志 2 | 3 | ### v0.1.0 4 | 5 | 首次发布。 6 | 7 | ### v0.0.0 (2024年1月20日) 8 | 9 | 测试ing。 -------------------------------------------------------------------------------- /.release.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | with open('CHANGELOG.md', 'r', encoding='utf-8') as f: 4 | readme_str = f.read() 5 | 6 | match_obj = re.search(r'(?<=### )[\s\S]*?(?=#)', readme_str, re.DOTALL) 7 | if match_obj: 8 | h3_title = match_obj.group(0) 9 | with open('result.txt', 'w') as f: 10 | f.write(h3_title) 11 | else: 12 | with open('result.txt', 'w') as f: 13 | f.write("") 14 | -------------------------------------------------------------------------------- /plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "syplugin-fileLostAlarmer", 3 | "author": "OpaqueGlass", 4 | "url": "https://github.com/OpaqueGlass/syplugin-fileLostAlarmer", 5 | "version": "0.1.0", 6 | "displayName": { 7 | "default": "fileLostAlarmer", 8 | "zh_CN": "同步删除文件检查提示", 9 | "en_US": "fileLostAlarmer" 10 | }, 11 | "description": { 12 | "default": "fileLostAlarmer", 13 | "zh_CN": "在内置同步执行完成后,检查快照情况,在发生删除文件时提醒用户", 14 | "en_US": "fileLostAlarmer" 15 | }, 16 | "readme": { 17 | "default": "README.md", 18 | "zh_CN": "README_zh_CN.md", 19 | "en_US": "README.md" 20 | }, 21 | "i18n": [ 22 | "zh_CN", 23 | "en_US" 24 | ], 25 | "funding": { 26 | "custom": [ 27 | "https://wj.qq.com/s2/12395364/b69f/" 28 | ] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /i18n/en_US.json: -------------------------------------------------------------------------------- 1 | { 2 | "button_save": "Save", 3 | "button_cancel": "Cancel", 4 | "setting_panel_title": "Plugin Settings: ", 5 | 6 | 7 | "setting_dblclickShowSubDoc_name": "启用", 8 | "setting_dblclickShowSubDoc_desp": "启用双击展开子层级功能;启用后将插件将代为处理文档树单击、双击事件,如出现文档树打不开文档、不显示菜单等问题,请停用插件并向开发者反馈", 9 | "setting_dblclickDelay_name": "双击判定时段", 10 | "setting_dblclickDelay_desp": "单位:毫秒;注意,小于150毫秒几乎无法判定为双击", 11 | "setting_disableChangeIcon_name": "禁用文档树切换图标", 12 | "setting_disableChangeIcon_desp": "需要先启用双击功能", 13 | "setting_unfoldSubDocsWhileOpenParent_name": "打开父文档时展开子文档", 14 | "setting_unfoldSubDocsWhileOpenParent_desp": "需要先启用双击功能", 15 | 16 | "file_lost_warning": "[syplugin-fileLostAlarmer]File lost detected!", 17 | "assets_lost_warning_desp": "%% resource file missing detected, please confirm manually", 18 | 19 | 20 | "setting_experimental": "(Experimental Feature. UNSTABLE. May be removed at any time) ", 21 | "error_initFailed": "Double-click-file-tree (File tree enhance) Plugin initialization failed, please report this issue to developer at your convenience" 22 | } -------------------------------------------------------------------------------- /README_zh_CN.md: -------------------------------------------------------------------------------- 1 | ## fileLostAlarmer 同步删除文件检查提示 2 | 3 | [English](README.md) 4 | 5 | > 当前版本:v0.0.1 测试中 6 | 7 | 在同步后,调用API检查最近的快照是否存在资源文件缺失,如果有,则提醒用户; 8 | 9 | > [!WARNING] 10 | > 前排提醒:插件通过思源快照对比api进行检查,仅为自动化执行对比和删除文件的提示,并没有另外实现文件快照、文件状态记录; 11 | > 12 | > 如果没有使用内置的同步,则无法检查同步情况;如果快照信息异常或滞后,也无法发现同步删除情况;插件也无法发现旧文件覆盖新文件的情况;对比结果仅供参考; 13 | 14 | ### 快速开始 15 | 16 | - 开启插件即可; 17 | - 其他请浏览插件设置页面(`设置`→`集市`→`已下载`→`插件`→`同步删除文件检查提示`→“设置图标”),**提示:设置页可以上下滑动哦**; 18 | - 默认禁用文档树修改图标,可到插件设置更改; 19 | 20 | #### 说明 21 | 22 | 23 | 24 | ## 反馈bug 25 | 26 | (推荐)请前往[github仓库](https://github.com/OpaqueGlass/syplugin-fileLostAlarmer)反馈问题。 27 | 28 | 如果您无法访问github,请[在此反馈](https://wj.qq.com/s2/12395364/b69f/)。 29 | 30 | ## 参考&感谢 31 | 32 | 代码贡献者(开发者)详见[贡献者列表](https://github.com/OpaqueGlass/syplugin-my-plugin-collection/graphs/contributors)。 33 | 34 | | 开发者/项目 | 描述 | 说明 | 35 | | ------------------------------------------------------------ | ------------------------------------------------------------ | ---------------------------- | 36 | | N/A | N/A | N/A | 37 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Create Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v2 17 | 18 | - name: Generate Package.zip 19 | run: find . -not -path '*/\.*' -print | xargs zip package.zip 20 | 21 | 22 | - name: Set up Python 23 | uses: actions/setup-python@v2 24 | with: 25 | python-version: '3.8' 26 | 27 | - name: Get CHANGELOGS 28 | run: python .release.py 29 | 30 | - name: Pre Release 31 | uses: softprops/action-gh-release@v1 32 | if: contains(github.ref, 'beta') || contains(github.ref, 'alpha') 33 | with: 34 | body_path: ./result.txt 35 | files: package.zip 36 | prerelease: true 37 | token: ${{ secrets.GITHUB_TOKEN }} 38 | 39 | - name: Release 40 | uses: softprops/action-gh-release@v1 41 | if: ${{ ! contains(github.ref, 'beta') && ! contains(github.ref, 'alpha') }} 42 | with: 43 | body_path: ./result.txt 44 | files: package.zip 45 | prerelease: false 46 | token: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## fileLostAlarmer 2 | 3 | [请点这里阅读中文说明](README_zh_CN.md) 4 | 5 | > Most of this document was translated by Google Translate. 6 | 7 | After synchronization, call API to check whether there is missing resource file in the latest snapshot, and if there is, remind the user; 8 | 9 | > [!WARNING] 10 | > We not support English yet. 11 | > 12 | > The plugin checks through the Siyuan snapshot comparison API, only for automated execution of comparisons and prompting for file deletions, without separately implementing file snapshots or file status recording. 13 | > 14 | > If the Siyuan built-in synchronization is not used, synchronization conditions cannot be checked. If snapshot information is abnormal or delayed, synchronization deletion situations cannot be discovered either. 15 | > 16 | > The plugin is also unable to detect cases where old files overwrite new files. The results of the comparison are for reference only. 17 | 18 | ### Quick Start 19 | 20 | - Just turn on the plugin; (`Marketplace`--`Downloaded`--`Plugin`--`fileLostAlarmer`, click switch icon) 21 | - For more information, please refer to the plugin setting page; (`Marketplace`--`Downloaded`--`Plugin`--`fileLostAlarmer`-- Click setting icon) 22 | 23 | #### Other explanation 24 | 25 | 26 | 27 | ## Feedback bugs 28 | 29 | Please go to [github repository](https://github.com/OpaqueGlass/syplugin-fileLostAlarmer) to report problems. 30 | 31 | ## References & Appreciations 32 | 33 | | Developer/Project | Description | Illustration | 34 | | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | 35 | | N/A | N/A | N/A | -------------------------------------------------------------------------------- /i18n/zh_CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "button_save": "保存", 3 | "button_cancel": "取消", 4 | "setting_panel_title": "资源文件丢失检查插件设置", 5 | 6 | 7 | "setting_checkMissingAssets_name": "检查资源文件缺失情况", 8 | "setting_checkMissingAssets_desp": "与 设置 资源 丢失的资源文件 使用相同的api", 9 | "setting_checkRepoSnapshotSingleTypeCount_name": "同步后、单项文件删除警告阈值", 10 | "setting_checkRepoSnapshotSingleTypeCount_desp": "检查.sy、.png、.jpg文件单项在近2个快照中是否存在缺失情况,这里设置对应的检测阈值,超过阈值将提示", 11 | "setting_checkLastSnapshotRemoveThreshold_name": "同步后、关键文件删除警告阈值", 12 | "setting_checkLastSnapshotRemoveThreshold_desp": "同步后,比对近1个快照,若有文件删除,则在超过阈值后提示(仅比较.sy文件、assets文件,不检查Confilct冲突文件)", 13 | "setting_checkLastSnapshot3rdFileLostThreshold_name": "同步后、第三方文件删除警告阈值", 14 | "setting_checkLastSnapshot3rdFileLostThreshold_desp": "同步后,比对近1个快照,若有第三方文件删除,则在超过阈值后提示(比较除.sy文件、assets文件以外的插件、挂件或其他类型的数据)", 15 | "setting_alwaysShowSummaryDialog_name": "总是展示总结对话框", 16 | "setting_alwaysShowSummaryDialog_desp": "启用后,无论是否发生文件删除、是否超出阈值,都在每次同步后展示总结对话框", 17 | 18 | "check_file_lost_btn": "立即检查最近快照情况", 19 | "file_lost_warning": "[syplugin-fileLostAlarmer]⚠发现文件丢失!", 20 | "file_compare_result_title": "[syplugin-fileLostAlarmer]%%快照对比结果", 21 | "file_lost_warning_desp": "发现文件移除情况如下:", 22 | "snapshot_sync_desp": "本次(最近)同步:%0%%1%;
上次同步:%2%;", 23 | "merge_sync": "[合并]", 24 | "assets_lost_warning": "资源文件移除%%个", 25 | "critical_lost_warning": "快照关键文件移除%%个", 26 | "3rd_lost_warning": "第三方文件移除%%个", 27 | "doc_id_not_exist_warning": "文档id不存在%%个", 28 | "critical_lost_detail": "关键资源文件移除列表:", 29 | "3rd_lost_detail": "第三方文件移除列表:", 30 | "snapshot_not_enough": "快照数量不足,不能比对快照情况", 31 | 32 | "assets_lost_warning_desp": "‼检测到%%个资源文件丢失,请手动确认", 33 | "snapshot_file_count_warning_desp": "‼近3个快照中,发现有%%丢失,请手动确认", 34 | "snapshot_removed_warning_desp": "‼近2个快照中,发现有%%个文档被移除,请手动确认", 35 | "doc_id_not_exist_warning_desp": "‼检测到%%个文档id不存在,请手动确认", 36 | 37 | "file_count_type": "%1%个%2%", 38 | "file_type_sy": ".sy: %%", 39 | "file_type_png": ".png: %%", 40 | "file_type_jpg": ".jpg: %%", 41 | 42 | "setting_experimental": "(⚠实验性功能,不稳定,可能随时被移除)", 43 | "error_initFailed": "双击文档树(文档树增强)插件初始化失败,如果可以,请向开发者反馈此问题" 44 | } 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU Affero General Public License is a free, copyleft license for 11 | software and other kinds of works, specifically designed to ensure 12 | cooperation with the community in the case of network server software. 13 | 14 | The licenses for most software and other practical works are designed 15 | to take away your freedom to share and change the works. By contrast, 16 | our General Public Licenses are intended to guarantee your freedom to 17 | share and change all versions of a program--to make sure it remains free 18 | software for all its users. 19 | 20 | When we speak of free software, we are referring to freedom, not 21 | price. Our General Public Licenses are designed to make sure that you 22 | have the freedom to distribute copies of free software (and charge for 23 | them if you wish), that you receive source code or can get it if you 24 | want it, that you can change the software or use pieces of it in new 25 | free programs, and that you know you can do these things. 26 | 27 | Developers that use our General Public Licenses protect your rights 28 | with two steps: (1) assert copyright on the software, and (2) offer 29 | you this License which gives you legal permission to copy, distribute 30 | and/or modify the software. 31 | 32 | A secondary benefit of defending all users' freedom is that 33 | improvements made in alternate versions of the program, if they 34 | receive widespread use, become available for other developers to 35 | incorporate. Many developers of free software are heartened and 36 | encouraged by the resulting cooperation. However, in the case of 37 | software used on network servers, this result may fail to come about. 38 | The GNU General Public License permits making a modified version and 39 | letting the public access it on a server without ever releasing its 40 | source code to the public. 41 | 42 | The GNU Affero General Public License is designed specifically to 43 | ensure that, in such cases, the modified source code becomes available 44 | to the community. It requires the operator of a network server to 45 | provide the source code of the modified version running there to the 46 | users of that server. Therefore, public use of a modified version, on 47 | a publicly accessible server, gives the public access to the source 48 | code of the modified version. 49 | 50 | An older license, called the Affero General Public License and 51 | published by Affero, was designed to accomplish similar goals. This is 52 | a different license, not a version of the Affero GPL, but Affero has 53 | released a new version of the Affero GPL which permits relicensing under 54 | this license. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | TERMS AND CONDITIONS 60 | 61 | 0. Definitions. 62 | 63 | "This License" refers to version 3 of the GNU Affero General Public License. 64 | 65 | "Copyright" also means copyright-like laws that apply to other kinds of 66 | works, such as semiconductor masks. 67 | 68 | "The Program" refers to any copyrightable work licensed under this 69 | License. Each licensee is addressed as "you". "Licensees" and 70 | "recipients" may be individuals or organizations. 71 | 72 | To "modify" a work means to copy from or adapt all or part of the work 73 | in a fashion requiring copyright permission, other than the making of an 74 | exact copy. The resulting work is called a "modified version" of the 75 | earlier work or a work "based on" the earlier work. 76 | 77 | A "covered work" means either the unmodified Program or a work based 78 | on the Program. 79 | 80 | To "propagate" a work means to do anything with it that, without 81 | permission, would make you directly or secondarily liable for 82 | infringement under applicable copyright law, except executing it on a 83 | computer or modifying a private copy. Propagation includes copying, 84 | distribution (with or without modification), making available to the 85 | public, and in some countries other activities as well. 86 | 87 | To "convey" a work means any kind of propagation that enables other 88 | parties to make or receive copies. Mere interaction with a user through 89 | a computer network, with no transfer of a copy, is not conveying. 90 | 91 | An interactive user interface displays "Appropriate Legal Notices" 92 | to the extent that it includes a convenient and prominently visible 93 | feature that (1) displays an appropriate copyright notice, and (2) 94 | tells the user that there is no warranty for the work (except to the 95 | extent that warranties are provided), that licensees may convey the 96 | work under this License, and how to view a copy of this License. If 97 | the interface presents a list of user commands or options, such as a 98 | menu, a prominent item in the list meets this criterion. 99 | 100 | 1. Source Code. 101 | 102 | The "source code" for a work means the preferred form of the work 103 | for making modifications to it. "Object code" means any non-source 104 | form of a work. 105 | 106 | A "Standard Interface" means an interface that either is an official 107 | standard defined by a recognized standards body, or, in the case of 108 | interfaces specified for a particular programming language, one that 109 | is widely used among developers working in that language. 110 | 111 | The "System Libraries" of an executable work include anything, other 112 | than the work as a whole, that (a) is included in the normal form of 113 | packaging a Major Component, but which is not part of that Major 114 | Component, and (b) serves only to enable use of the work with that 115 | Major Component, or to implement a Standard Interface for which an 116 | implementation is available to the public in source code form. A 117 | "Major Component", in this context, means a major essential component 118 | (kernel, window system, and so on) of the specific operating system 119 | (if any) on which the executable work runs, or a compiler used to 120 | produce the work, or an object code interpreter used to run it. 121 | 122 | The "Corresponding Source" for a work in object code form means all 123 | the source code needed to generate, install, and (for an executable 124 | work) run the object code and to modify the work, including scripts to 125 | control those activities. However, it does not include the work's 126 | System Libraries, or general-purpose tools or generally available free 127 | programs which are used unmodified in performing those activities but 128 | which are not part of the work. For example, Corresponding Source 129 | includes interface definition files associated with source files for 130 | the work, and the source code for shared libraries and dynamically 131 | linked subprograms that the work is specifically designed to require, 132 | such as by intimate data communication or control flow between those 133 | subprograms and other parts of the work. 134 | 135 | The Corresponding Source need not include anything that users 136 | can regenerate automatically from other parts of the Corresponding 137 | Source. 138 | 139 | The Corresponding Source for a work in source code form is that 140 | same work. 141 | 142 | 2. Basic Permissions. 143 | 144 | All rights granted under this License are granted for the term of 145 | copyright on the Program, and are irrevocable provided the stated 146 | conditions are met. This License explicitly affirms your unlimited 147 | permission to run the unmodified Program. The output from running a 148 | covered work is covered by this License only if the output, given its 149 | content, constitutes a covered work. This License acknowledges your 150 | rights of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not 153 | convey, without conditions so long as your license otherwise remains 154 | in force. You may convey covered works to others for the sole purpose 155 | of having them make modifications exclusively for you, or provide you 156 | with facilities for running those works, provided that you comply with 157 | the terms of this License in conveying all material for which you do 158 | not control copyright. Those thus making or running the covered works 159 | for you must do so exclusively on your behalf, under your direction 160 | and control, on terms that prohibit them from making any copies of 161 | your copyrighted material outside their relationship with you. 162 | 163 | Conveying under any other circumstances is permitted solely under 164 | the conditions stated below. Sublicensing is not allowed; section 10 165 | makes it unnecessary. 166 | 167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 168 | 169 | No covered work shall be deemed part of an effective technological 170 | measure under any applicable law fulfilling obligations under article 171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 172 | similar laws prohibiting or restricting circumvention of such 173 | measures. 174 | 175 | When you convey a covered work, you waive any legal power to forbid 176 | circumvention of technological measures to the extent such circumvention 177 | is effected by exercising rights under this License with respect to 178 | the covered work, and you disclaim any intention to limit operation or 179 | modification of the work as a means of enforcing, against the work's 180 | users, your or third parties' legal rights to forbid circumvention of 181 | technological measures. 182 | 183 | 4. Conveying Verbatim Copies. 184 | 185 | You may convey verbatim copies of the Program's source code as you 186 | receive it, in any medium, provided that you conspicuously and 187 | appropriately publish on each copy an appropriate copyright notice; 188 | keep intact all notices stating that this License and any 189 | non-permissive terms added in accord with section 7 apply to the code; 190 | keep intact all notices of the absence of any warranty; and give all 191 | recipients a copy of this License along with the Program. 192 | 193 | You may charge any price or no price for each copy that you convey, 194 | and you may offer support or warranty protection for a fee. 195 | 196 | 5. Conveying Modified Source Versions. 197 | 198 | You may convey a work based on the Program, or the modifications to 199 | produce it from the Program, in the form of source code under the 200 | terms of section 4, provided that you also meet all of these conditions: 201 | 202 | a) The work must carry prominent notices stating that you modified 203 | it, and giving a relevant date. 204 | 205 | b) The work must carry prominent notices stating that it is 206 | released under this License and any conditions added under section 207 | 7. This requirement modifies the requirement in section 4 to 208 | "keep intact all notices". 209 | 210 | c) You must license the entire work, as a whole, under this 211 | License to anyone who comes into possession of a copy. This 212 | License will therefore apply, along with any applicable section 7 213 | additional terms, to the whole of the work, and all its parts, 214 | regardless of how they are packaged. This License gives no 215 | permission to license the work in any other way, but it does not 216 | invalidate such permission if you have separately received it. 217 | 218 | d) If the work has interactive user interfaces, each must display 219 | Appropriate Legal Notices; however, if the Program has interactive 220 | interfaces that do not display Appropriate Legal Notices, your 221 | work need not make them do so. 222 | 223 | A compilation of a covered work with other separate and independent 224 | works, which are not by their nature extensions of the covered work, 225 | and which are not combined with it such as to form a larger program, 226 | in or on a volume of a storage or distribution medium, is called an 227 | "aggregate" if the compilation and its resulting copyright are not 228 | used to limit the access or legal rights of the compilation's users 229 | beyond what the individual works permit. Inclusion of a covered work 230 | in an aggregate does not cause this License to apply to the other 231 | parts of the aggregate. 232 | 233 | 6. Conveying Non-Source Forms. 234 | 235 | You may convey a covered work in object code form under the terms 236 | of sections 4 and 5, provided that you also convey the 237 | machine-readable Corresponding Source under the terms of this License, 238 | in one of these ways: 239 | 240 | a) Convey the object code in, or embodied in, a physical product 241 | (including a physical distribution medium), accompanied by the 242 | Corresponding Source fixed on a durable physical medium 243 | customarily used for software interchange. 244 | 245 | b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the 255 | Corresponding Source from a network server at no charge. 256 | 257 | c) Convey individual copies of the object code with a copy of the 258 | written offer to provide the Corresponding Source. This 259 | alternative is allowed only occasionally and noncommercially, and 260 | only if you received the object code with such an offer, in accord 261 | with subsection 6b. 262 | 263 | d) Convey the object code by offering access from a designated 264 | place (gratis or for a charge), and offer equivalent access to the 265 | Corresponding Source in the same way through the same place at no 266 | further charge. You need not require recipients to copy the 267 | Corresponding Source along with the object code. If the place to 268 | copy the object code is a network server, the Corresponding Source 269 | may be on a different server (operated by you or a third party) 270 | that supports equivalent copying facilities, provided you maintain 271 | clear directions next to the object code saying where to find the 272 | Corresponding Source. Regardless of what server hosts the 273 | Corresponding Source, you remain obligated to ensure that it is 274 | available for as long as needed to satisfy these requirements. 275 | 276 | e) Convey the object code using peer-to-peer transmission, provided 277 | you inform other peers where the object code and Corresponding 278 | Source of the work are being offered to the general public at no 279 | charge under subsection 6d. 280 | 281 | A separable portion of the object code, whose source code is excluded 282 | from the Corresponding Source as a System Library, need not be 283 | included in conveying the object code work. 284 | 285 | A "User Product" is either (1) a "consumer product", which means any 286 | tangible personal property which is normally used for personal, family, 287 | or household purposes, or (2) anything designed or sold for incorporation 288 | into a dwelling. In determining whether a product is a consumer product, 289 | doubtful cases shall be resolved in favor of coverage. For a particular 290 | product received by a particular user, "normally used" refers to a 291 | typical or common use of that class of product, regardless of the status 292 | of the particular user or of the way in which the particular user 293 | actually uses, or expects or is expected to use, the product. A product 294 | is a consumer product regardless of whether the product has substantial 295 | commercial, industrial or non-consumer uses, unless such uses represent 296 | the only significant mode of use of the product. 297 | 298 | "Installation Information" for a User Product means any methods, 299 | procedures, authorization keys, or other information required to install 300 | and execute modified versions of a covered work in that User Product from 301 | a modified version of its Corresponding Source. The information must 302 | suffice to ensure that the continued functioning of the modified object 303 | code is in no case prevented or interfered with solely because 304 | modification has been made. 305 | 306 | If you convey an object code work under this section in, or with, or 307 | specifically for use in, a User Product, and the conveying occurs as 308 | part of a transaction in which the right of possession and use of the 309 | User Product is transferred to the recipient in perpetuity or for a 310 | fixed term (regardless of how the transaction is characterized), the 311 | Corresponding Source conveyed under this section must be accompanied 312 | by the Installation Information. But this requirement does not apply 313 | if neither you nor any third party retains the ability to install 314 | modified object code on the User Product (for example, the work has 315 | been installed in ROM). 316 | 317 | The requirement to provide Installation Information does not include a 318 | requirement to continue to provide support service, warranty, or updates 319 | for a work that has been modified or installed by the recipient, or for 320 | the User Product in which it has been modified or installed. Access to a 321 | network may be denied when the modification itself materially and 322 | adversely affects the operation of the network or violates the rules and 323 | protocols for communication across the network. 324 | 325 | Corresponding Source conveyed, and Installation Information provided, 326 | in accord with this section must be in a format that is publicly 327 | documented (and with an implementation available to the public in 328 | source code form), and must require no special password or key for 329 | unpacking, reading or copying. 330 | 331 | 7. Additional Terms. 332 | 333 | "Additional permissions" are terms that supplement the terms of this 334 | License by making exceptions from one or more of its conditions. 335 | Additional permissions that are applicable to the entire Program shall 336 | be treated as though they were included in this License, to the extent 337 | that they are valid under applicable law. If additional permissions 338 | apply only to part of the Program, that part may be used separately 339 | under those permissions, but the entire Program remains governed by 340 | this License without regard to the additional permissions. 341 | 342 | When you convey a copy of a covered work, you may at your option 343 | remove any additional permissions from that copy, or from any part of 344 | it. (Additional permissions may be written to require their own 345 | removal in certain cases when you modify the work.) You may place 346 | additional permissions on material, added by you to a covered work, 347 | for which you have or can give appropriate copyright permission. 348 | 349 | Notwithstanding any other provision of this License, for material you 350 | add to a covered work, you may (if authorized by the copyright holders of 351 | that material) supplement the terms of this License with terms: 352 | 353 | a) Disclaiming warranty or limiting liability differently from the 354 | terms of sections 15 and 16 of this License; or 355 | 356 | b) Requiring preservation of specified reasonable legal notices or 357 | author attributions in that material or in the Appropriate Legal 358 | Notices displayed by works containing it; or 359 | 360 | c) Prohibiting misrepresentation of the origin of that material, or 361 | requiring that modified versions of such material be marked in 362 | reasonable ways as different from the original version; or 363 | 364 | d) Limiting the use for publicity purposes of names of licensors or 365 | authors of the material; or 366 | 367 | e) Declining to grant rights under trademark law for use of some 368 | trade names, trademarks, or service marks; or 369 | 370 | f) Requiring indemnification of licensors and authors of that 371 | material by anyone who conveys the material (or modified versions of 372 | it) with contractual assumptions of liability to the recipient, for 373 | any liability that these contractual assumptions directly impose on 374 | those licensors and authors. 375 | 376 | All other non-permissive additional terms are considered "further 377 | restrictions" within the meaning of section 10. If the Program as you 378 | received it, or any part of it, contains a notice stating that it is 379 | governed by this License along with a term that is a further 380 | restriction, you may remove that term. If a license document contains 381 | a further restriction but permits relicensing or conveying under this 382 | License, you may add to a covered work material governed by the terms 383 | of that license document, provided that the further restriction does 384 | not survive such relicensing or conveying. 385 | 386 | If you add terms to a covered work in accord with this section, you 387 | must place, in the relevant source files, a statement of the 388 | additional terms that apply to those files, or a notice indicating 389 | where to find the applicable terms. 390 | 391 | Additional terms, permissive or non-permissive, may be stated in the 392 | form of a separately written license, or stated as exceptions; 393 | the above requirements apply either way. 394 | 395 | 8. Termination. 396 | 397 | You may not propagate or modify a covered work except as expressly 398 | provided under this License. Any attempt otherwise to propagate or 399 | modify it is void, and will automatically terminate your rights under 400 | this License (including any patent licenses granted under the third 401 | paragraph of section 11). 402 | 403 | However, if you cease all violation of this License, then your 404 | license from a particular copyright holder is reinstated (a) 405 | provisionally, unless and until the copyright holder explicitly and 406 | finally terminates your license, and (b) permanently, if the copyright 407 | holder fails to notify you of the violation by some reasonable means 408 | prior to 60 days after the cessation. 409 | 410 | Moreover, your license from a particular copyright holder is 411 | reinstated permanently if the copyright holder notifies you of the 412 | violation by some reasonable means, this is the first time you have 413 | received notice of violation of this License (for any work) from that 414 | copyright holder, and you cure the violation prior to 30 days after 415 | your receipt of the notice. 416 | 417 | Termination of your rights under this section does not terminate the 418 | licenses of parties who have received copies or rights from you under 419 | this License. If your rights have been terminated and not permanently 420 | reinstated, you do not qualify to receive new licenses for the same 421 | material under section 10. 422 | 423 | 9. Acceptance Not Required for Having Copies. 424 | 425 | You are not required to accept this License in order to receive or 426 | run a copy of the Program. Ancillary propagation of a covered work 427 | occurring solely as a consequence of using peer-to-peer transmission 428 | to receive a copy likewise does not require acceptance. However, 429 | nothing other than this License grants you permission to propagate or 430 | modify any covered work. These actions infringe copyright if you do 431 | not accept this License. Therefore, by modifying or propagating a 432 | covered work, you indicate your acceptance of this License to do so. 433 | 434 | 10. Automatic Licensing of Downstream Recipients. 435 | 436 | Each time you convey a covered work, the recipient automatically 437 | receives a license from the original licensors, to run, modify and 438 | propagate that work, subject to this License. You are not responsible 439 | for enforcing compliance by third parties with this License. 440 | 441 | An "entity transaction" is a transaction transferring control of an 442 | organization, or substantially all assets of one, or subdividing an 443 | organization, or merging organizations. If propagation of a covered 444 | work results from an entity transaction, each party to that 445 | transaction who receives a copy of the work also receives whatever 446 | licenses to the work the party's predecessor in interest had or could 447 | give under the previous paragraph, plus a right to possession of the 448 | Corresponding Source of the work from the predecessor in interest, if 449 | the predecessor has it or can get it with reasonable efforts. 450 | 451 | You may not impose any further restrictions on the exercise of the 452 | rights granted or affirmed under this License. For example, you may 453 | not impose a license fee, royalty, or other charge for exercise of 454 | rights granted under this License, and you may not initiate litigation 455 | (including a cross-claim or counterclaim in a lawsuit) alleging that 456 | any patent claim is infringed by making, using, selling, offering for 457 | sale, or importing the Program or any portion of it. 458 | 459 | 11. Patents. 460 | 461 | A "contributor" is a copyright holder who authorizes use under this 462 | License of the Program or a work on which the Program is based. The 463 | work thus licensed is called the contributor's "contributor version". 464 | 465 | A contributor's "essential patent claims" are all patent claims 466 | owned or controlled by the contributor, whether already acquired or 467 | hereafter acquired, that would be infringed by some manner, permitted 468 | by this License, of making, using, or selling its contributor version, 469 | but do not include claims that would be infringed only as a 470 | consequence of further modification of the contributor version. For 471 | purposes of this definition, "control" includes the right to grant 472 | patent sublicenses in a manner consistent with the requirements of 473 | this License. 474 | 475 | Each contributor grants you a non-exclusive, worldwide, royalty-free 476 | patent license under the contributor's essential patent claims, to 477 | make, use, sell, offer for sale, import and otherwise run, modify and 478 | propagate the contents of its contributor version. 479 | 480 | In the following three paragraphs, a "patent license" is any express 481 | agreement or commitment, however denominated, not to enforce a patent 482 | (such as an express permission to practice a patent or covenant not to 483 | sue for patent infringement). To "grant" such a patent license to a 484 | party means to make such an agreement or commitment not to enforce a 485 | patent against the party. 486 | 487 | If you convey a covered work, knowingly relying on a patent license, 488 | and the Corresponding Source of the work is not available for anyone 489 | to copy, free of charge and under the terms of this License, through a 490 | publicly available network server or other readily accessible means, 491 | then you must either (1) cause the Corresponding Source to be so 492 | available, or (2) arrange to deprive yourself of the benefit of the 493 | patent license for this particular work, or (3) arrange, in a manner 494 | consistent with the requirements of this License, to extend the patent 495 | license to downstream recipients. "Knowingly relying" means you have 496 | actual knowledge that, but for the patent license, your conveying the 497 | covered work in a country, or your recipient's use of the covered work 498 | in a country, would infringe one or more identifiable patents in that 499 | country that you have reason to believe are valid. 500 | 501 | If, pursuant to or in connection with a single transaction or 502 | arrangement, you convey, or propagate by procuring conveyance of, a 503 | covered work, and grant a patent license to some of the parties 504 | receiving the covered work authorizing them to use, propagate, modify 505 | or convey a specific copy of the covered work, then the patent license 506 | you grant is automatically extended to all recipients of the covered 507 | work and works based on it. 508 | 509 | A patent license is "discriminatory" if it does not include within 510 | the scope of its coverage, prohibits the exercise of, or is 511 | conditioned on the non-exercise of one or more of the rights that are 512 | specifically granted under this License. You may not convey a covered 513 | work if you are a party to an arrangement with a third party that is 514 | in the business of distributing software, under which you make payment 515 | to the third party based on the extent of your activity of conveying 516 | the work, and under which the third party grants, to any of the 517 | parties who would receive the covered work from you, a discriminatory 518 | patent license (a) in connection with copies of the covered work 519 | conveyed by you (or copies made from those copies), or (b) primarily 520 | for and in connection with specific products or compilations that 521 | contain the covered work, unless you entered into that arrangement, 522 | or that patent license was granted, prior to 28 March 2007. 523 | 524 | Nothing in this License shall be construed as excluding or limiting 525 | any implied license or other defenses to infringement that may 526 | otherwise be available to you under applicable patent law. 527 | 528 | 12. No Surrender of Others' Freedom. 529 | 530 | If conditions are imposed on you (whether by court order, agreement or 531 | otherwise) that contradict the conditions of this License, they do not 532 | excuse you from the conditions of this License. If you cannot convey a 533 | covered work so as to satisfy simultaneously your obligations under this 534 | License and any other pertinent obligations, then as a consequence you may 535 | not convey it at all. For example, if you agree to terms that obligate you 536 | to collect a royalty for further conveying from those to whom you convey 537 | the Program, the only way you could satisfy both those terms and this 538 | License would be to refrain entirely from conveying the Program. 539 | 540 | 13. Remote Network Interaction; Use with the GNU General Public License. 541 | 542 | Notwithstanding any other provision of this License, if you modify the 543 | Program, your modified version must prominently offer all users 544 | interacting with it remotely through a computer network (if your version 545 | supports such interaction) an opportunity to receive the Corresponding 546 | Source of your version by providing access to the Corresponding Source 547 | from a network server at no charge, through some standard or customary 548 | means of facilitating copying of software. This Corresponding Source 549 | shall include the Corresponding Source for any work covered by version 3 550 | of the GNU General Public License that is incorporated pursuant to the 551 | following paragraph. 552 | 553 | Notwithstanding any other provision of this License, you have 554 | permission to link or combine any covered work with a work licensed 555 | under version 3 of the GNU General Public License into a single 556 | combined work, and to convey the resulting work. The terms of this 557 | License will continue to apply to the part which is the covered work, 558 | but the work with which it is combined will remain governed by version 559 | 3 of the GNU General Public License. 560 | 561 | 14. Revised Versions of this License. 562 | 563 | The Free Software Foundation may publish revised and/or new versions of 564 | the GNU Affero General Public License from time to time. Such new versions 565 | will be similar in spirit to the present version, but may differ in detail to 566 | address new problems or concerns. 567 | 568 | Each version is given a distinguishing version number. If the 569 | Program specifies that a certain numbered version of the GNU Affero General 570 | Public License "or any later version" applies to it, you have the 571 | option of following the terms and conditions either of that numbered 572 | version or of any later version published by the Free Software 573 | Foundation. If the Program does not specify a version number of the 574 | GNU Affero General Public License, you may choose any version ever published 575 | by the Free Software Foundation. 576 | 577 | If the Program specifies that a proxy can decide which future 578 | versions of the GNU Affero General Public License can be used, that proxy's 579 | public statement of acceptance of a version permanently authorizes you 580 | to choose that version for the Program. 581 | 582 | Later license versions may give you additional or different 583 | permissions. However, no additional obligations are imposed on any 584 | author or copyright holder as a result of your choosing to follow a 585 | later version. 586 | 587 | 15. Disclaimer of Warranty. 588 | 589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 597 | 598 | 16. Limitation of Liability. 599 | 600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 608 | SUCH DAMAGES. 609 | 610 | 17. Interpretation of Sections 15 and 16. 611 | 612 | If the disclaimer of warranty and limitation of liability provided 613 | above cannot be given local legal effect according to their terms, 614 | reviewing courts shall apply local law that most closely approximates 615 | an absolute waiver of all civil liability in connection with the 616 | Program, unless a warranty or assumption of liability accompanies a 617 | copy of the Program in return for a fee. 618 | 619 | END OF TERMS AND CONDITIONS 620 | 621 | How to Apply These Terms to Your New Programs 622 | 623 | If you develop a new program, and you want it to be of the greatest 624 | possible use to the public, the best way to achieve this is to make it 625 | free software which everyone can redistribute and change under these terms. 626 | 627 | To do so, attach the following notices to the program. It is safest 628 | to attach them to the start of each source file to most effectively 629 | state the exclusion of warranty; and each file should have at least 630 | the "copyright" line and a pointer to where the full notice is found. 631 | 632 | 633 | Copyright (C) 634 | 635 | This program is free software: you can redistribute it and/or modify 636 | it under the terms of the GNU Affero General Public License as published 637 | by the Free Software Foundation, either version 3 of the License, or 638 | (at your option) any later version. 639 | 640 | This program is distributed in the hope that it will be useful, 641 | but WITHOUT ANY WARRANTY; without even the implied warranty of 642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 643 | GNU Affero General Public License for more details. 644 | 645 | You should have received a copy of the GNU Affero General Public License 646 | along with this program. If not, see . 647 | 648 | Also add information on how to contact you by electronic and paper mail. 649 | 650 | If your software can interact with users remotely through a computer 651 | network, you should also make sure that it provides a way for users to 652 | get its source. For example, if your program is a web application, its 653 | interface could display a "Source" link that leads users to an archive 654 | of the code. There are many ways you could offer source, and different 655 | solutions will be better for different programs; see section 13 for the 656 | specific requirements. 657 | 658 | You should also get your employer (if you work as a programmer) or school, 659 | if any, to sign a "copyright disclaimer" for the program, if necessary. 660 | For more information on this, and how to apply and follow the GNU AGPL, see 661 | . 662 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2023 OpaqueGlass 3 | 4 | This program is released under the AGPLv3 license. 5 | For details, see: 6 | - License Text: https://www.gnu.org/licenses/agpl-3.0.html 7 | - License Summary: https://tldrlegal.com/license/gnu-affero-general-public-license-v3-(agpl-3.0) 8 | 9 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 10 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM 11 | "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 12 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK 13 | AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, 14 | YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 15 | 16 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, 17 | OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU 18 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF 19 | THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 20 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO 21 | OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 22 | POSSIBILITY OF SUCH DAMAGES. 23 | 24 | */ 25 | 26 | const siyuan = require('siyuan'); 27 | 28 | /** 29 | * 全局变量 30 | */ 31 | let g_switchTabObserver; // 页签切换与新建监视器 32 | let g_windowObserver; // 窗口监视器 33 | const CONSTANTS = { 34 | STYLE_ID: "og-file-lost-alarmer-plugin-style", 35 | ICON_ALL: 2, 36 | ICON_NONE: 0, 37 | ICON_CUSTOM_ONLY: 1, 38 | PLUGIN_NAME: "og_hierachy_navigate", 39 | SAVE_TIMEOUT: 900, 40 | POP_NONE: 0, 41 | POP_LIMIT: 1, 42 | POP_ALL: 2, 43 | ICON_WARN: "❌⚠", 44 | ICON_QUESTION: "❓", 45 | ICON_SUCCESS: "✅", 46 | } 47 | let g_writeStorage; 48 | let g_isMobile = false; 49 | let g_mutex = 0; 50 | let g_app; 51 | let g_isRecentClicked = 0; // 判定是否近期点击过文档树 52 | let g_recentClickedId = null; 53 | let g_recentClickCheckTimeout = null; // 等待重新判定timeout 54 | let g_delayTimeMs = 300; // 判定延迟300ms 55 | let g_setting = { 56 | checkMissingAssets: null, // 检查资源文件 57 | checkRepoSnapshotSingleTypeCount: null, // 将最近的快照文件数与上次、上上次比较,当文件数量减少超过阈值时提示,设置为0则不提示(只检查.sy、.png、.jpg文件) 58 | checkLastSnapshotRemoveThreshold: null, // 比较近2个快照的文件删除情况,当删除文件数到达阈值时提示 需要遍历一下,只检查文档、assets文件 59 | alwaysShowSummaryDialog: null, // 总是展示总结对话框 60 | checkFileIdExist: null, // 检查指定的文件id是否存在,不存在则提示 61 | checkLastSnapshot3rdFileLostThreshold: null, 62 | }; 63 | let g_setting_default = { 64 | checkMissingAssets: true, 65 | checkRepoSnapshotSingleTypeCount: 50, 66 | checkLastSnapshotRemoveThreshold: 50, 67 | alwaysShowSummaryDialog: false, 68 | checkFileIdExist: "", 69 | checkLastSnapshot3rdFileLostThreshold: 50, 70 | }; 71 | /** 72 | * Plugin类 73 | */ 74 | class FileLostAlarmerPlugin extends siyuan.Plugin { 75 | 76 | tabOpenObserver = null; 77 | 78 | onload() { 79 | g_isMobile = isMobile(); 80 | language = this.i18n; 81 | g_app = this.app; 82 | // 读取配置 83 | // TODO: 读取配置API变更 84 | Object.assign(g_setting, g_setting_default); 85 | g_writeStorage = this.saveData; 86 | this.addIcons(``) 87 | this.addTopBar({ 88 | icon: "iconPackageCheck", 89 | title: '立即检查最近快照情况', 90 | callback: checkMain.bind(true) 91 | }); 92 | 93 | logPush('FileLostAlarmerPlugin'); 94 | } 95 | onLayoutReady() { 96 | this.loadData("settings.json").then((settingCache)=>{ 97 | // 解析并载入配置 98 | try { 99 | // let settingData = JSON.parse(settingCache); 100 | Object.assign(g_setting, settingCache); 101 | this.eventBusInnerHandler(); 102 | }catch(e){ 103 | warnPush("DBT载入配置时发生错误",e); 104 | } 105 | // if (!initRetry()) { 106 | // setInterval(initRetry, 3000); 107 | // } 108 | setTimeout(checkMain.bind(false), 5000); 109 | setStyle(); 110 | }, (e)=> { 111 | debugPush("配置文件读入失败", e); 112 | }); 113 | } 114 | 115 | onunload() { 116 | this.el && this.el.remove(); 117 | removeStyle(); 118 | this.eventBusUnbind(); 119 | // 善后 120 | } 121 | // TODO: 重写载入设置 122 | openSetting() { 123 | // 生成Dialog内容 124 | 125 | // 创建dialog 126 | const settingDialog = new siyuan.Dialog({ 127 | "title": language["setting_panel_title"], 128 | "content": ` 129 |
130 |
131 |
132 |
133 |
134 | 135 |
136 | `, 137 | "width": isMobile() ? "92vw":"1040px", 138 | "height": isMobile() ? "50vw":"80vh", 139 | }); 140 | // console.log("dialog", settingDialog); 141 | const actionButtons = settingDialog.element.querySelectorAll(`#${CONSTANTS.PLUGIN_NAME}-form-action button`); 142 | actionButtons[0].addEventListener("click",()=>{settingDialog.destroy()}), 143 | actionButtons[1].addEventListener("click",()=>{ 144 | // this.writeStorage('hello.txt', 'world' + Math.random().toFixed(2)); 145 | debugPush('SAVING'); 146 | let uiSettings = loadUISettings(settingForm); 147 | // clearTimeout(g_saveTimeout); 148 | // g_saveTimeout = setTimeout(()=>{ 149 | this.saveData(`settings.json`, JSON.stringify(uiSettings)); 150 | Object.assign(g_setting, uiSettings); 151 | removeStyle(); 152 | setStyle(); 153 | try { 154 | this.eventBusInnerHandler(); 155 | }catch(err){ 156 | console.error("og eventBusError", err); 157 | } 158 | debugPush("SAVED"); 159 | settingDialog.destroy(); 160 | // }, CONSTANTS.SAVE_TIMEOUT); 161 | }); 162 | // 绑定dialog和移除操作 163 | 164 | // 生成配置页面 165 | const hello = document.createElement('div'); 166 | const settingForm = document.createElement("form"); 167 | settingForm.setAttribute("name", CONSTANTS.PLUGIN_NAME); 168 | settingForm.innerHTML = generateSettingPanelHTML([ 169 | // 基础设定 170 | new SettingProperty("checkMissingAssets", "SWITCH", null), 171 | new SettingProperty("checkRepoSnapshotSingleTypeCount", "NUMBER", [0, 1200]), 172 | new SettingProperty("checkLastSnapshotRemoveThreshold", "NUMBER", [0, 1200]), 173 | new SettingProperty("checkLastSnapshot3rdFileLostThreshold", "NUMBER", [0, 1200]), 174 | new SettingProperty("alwaysShowSummaryDialog", "SWITCH", null), 175 | ]); 176 | 177 | hello.appendChild(settingForm); 178 | settingDialog.element.querySelector(`#${CONSTANTS.PLUGIN_NAME}-form-content`).appendChild(hello); 179 | } 180 | 181 | eventBusInnerHandler() { 182 | 183 | // if (true || g_setting.dblclickShowSubDoc) { 184 | // document.querySelector('.sy__file')?.addEventListener('click', clickFileTreeHandler, true); 185 | // } else { 186 | // document.querySelector('.sy__file')?.removeEventListener('click', clickFileTreeHandler, true); 187 | // } 188 | this.eventBus.on("sync-end", this.checkIt); 189 | } 190 | eventBusUnbind() { 191 | this.eventBus.off("sync-end", this.checkIt); 192 | } 193 | checkIt() { 194 | checkMain(false); 195 | } 196 | } 197 | 198 | 199 | 200 | // debug push 201 | let g_DEBUG = 5; 202 | const g_NAME = "fla"; 203 | const g_FULLNAME = "文件丢失提示"; 204 | 205 | /* 206 | LEVEL 0 忽略所有 207 | LEVEL 1 仅Error 208 | LEVEL 2 Err + Warn 209 | LEVEL 3 Err + Warn + Info 210 | LEVEL 4 Err + Warn + Info + Log 211 | LEVEL 5 Err + Warn + Info + Log + Debug 212 | */ 213 | function commonPushCheck() { 214 | if (window.top["OpaqueGlassDebugV2"] == undefined || window.top["OpaqueGlassDebugV2"][g_NAME] == undefined) { 215 | return g_DEBUG; 216 | } 217 | return window.top["OpaqueGlassDebugV2"][g_NAME]; 218 | } 219 | 220 | function isDebugMode() { 221 | return commonPushCheck() > g_DEBUG; 222 | } 223 | 224 | function debugPush(str, ...args) { 225 | if (commonPushCheck() >= 5) { 226 | console.debug(`${g_FULLNAME}[D] ${new Date().toLocaleString()} ${str}`, ...args); 227 | } 228 | } 229 | 230 | function logPush(str, ...args) { 231 | if (commonPushCheck() >= 4) { 232 | console.log(`${g_FULLNAME}[L] ${new Date().toLocaleString()} ${str}`, ...args); 233 | } 234 | } 235 | 236 | function errorPush(str, ... args) { 237 | if (commonPushCheck() >= 1) { 238 | console.error(`${g_FULLNAME}[E] ${new Date().toLocaleString()} ${str}`, ...args); 239 | } 240 | } 241 | 242 | function warnPush(str, ... args) { 243 | if (commonPushCheck() >= 2) { 244 | console.warn(`${g_FULLNAME}[W] ${new Date().toLocaleString()} ${str}`, ...args); 245 | } 246 | } 247 | 248 | class SettingProperty { 249 | id; 250 | simpId; 251 | name; 252 | desp; 253 | type; 254 | limit; 255 | value; 256 | /** 257 | * 设置属性对象 258 | * @param {*} id 唯一定位id 259 | * @param {*} type 设置项类型 260 | * @param {*} limit 限制 261 | */ 262 | constructor(id, type, limit, value = undefined) { 263 | this.id = `${CONSTANTS.PLUGIN_NAME}_${id}`; 264 | this.simpId = id; 265 | this.name = language[`setting_${id}_name`]; 266 | this.desp = language[`setting_${id}_desp`]; 267 | this.type = type; 268 | this.limit = limit; 269 | if (value) { 270 | this.value = value; 271 | }else{ 272 | this.value = g_setting[this.simpId]; 273 | } 274 | } 275 | } 276 | 277 | 278 | function initRetry() { 279 | if (!document.querySelector(".sy__file")) { 280 | logPush("未检测到文档树,终止listener绑定"); 281 | return true; 282 | } 283 | document.querySelector('.sy__file')?.addEventListener('click', clickFileTreeHandler, true); 284 | } 285 | 286 | 287 | async function checkMain(showDialog = false) { 288 | let data = { 289 | assetsLostCount: 0, 290 | repoTotalFileLostCount: 0, 291 | repoSYFileLostCount: 0, 292 | repoPNGFileLostCount: 0, 293 | repoJPGFileLostCount: 0, 294 | repoLastSnapshotRemoveList: [], 295 | repoLastSnapshotRemove3rdFileList: [], 296 | docIdNotExistDetailList: [], 297 | currentSyncTimeStr: "", 298 | previousSyncTimeStr: "", 299 | isMergeSync: null, 300 | snapshotNotEnought: false 301 | } 302 | let needAlert = false; 303 | data.assetsLostCount = await checkAssets(); 304 | if (data.assetsLostCount > 0) { 305 | needAlert = true; 306 | } 307 | let tempSnapshotNeedAlert = false; 308 | [data.snapshotNotEnought, tempSnapshotNeedAlert, data.repoSYFileLostCount, data.repoPNGFileLostCount, data.repoJPGFileLostCount, 309 | data.repoLastSnapshotRemoveList, data.repoLastSnapshotRemove3rdFileList, data.isMergeSync, data.currentSyncTimeStr, data.previousSyncTimeStr] = await checkRepoSnapshot(); 310 | if (tempSnapshotNeedAlert) { 311 | needAlert = true; 312 | } 313 | debugPush("needAlert", needAlert, g_setting.alwaysShowSummaryDialog, showDialog); 314 | debugPush("data", data); 315 | // const totalLost = data.assetsLostCount + data.repoSYFileLostCount + data.repoPNGFileLostCount + data.repoJPGFileLostCount + repoLastSnapshotRemove3rdFileList.length + repoLastSnapshotRemoveList.length + data.docIdNotExistCount; 316 | // TODO: 检查文件id是否存在) 317 | if (needAlert || g_setting.alwaysShowSummaryDialog || showDialog) { 318 | pushUserWarning(data); 319 | } 320 | } 321 | 322 | async function pushUserWarning(data) { 323 | let checkResult = { 324 | assets: getWarningEmoji(data.assetsLostCount, 0), 325 | fileCount: getWarningEmoji(Math.max(data.repoSYFileLostCount, data.repoPNGFileLostCount, data.repoJPGFileLostCount), g_setting.checkRepoSnapshotSingleTypeCount), 326 | deleteFileCount: getWarningEmoji(data.repoLastSnapshotRemoveList.length, g_setting.checkLastSnapshotRemoveThreshold), 327 | delete3rdFileCount: getWarningEmoji(data.repoLastSnapshotRemove3rdFileList.length, g_setting.checkLastSnapshot3rdFileLostThreshold), 328 | deleteFileIdCount: getWarningEmoji(data.docIdNotExistDetailList.length, 0) 329 | } 330 | let snapshopMsg = ` 331 | ${checkResult.deleteFileCount}${language["critical_lost_warning"].replace("%%",data.repoLastSnapshotRemoveList.length)} 332 | (${language["file_type_sy"].replace("%%", data.repoSYFileLostCount)}, ${language["file_type_png"].replace("%%", data.repoPNGFileLostCount)}, ${language["file_type_jpg"].replace("%%", data.repoJPGFileLostCount)})
333 | ${checkResult.delete3rdFileCount}${language["3rd_lost_warning"].replace("%%", data.repoLastSnapshotRemove3rdFileList.length)}
334 | 335 | ${language["critical_lost_detail"]}
336 | ${JSON.stringify(data.repoLastSnapshotRemoveList)}
337 | ${language["3rd_lost_detail"]}
338 | ${JSON.stringify(data.repoLastSnapshotRemove3rdFileList)}
339 | `; 340 | if (data.snapshotNotEnought) { 341 | snapshopMsg = `${language["snapshot_not_enough"]}
`; 342 | } 343 | let despMsg = ` 344 |
345 | 346 | ${language["snapshot_sync_desp"].replace("%0%", data.isMergeSync ? language["merge_sync"]:"").replace("%1%", data.currentSyncTimeStr).replace("%2%", data.previousSyncTimeStr)}${language["file_lost_warning_desp"]}
347 | ${checkResult.assets}${language["assets_lost_warning"].replace("%%", data.assetsLostCount)}
348 | 349 | ${snapshopMsg} 350 | 351 |
352 | `; 353 | let type = CONSTANTS.ICON_SUCCESS; 354 | for (let key in checkResult) { 355 | let icon = checkResult[key]; 356 | if (icon == CONSTANTS.ICON_WARN) { 357 | type = icon; 358 | break; 359 | } 360 | if (icon == CONSTANTS.ICON_QUESTION) { 361 | type = icon; 362 | } 363 | } 364 | siyuan.confirm(language["file_compare_result_title"].replace("%%", type), despMsg, openRepoDialog); 365 | if (type == CONSTANTS.ICON_WARN) { 366 | siyuan.showMessage(language["file_lost_warning"]); 367 | } 368 | function getWarningEmoji(test, threshold) { 369 | if (test > threshold) { 370 | return CONSTANTS.ICON_WARN 371 | } else if (test <= 0) { 372 | return CONSTANTS.ICON_SUCCESS; 373 | } else { 374 | return CONSTANTS.ICON_QUESTION; 375 | } 376 | } 377 | } 378 | 379 | function openRepoDialog() { 380 | dispatchKeyEvent("dataHistory"); 381 | setTimeout(()=>{window.document.querySelector("div[data-key='dialog-history'] .layout-tab-bar [data-type=repo]")?.click()}, 400); 382 | } 383 | 384 | async function checkAssets() { 385 | const missingAssetsList = await getMissingAssets(); 386 | debugPush("missngAssetsList", missingAssetsList); 387 | if (missingAssetsList != null && missingAssetsList.length > 0) { 388 | return missingAssetsList.length; 389 | } else if (missingAssetsList == null) { 390 | warnPush("网络请求错误,未能检测文件资源丢失情况"); 391 | } 392 | return 0; 393 | } 394 | 395 | async function checkRepoSnapshot() { 396 | let needAlert = false; 397 | const snapshotsList = await getSnapshotsList(); 398 | if (!snapshotsList || snapshotsList.length < 3) { 399 | logPush("快照数量不足,无法检测文件丢失情况"); 400 | return [true, false, 0, 0, 0, [], [], false, 'N/A', 'N/A']; 401 | } 402 | // 获取最近同步时间和上一次同步快照时间,注意,如果是merge,则需要获取[2]的快照时间为上一个快照同步时间 403 | let snapshotIds = []; 404 | for (let i = 0; i < 3; i++) { 405 | let snapshot = snapshotsList[i]; 406 | snapshotIds.push(snapshot.id); 407 | } 408 | let isMergeSync = false; 409 | if (snapshotsList[0].memo.includes("[Sync] Cloud sync merge")) { 410 | isMergeSync = true; 411 | } 412 | // 生成同步时间和同步状态提示 413 | let currentSyncTimeStr = snapshotsList[0].hCreated; 414 | let previousSyncTimeStr = snapshotsList[1].hCreated; 415 | if (isMergeSync) { 416 | previousSyncTimeStr = snapshotsList[2].hCreated; 417 | } 418 | // 检查总文件数 419 | let removeJPGFileCount = 0, removePNGFileCount = 0, removeSYFileCount = 0; 420 | // 检查文件删除情况 421 | let diffSnapshots = await getDiffSnapshots(snapshotIds[1], snapshotIds[0]); 422 | let repoLastSnapshotRemoveList = []; 423 | let repoLastSnapshotRemove3rdFileList = []; 424 | if (diffSnapshots && diffSnapshots.removesRight && diffSnapshots.removesRight.length > 0) { 425 | for (let remove of diffSnapshots.removesRight) { 426 | let lowerCasePath = remove.path.toLowerCase(); 427 | if (lowerCasePath.startsWith("/assets") || (lowerCasePath.endsWith(".sy") && !remove.title.includes("conflict") )) { 428 | repoLastSnapshotRemoveList.push(remove.title); 429 | switch (lowerCasePath.split(".").pop()) { 430 | case "jpg": { 431 | removeJPGFileCount++; 432 | break; 433 | } 434 | case "png": { 435 | removePNGFileCount++; 436 | break; 437 | } 438 | case "sy": { 439 | removeSYFileCount++; 440 | break; 441 | } 442 | } 443 | continue; 444 | } 445 | repoLastSnapshotRemove3rdFileList.push(remove.title); 446 | } 447 | } 448 | if (removeJPGFileCount > g_setting.checkRepoSnapshotSingleTypeCount || removePNGFileCount > g_setting.checkRepoSnapshotSingleTypeCount || removeSYFileCount > g_setting.checkRepoSnapshotSingleTypeCount) { 449 | needAlert = true; 450 | } 451 | if (repoLastSnapshotRemoveList.length > g_setting.checkLastSnapshotRemoveThreshold) { 452 | needAlert = true; 453 | } 454 | if (repoLastSnapshotRemove3rdFileList.length > 0 && g_setting.checkLastSnapshot3rdFileLostThreshold) { 455 | 456 | } 457 | debugPush("data_list", removeSYFileCount, removePNGFileCount, removeJPGFileCount, repoLastSnapshotRemoveList); 458 | return [false, needAlert, removeSYFileCount, removePNGFileCount, removeJPGFileCount, repoLastSnapshotRemoveList, repoLastSnapshotRemove3rdFileList, isMergeSync, currentSyncTimeStr, previousSyncTimeStr]; 459 | } 460 | 461 | async function checkFileIdExist() { 462 | // 可能需要缓存一下文件名和文件路径,保存在/temp/plugins吧 463 | 464 | } 465 | 466 | 467 | 468 | function setStyle() { 469 | const head = document.getElementsByTagName('head')[0]; 470 | const style = document.createElement('style'); 471 | style.setAttribute("id", CONSTANTS.STYLE_ID); 472 | 473 | 474 | style.innerHTML = ` 475 | .og-file-lost-dialog-number{ 476 | font-weight: bold; 477 | } 478 | `; 479 | head.appendChild(style); 480 | } 481 | 482 | function styleEscape(str) { 483 | if (!str) return ""; 484 | return str.replace(new RegExp("<[^<]*style[^>]*>", "g"), ""); 485 | } 486 | 487 | function removeStyle() { 488 | document.getElementById(CONSTANTS.STYLE_ID)?.remove(); 489 | } 490 | 491 | /* ************ API 相关 **************** */ 492 | 493 | async function getMissingAssets() { 494 | const url = "/api/asset/getMissingAssets"; 495 | let response = await request(url, {}); 496 | debugPush("getMissingAssets", response); 497 | if (response.code == 0) { 498 | return response.data.missingAssets; 499 | } 500 | return null; 501 | } 502 | 503 | async function getSnapshotsList() { 504 | const url = "/api/repo/getRepoSnapshots"; 505 | let response = await request(url, {page: 1}); 506 | debugPush("getSnapshotsList", response); 507 | if (response.code == 0) { 508 | return response.data.snapshots; 509 | } 510 | return null; 511 | } 512 | 513 | async function getDiffSnapshots(leftId, rightId) { 514 | const url = "/api/repo/diffRepoSnapshots"; 515 | let response = await request(url, {left: leftId, right: rightId}); 516 | debugPush("diffSnapshots", response); 517 | if (response.code == 0) { 518 | return response.data; 519 | } 520 | return null; 521 | } 522 | 523 | async function checkFileIdExist(docId) { 524 | // 解析设置项,获取文档id,使用getDocInfo遍历(为了保证及时行,不使用sql) 525 | } 526 | 527 | function getNotebooks() { 528 | let notebooks = window.top.siyuan.notebooks; 529 | return notebooks; 530 | } 531 | 532 | 533 | function getFocusedBlock() { 534 | if (document.activeElement.classList.contains('protyle-wysiwyg')) { 535 | /* 光标在编辑区内 */ 536 | let block = window.getSelection()?.focusNode?.parentElement; // 当前光标 537 | while (block != null && block?.dataset?.nodeId == null) block = block.parentElement; 538 | return block; 539 | } 540 | else return null; 541 | } 542 | 543 | function getFocusedBlockId() { 544 | const focusedBlock = getFocusedBlock(); 545 | if (focusedBlock == null) { 546 | return null; 547 | } 548 | return focusedBlock.dataset.nodeId; 549 | } 550 | 551 | async function request(url, data) { 552 | let resData = null; 553 | await fetch(url, { 554 | body: JSON.stringify(data), 555 | method: 'POST' 556 | }).then(function (response) { 557 | resData = response.json(); 558 | }); 559 | return resData; 560 | } 561 | 562 | async function parseBody(response) { 563 | let r = await response; 564 | return r.code === 0 ? r.data : null; 565 | } 566 | 567 | async function listDocsByPath({path, notebook = undefined, sort = undefined, maxListLength = undefined}) { 568 | let data = { 569 | path: path 570 | }; 571 | if (notebook) data["notebook"] = notebook; 572 | if (sort) data["sort"] = sort; 573 | if (g_setting.docMaxNum != 0) { 574 | data["maxListCount"] = g_setting.docMaxNum >= 32 ? g_setting.docMaxNum : 32; 575 | } else { 576 | data["maxListCount"] = 0; 577 | } 578 | let url = '/api/filetree/listDocsByPath'; 579 | return parseBody(request(url, data)); 580 | //文档hepath与Markdown 内容 581 | } 582 | 583 | async function sqlAPI(stmt) { 584 | let data = { 585 | "stmt": stmt 586 | }; 587 | let url = `/api/query/sql`; 588 | return parseBody(request(url, data)); 589 | } 590 | 591 | async function getTreeStat(id) { 592 | let data = { 593 | "id": id 594 | }; 595 | let url = `/api/block/getTreeStat`; 596 | return parseBody(request(url, data)); 597 | } 598 | 599 | async function getDocInfo(id) { 600 | let data = { 601 | "id": id 602 | }; 603 | let url = `/api/block/getDocInfo`; 604 | return parseBody(request(url, data)); 605 | } 606 | 607 | async function getKramdown(blockid){ 608 | let data = { 609 | "id": blockid 610 | }; 611 | let url = "/api/block/getBlockKramdown"; 612 | let response = await parseBody(request(url, data)); 613 | if (response) { 614 | return response.kramdown; 615 | } 616 | } 617 | 618 | async function isDocHasAv(docId) { 619 | let sqlResult = await sqlAPI(` 620 | SELECT count(*) as avcount FROM blocks WHERE root_id = '${docId}' 621 | AND type = 'av' 622 | `); 623 | debugPush("文档 av判断", sqlResult); 624 | if (sqlResult.length > 0 && sqlResult[0].avcount > 0) { 625 | return true; 626 | } else { 627 | 628 | return false; 629 | } 630 | } 631 | 632 | async function isDocEmpty(docId, blockCountThreshold = 0) { 633 | // 检查父文档是否为空 634 | let treeStat = await getTreeStat(docId); 635 | if (blockCountThreshold == 0 && treeStat.wordCount != 0 && treeStat.imageCount != 0) { 636 | debugPush("treeStat判定文档非空,不插入挂件"); 637 | return false; 638 | } 639 | if (blockCountThreshold != 0) { 640 | let blockCountSqlResult = await sqlAPI(`SELECT count(*) as bcount FROM blocks WHERE root_id like '${docId}' AND type in ('p', 'c', 'iframe', 'html', 'video', 'audio', 'widget', 'query_embed', 't')`); 641 | if (blockCountSqlResult.length > 0) { 642 | if (blockCountSqlResult[0].bcount > blockCountThreshold) { 643 | return false; 644 | } else { 645 | return true; 646 | } 647 | } 648 | } 649 | 650 | let sqlResult = await sqlAPI(`SELECT markdown FROM blocks WHERE 651 | root_id like '${docId}' 652 | AND type != 'd' 653 | AND (type != 'p' 654 | OR (type = 'p' AND length != 0) 655 | ) 656 | LIMIT 5`); 657 | if (sqlResult.length <= 0) { 658 | return true; 659 | } else { 660 | debugPush("sql判定文档非空,不插入挂件"); 661 | return false; 662 | } 663 | // 获取父文档内容 664 | let parentDocContent = await getKramdown(docId); 665 | // 简化判断,过长的父文档内容必定有文本,不插入 // 作为参考,空文档的kramdown长度约为400 666 | if (parentDocContent.length > 1000) { 667 | debugPush("父文档较长,认为非空,不插入挂件", parentDocContent.length); 668 | return; 669 | } 670 | // console.log(parentDocContent); 671 | // 清理ial和换行、空格 672 | let parentDocPlainText = parentDocContent; 673 | // 清理ial中的对象信息(例:文档块中的scrool字段),防止后面匹配ial出现遗漏 674 | parentDocPlainText = parentDocPlainText.replace(new RegExp('\\"{[^\n]*}\\"', "gm"), "\"\"") 675 | // console.log("替换内部对象中间结果", parentDocPlainText); 676 | // 清理ial 677 | parentDocPlainText = parentDocPlainText.replace(new RegExp('{:[^}]*}', "gm"), ""); 678 | // 清理换行 679 | parentDocPlainText = parentDocPlainText.replace(new RegExp('\n', "gm"), ""); 680 | // 清理空格 681 | parentDocPlainText = parentDocPlainText.replace(new RegExp(' +', "gm"), ""); 682 | debugPush(`父文档文本(+标记)为 ${parentDocPlainText}`); 683 | debugPush(`父文档内容为空?${parentDocPlainText == ""}`); 684 | if (parentDocPlainText != "") return false; 685 | return true; 686 | } 687 | 688 | async function getCurrentDocIdF() { 689 | let thisDocId; 690 | thisDocId = window.top.document.querySelector(".layout__wnd--active .protyle.fn__flex-1:not(.fn__none) .protyle-background")?.getAttribute("data-node-id"); 691 | debugPush("thisDocId by first id", thisDocId); 692 | if (!thisDocId && g_isMobile) { 693 | // UNSTABLE: 面包屑样式变动将导致此方案错误! 694 | try { 695 | let temp; 696 | temp = window.top.document.querySelector(".protyle-breadcrumb .protyle-breadcrumb__item .popover__block[data-id]")?.getAttribute("data-id"); 697 | let iconArray = window.top.document.querySelectorAll(".protyle-breadcrumb .protyle-breadcrumb__item .popover__block[data-id]"); 698 | for (let i = 0; i < iconArray.length; i++) { 699 | let iconOne = iconArray[i]; 700 | if (iconOne.children.length > 0 701 | && iconOne.children[0].getAttribute("xlink:href") == "#iconFile"){ 702 | temp = iconOne.getAttribute("data-id"); 703 | break; 704 | } 705 | } 706 | thisDocId = temp; 707 | }catch(e){ 708 | console.error(e); 709 | temp = null; 710 | } 711 | } 712 | if (!thisDocId) { 713 | thisDocId = window.top.document.querySelector(".protyle.fn__flex-1:not(.fn__none) .protyle-background")?.getAttribute("data-node-id"); 714 | debugPush("thisDocId by background must match, id", thisDocId); 715 | } 716 | return thisDocId; 717 | } 718 | 719 | function sleep(time){ 720 | return new Promise((resolve) => setTimeout(resolve, time)); 721 | } 722 | 723 | /** 724 | * 在点击时打开思源块/文档 725 | * 为引入本项目,和原代码相比有更改 726 | * @refer https://github.com/leolee9086/cc-template/blob/6909dac169e720d3354d77685d6cc705b1ae95be/baselib/src/commonFunctionsForSiyuan.js#L118-L141 727 | * @license 木兰宽松许可证 728 | * @param {点击事件} event 729 | */ 730 | let openRefLink = function(event, paramId = ""){ 731 | 732 | let 主界面= window.parent.document 733 | let id = event?.currentTarget?.getAttribute("data-id") ?? paramId; 734 | // 处理笔记本等无法跳转的情况 735 | if (!isValidStr(id)) {return;} 736 | event?.preventDefault(); 737 | event?.stopPropagation(); 738 | let 虚拟链接 = 主界面.createElement("span") 739 | 虚拟链接.setAttribute("data-type","block-ref") 740 | 虚拟链接.setAttribute("data-id",id) 741 | 虚拟链接.style.display = "none";//不显示虚拟链接,防止视觉干扰 742 | let 临时目标 = 主界面.querySelector(".protyle-wysiwyg div[data-node-id] div[contenteditable]") 743 | 临时目标.appendChild(虚拟链接); 744 | let clickEvent = new MouseEvent("click", { 745 | ctrlKey: event?.ctrlKey, 746 | shiftKey: event?.shiftKey, 747 | altKey: event?.altKey, 748 | bubbles: true 749 | }); 750 | 虚拟链接.dispatchEvent(clickEvent); 751 | 虚拟链接.remove(); 752 | } 753 | 754 | function isValidStr(s){ 755 | if (s == undefined || s == null || s === '') { 756 | return false; 757 | } 758 | return true; 759 | } 760 | 761 | 762 | function dispatchKeyEvent(functionName) { 763 | let keyInit = parseHotKeyStr(window.top.siyuan.config.keymap.general[functionName].custom); 764 | keyInit["bubbles"] = true; 765 | let keydownEvent = new KeyboardEvent('keydown', keyInit); 766 | document.getElementsByTagName("body")[0].dispatchEvent(keydownEvent); 767 | let keyUpEvent = new KeyboardEvent('keyup', keyInit); 768 | document.getElementsByTagName("body")[0].dispatchEvent(keyUpEvent); 769 | } 770 | 771 | /** 772 | * 773 | * @param {*} hotkeyStr 思源hotkey格式 Refer: https://github.com/siyuan-note/siyuan/blob/d0f011b1a5b12e5546421f8bd442606bf0b5ad86/app/src/protyle/util/hotKey.ts#L4 774 | * @returns KeyboardEventInit Refer: https://developer.mozilla.org/zh-CN/docs/Web/API/KeyboardEvent/KeyboardEvent 775 | */ 776 | function parseHotKeyStr(hotkeyStr) { 777 | let result = { 778 | ctrlKey: false, 779 | altKey: false, 780 | metaKey: false, 781 | shiftKey: false, 782 | key: 'A', 783 | keyCode: 0 784 | } 785 | if (hotkeyStr == "" || hotkeyStr == undefined || hotkeyStr == null) { 786 | console.error("解析快捷键设置失败", hotkeyStr); 787 | throw new Error("解析快捷键设置失败"); 788 | } 789 | let onlyKey = hotkeyStr; 790 | if (hotkeyStr.indexOf("⌘") != -1) { 791 | result.ctrlKey = true; 792 | onlyKey = onlyKey.replace("⌘", ""); 793 | } 794 | if (hotkeyStr.indexOf("⌥") != -1) { 795 | result.altKey = true; 796 | onlyKey = onlyKey.replace("⌥", ""); 797 | } 798 | if (hotkeyStr.indexOf("⇧") != -1) { 799 | result.shiftKey = true; 800 | onlyKey = onlyKey.replace("⇧", ""); 801 | } 802 | // 未处理 windows btn (MetaKey) 803 | result.key = onlyKey; 804 | // 在https://github.com/siyuan-note/siyuan/commit/70acd57c4b4701b973a8ca93fadf6c003b24c789#diff-558f9f531a326d2fd53151e3fc250ac4bd545452ba782b0c7c18765a37a4e2cc 805 | // 更改中,思源改为使用keyCode判断快捷键按下事件,这里进行了对应的转换 806 | // 另请参考该提交中涉及的文件 807 | result.keyCode = keyCodeList[result.key]; 808 | console.assert(result.keyCode != undefined, `keyCode转换错误,key为${result.key}`); 809 | switch (result.key) { 810 | case "→": { 811 | result.key = "ArrowRight"; 812 | break; 813 | } 814 | case "←": { 815 | result.key = "ArrowLeft"; 816 | break; 817 | } 818 | case "↑": { 819 | result.key = "ArrowUp"; 820 | break; 821 | } 822 | case "↓": { 823 | result.key = "ArrowDown"; 824 | break; 825 | } 826 | case "⌦": { 827 | result.key = "Delete"; 828 | break; 829 | } 830 | case "⌫": { 831 | result.key = "Backspace"; 832 | break; 833 | } 834 | case "↩": { 835 | result.key = "Enter"; 836 | break; 837 | } 838 | } 839 | return result; 840 | } 841 | 842 | 843 | let zh_CN = { 844 | 845 | } 846 | 847 | let en_US = {} 848 | let language = zh_CN; 849 | 850 | /* **************** 设置项相关 ***************** 851 | * 852 | */ 853 | 854 | /** 855 | * 由需要的设置项生成设置页面 856 | * @param {*} settingObject 857 | */ 858 | function generateSettingPanelHTML(settingObjectArray) { 859 | let resultHTML = ""; 860 | for (let oneSettingProperty of settingObjectArray) { 861 | let inputElemStr = ""; 862 | oneSettingProperty.desp = oneSettingProperty.desp?.replace(new RegExp("", "g"), ""); 863 | if (oneSettingProperty.name.includes("🧪")) { 864 | oneSettingProperty.desp = language["setting_experimental"] + oneSettingProperty.desp; 865 | } 866 | let temp = ` 867 | 875 | `; 876 | switch (oneSettingProperty.type) { 877 | case "NUMBER": { 878 | let min = oneSettingProperty.limit[0]; 879 | let max = oneSettingProperty.limit[1]; 880 | inputElemStr = ``; 888 | break; 889 | } 890 | case "SELECT": { 891 | 892 | let optionStr = ""; 893 | for (let option of oneSettingProperty.limit) { 894 | let optionName = option.name; 895 | if (!optionName) { 896 | optionName = language[`setting_${oneSettingProperty.simpId}_option_${option.value}`]; 897 | } 898 | optionStr += ``; 902 | } 903 | inputElemStr = ``; 909 | break; 910 | } 911 | case "TEXT": { 912 | inputElemStr = ``; 913 | temp = ` 914 | ` 921 | break; 922 | } 923 | case "SWITCH": { 924 | inputElemStr = ` 929 | `; 930 | break; 931 | } 932 | case "TEXTAREA": { 933 | inputElemStr = ``; 937 | temp = ` 938 | ` 946 | break; 947 | } 948 | } 949 | 950 | resultHTML += temp.replace("*#*##*#*", inputElemStr); 951 | } 952 | // console.log(resultHTML); 953 | return resultHTML; 954 | } 955 | 956 | /** 957 | * 由配置文件读取配置 958 | */ 959 | function loadCacheSettings() { 960 | // 检索当前页面所有设置项元素 961 | 962 | } 963 | 964 | /** 965 | * 由设置界面读取配置 966 | */ 967 | function loadUISettings(formElement) { 968 | let data = new FormData(formElement); 969 | // 扫描标准元素 input[] 970 | let result = {}; 971 | for(const [key, value] of data.entries()) { 972 | // console.log(key, value); 973 | result[key] = value; 974 | if (value === "on") { 975 | result[key] = true; 976 | }else if (value === "null" || value == "false") { 977 | result[key] = ""; 978 | } 979 | } 980 | let checkboxes = formElement.querySelectorAll('input[type="checkbox"]'); 981 | for (let i = 0; i < checkboxes.length; i++) { 982 | let checkbox = checkboxes[i]; 983 | // console.log(checkbox, checkbox.name, data[checkbox.name], checkbox.name); 984 | if (result[checkbox.name] == undefined) { 985 | result[checkbox.name] = false; 986 | } 987 | } 988 | 989 | let numbers = formElement.querySelectorAll("input[type='number']"); 990 | // console.log(numbers); 991 | for (let number of numbers) { 992 | let minValue = number.getAttribute("min"); 993 | let maxValue = number.getAttribute("max"); 994 | let value = parseFloat(number.value); 995 | 996 | if (minValue !== null && value < parseFloat(minValue)) { 997 | number.value = minValue; 998 | result[number.name] = parseFloat(minValue); 999 | } else if (maxValue !== null && value > parseFloat(maxValue)) { 1000 | number.value = maxValue; 1001 | result[number.name] = parseFloat(maxValue); 1002 | } else { 1003 | result[number.name] = value; 1004 | } 1005 | } 1006 | 1007 | debugPush("UI SETTING", result); 1008 | return result; 1009 | } 1010 | 1011 | function isMobile() { 1012 | return window.top.document.getElementById("sidebar") ? true : false; 1013 | }; 1014 | 1015 | 1016 | const keyCodeList = { 1017 | "⌫": 8, 1018 | "⇥": 9, 1019 | "↩": 13, 1020 | "⇧": 16, 1021 | "⌘": 91, 1022 | "⌥": 18, 1023 | "Pause": 19, 1024 | "CapsLock": 20, 1025 | "Escape": 27, 1026 | " ": 32, 1027 | "PageUp": 33, 1028 | "PageDown": 34, 1029 | "End": 35, 1030 | "Home": 36, 1031 | "←": 37, 1032 | "↑": 38, 1033 | "→": 39, 1034 | "↓": 40, 1035 | "PrintScreen": 44, 1036 | "Insert": 45, 1037 | "⌦": 46, 1038 | "0": 48, 1039 | "1": 49, 1040 | "2": 50, 1041 | "3": 51, 1042 | "4": 52, 1043 | "5": 53, 1044 | "6": 54, 1045 | "7": 55, 1046 | "8": 56, 1047 | "9": 57, 1048 | "A": 65, 1049 | "B": 66, 1050 | "C": 67, 1051 | "D": 68, 1052 | "E": 69, 1053 | "F": 70, 1054 | "G": 71, 1055 | "H": 72, 1056 | "I": 73, 1057 | "J": 74, 1058 | "K": 75, 1059 | "L": 76, 1060 | "M": 77, 1061 | "N": 78, 1062 | "O": 79, 1063 | "P": 80, 1064 | "Q": 81, 1065 | "R": 82, 1066 | "S": 83, 1067 | "T": 84, 1068 | "U": 85, 1069 | "V": 86, 1070 | "W": 87, 1071 | "X": 88, 1072 | "Y": 89, 1073 | "Z": 90, 1074 | "ContextMenu": 93, 1075 | "MyComputer": 182, 1076 | "MyCalculator": 183, 1077 | ";": 186, 1078 | "=": 187, 1079 | ",": 188, 1080 | "-": 189, 1081 | ".": 190, 1082 | "/": 191, 1083 | "`": 192, 1084 | "[": 219, 1085 | "\\": 220, 1086 | "]": 221, 1087 | "'": 222, 1088 | "*": 106, 1089 | "+": 107, 1090 | "-": 109, 1091 | ".": 110, 1092 | "/": 111, 1093 | "F1": 112, 1094 | "F2": 113, 1095 | "F3": 114, 1096 | "F4": 115, 1097 | "F5": 116, 1098 | "F6": 117, 1099 | "F7": 118, 1100 | "F8": 119, 1101 | "F9": 120, 1102 | "F10": 121, 1103 | "F11": 122, 1104 | "F12": 123, 1105 | "NumLock": 144, 1106 | "ScrollLock": 145 1107 | }; 1108 | 1109 | module.exports = { 1110 | default: FileLostAlarmerPlugin, 1111 | }; --------------------------------------------------------------------------------