├── .gitignore ├── ChangeLog.md ├── LICENSE ├── README.md ├── README_en.md ├── docs ├── bookmarks.svg ├── chrome_bookmarks.png └── lang_pak.txt ├── screenshots ├── 1-popup.png ├── 4-duolie.png ├── 5-options.png ├── README.md ├── bookmarks_demo.html └── en │ ├── 1-popup.png │ ├── 4-duolie.png │ └── 5-options.png ├── src ├── _locales │ ├── ar │ │ └── messages.json │ ├── de │ │ └── messages.json │ ├── en │ │ └── messages.json │ ├── es │ │ └── messages.json │ ├── fr │ │ └── messages.json │ ├── it │ │ └── messages.json │ ├── ja │ │ └── messages.json │ ├── ko │ │ └── messages.json │ ├── pt_BR │ │ └── messages.json │ ├── pt_PT │ │ └── messages.json │ ├── ru │ │ └── messages.json │ ├── zh_CN │ │ └── messages.json │ └── zh_TW │ │ └── messages.json ├── css │ ├── common.css │ ├── configTable.css │ ├── markdown.css │ ├── options.css │ └── popup.css ├── eventPage.js ├── icons │ ├── edit.svg │ ├── favicon │ │ ├── folder.png │ │ └── js.png │ ├── i.svg │ ├── icon128.png │ ├── icon16.png │ ├── icon32.png │ ├── icon48.png │ ├── list.svg │ ├── manager.svg │ ├── options.svg │ ├── undo.svg │ └── usage.svg ├── js │ ├── changeTabByHash.js │ ├── config.js │ ├── configTable.js │ ├── functions.js │ ├── loadmd.js │ ├── options.js │ └── popup.js ├── libs │ ├── dragula.css │ ├── dragula.min.js │ └── marked.min.js ├── manifest.json ├── md │ ├── usage-en.md │ └── usage-zh.md ├── options.html └── popup.html └── zip.bat /.gitignore: -------------------------------------------------------------------------------- 1 | /release/ 2 | /build/ 3 | *.zip 4 | TODO.md 5 | -------------------------------------------------------------------------------- /ChangeLog.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | *All notable changes to this project will be documented in this file.* 4 | 5 | **Ease Bookmarks**,简单易用的书签管理器 6 | 7 | 简要说明: 8 | Ease Bookmarks 是一款为了替代浏览器原有书签栏的扩展 9 | 在此基础上,尽可能适应不同用户的书签使用习惯,同时保持简单性 10 | 11 | ## 主要功能 12 | 13 | - 修改书签的默认打开方式 14 | - 对书签的各种基本操作(编辑、删除、移动、搜索等) 15 | - 书签多列显示 16 | - 快捷键支持 17 | - 另外,本扩展对 `JS 小书签` 进行了特别支持~ 18 | 19 | ## 更新日志 20 | > v3.1.4 & v2.1.4(mv2)(2025-04-30) 21 | Improve: 22 | 鼠标中键快速添加书签 23 | 24 | > v3.1.3 & v2.1.3(mv2)(2025-04-12) 25 | Improve: 26 | 取消选中快捷键(空格键)逻辑优化 27 | 自定义图标 允许上传所有格式的图片 28 | 重置配置时 还原图标 29 | 选项页刷新后 保持滚动位置(firefox兼容) 30 | 31 | > v3.1.2 & v2.1.2(mv2)(2025-04-06) 32 | Improve: 33 | UI和配色调整 34 | 代码优化 35 | 36 | > v3.1.1 & v2.1.1(mv2)(2025-03-28) 37 | Improve: 38 | 样式、文档调整 39 | 代码优化 40 | 41 | > v3.0.6 & v2.0.6(mv2)(2025-03-13) 42 | Improve: 43 | 书签管理器图标 对齐 44 | 代码优化 45 | 46 | > v3.0.5 & v2.0.3(mv2)(2025-02-18) 47 | Improve: 48 | 搜索结果按名称排序 49 | 配置的导出与导入 50 | 51 | > 3.0.1(2025-02-05) 52 | Bugs: 53 | 修复从上次目录启动失效(#37) 54 | 55 | > 3.0.0(2024-12-28) 56 | 迁移到 mv3 57 | 注: chrome 最低版本 123;mv3 不支持 js 小书签 58 | 59 | > 1.7.7(2024-09-29) 60 | Bugs: 61 | 搜索时 输入法空格合成文字与空格取消选择冲突 62 | 注: 应该是最后一个mv2版本了 63 | 64 | > 1.7.6(2024-06-05) 65 | Improve: 66 | 文件夹空白处也显示右键菜单 67 | 支持最外层的navigator.clipboard.writeText(#24) 68 | 文件夹删除的confirm 改为模拟实现 69 | 其他: 70 | 操作优化;样式调整 71 | 72 | > 1.7.3(2024-05-25) 73 | Bugs: 74 | 自定义css报错,不显示(#26) 75 | 其他: 76 | 优化翻译;样式调整 77 | 78 | > 1.7.2(2024-05-15) 79 | 功能: 80 | 恢复上次搜索关键字(内置参数 keepLastSearchValue: 0 -> 1) 81 | 自定义css 热更新 82 | 支持葡萄牙语、意大利语 83 | Bugs: 84 | 搜索结果布局显示错误 85 | 其他: 86 | 升级库marked.min.js;代码优化 87 | 88 | > 1.7.1(2024-05-04) 89 | 功能: 90 | (调整)交换Ctrl与Shift键的功能,与浏览器保持一致(#20) 91 | 支持横向滚动 92 | 支持阿拉伯语 93 | 修改内置参数的默认值(keepMaxCols:0 -> 1) 94 | 其他: 95 | 隐藏超长路径时body的滚动条;代码优化 96 | 97 | > 1.6.6(2024-04-05) 98 | 功能: 99 | 增加配置参数openBookmarkAfterCurrentTab 100 | 参数说明:https://github.com/qinxs/Ease-Bookmarks#内置参数 101 | Bugs: 102 | 打开所在目录可能的undefined;空白标题时更新dom视图 103 | 其他: 104 | 黑暗模式样式优化,pop页面操作和事件优化 105 | 106 | > 1.6.5(2024-02-19) 107 | 功能: 108 | 增加一些配置参数,允许修改多列布局时的popup窗口宽度等 109 | bodyWidth_*、compositionEvent、hotkeyDelete、hotkeyCancelSeleted 110 | 参数说明:https://github.com/qinxs/Ease-Bookmarks#内置参数 111 | Bugs: 112 | 打开文件夹时可能的跳动 113 | 其他: 114 | 精简代码,样式微调 115 | 116 | > 1.6.2(2023-05-23) 117 | 功能: 118 | 增加内置参数修改页面 119 | Bugs: 120 | 兼容Vivaldi 121 | 其他语言输入字母后实时搜索(仅中文合成后搜索 #16) 122 | 其他: 123 | 从搜索结果打开目录 返回时 保持搜索视图 124 | 代码重构优化;样式调整 125 | 126 | > 1.6.0(2023-04-27) 127 | 优化: 128 | UI及配色调整优化 129 | 搜索结果,默认选中第一条 130 | 拖拽排序改为需要拖拽favicon图标 131 | 最大布局列数改为8(原来为5) 132 | 其他: 133 | 代码优化;重新聚焦搜索框;增加西班牙语 134 | 135 | > 1.5.6(2023-03-30) 136 | 功能: 137 | 双击header区域空白处(或者末级文件夹) 返回上级文件夹 138 | Bugs: 139 | 正在输入中文时,不触发按键事件 140 | 141 | > 1.5.5(2023-03-04) 142 | Bugs: 143 | favicon图标高分屏显示模糊 144 | 145 | > 1.5.4(2022-12-15) 146 | 功能: 147 | 每列条数默认值改为10;样式调整 148 | 其他: 149 | 代码优化 150 | 151 | > 1.5.3(2022-11-30) 152 | 功能: 153 | 快捷键增加F2编辑;优化书签编辑框 154 | 菜单 `更新为当前网址` 允许同时更新title(需在控制台手动开启) 155 | 其他: 156 | 代码优化;增加日语、韩语、俄语、德语、法语 157 | 158 | > 1.3.9(2022-11-22) 159 | 功能: 160 | 支持自定义图标; footer区域增加"书签管理器" 161 | 162 | > 1.3.7(2022-11-10) 163 | Bugs: 164 | Edge中footer下空白 165 | 其他: 166 | (扩展本身)右键菜单里增加"书签管理器" 167 | 当前标签页打开书签后 也自动关闭popup窗口 168 | 169 | > 1.3.6(2022-02-17) 170 | Bugs: 171 | 不能在linux里安装 172 | 其他: 173 | 增加常见问题链接 174 | 175 | > 1.3.5(2022-02-11) 176 | Bugs: 177 | 退出搜索视图后 恢复原来的滚动条位置 178 | 其他: 179 | 改为后台运行; 更新拖拽库; 细节优化 180 | 181 | > 1.3.4(2022-02-05) 182 | Bugs: 183 | 个别js小书签解码报错; 极端情况下 右键菜单导致多余的滚动条 184 | 其他: 185 | 主题色默认色改为浅色; 启动速度优化、性能优化 186 | 187 | > 1.3.3(2022-01-28) 188 | Bugs: 189 | 极端情况下的footer显示错误 190 | 其他: 191 | 快捷键调整; 代码优化、重构 192 | 193 | > 1.3.1(2022-01-27) 194 | 功能: 195 | 支持快捷键操作; 支持取消保持最大宽度 196 | 197 | > 1.3.0(2022-01-24) 198 | 功能: 199 | 支持从任意目录启动 200 | Bugs: 201 | 自定义样式不能换行(导致页面空白) 202 | 其他: 203 | 代码优化 204 | 205 | > 1.2.6(2022-01-21) 206 | Bugs: 207 | 取消拖拽移动时 可能导致的卡死问题!!! 208 | 单个书签被删除后 不能直接创建书签/文件夹 209 | 其他: 210 | 右键菜单样式调整 211 | 212 | > 1.2.5(2022-01-19) 213 | 功能: 214 | 增加文件夹菜单 打开全部 215 | Bugs: 216 | 拖拽排序支持自动滚动;重复的handleFolderEvent事件 217 | 其他: 218 | 优化启动速度;优化dom结构 219 | 220 | > 1.2.3(2022-01-12) 221 | Bugs: 222 | 有滚动条时 在顶部打开右键菜单被遮挡 223 | 其他: 224 | 优化删除文件夹逻辑;优化深色模式样式 225 | 226 | > 1.2.2(2022-01-10) 227 | 功能: 228 | 从上次关闭位置启动;支持鼠标中键后台打开标签 229 | Bugs: 230 | 打开文件夹时 滚动条置顶;小书签包含 %,解码失败(URIError: URI malformed) 231 | 其他: 232 | 增加语言中文繁体;增加英文使用说明 233 | 234 | > 1.2.1(2022-01-06) 235 | 功能: 236 | 搜索结果title 显示路径 237 | 其他: 238 | 启动优化;部分代码重构 239 | 240 | > 1.2.0(2022-01-02) 241 | 功能: 242 | 搜索结果增加定位所在目录;鼠标中键添加当前页为书签 243 | Bugs: 244 | 新建空名字文件夹 不能操作 245 | 246 | > 1.1.0(2021-12-15) 247 | 功能: 248 | 增加新建书签、新建文件夹;优化多列布局 249 | Bugs: 250 | 修复书签不能移动到末尾位置; 调整深色模式footer颜色 251 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 qinxs 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 | [//]: # 2 |
3 | 4 |
5 | 简 单 易 用 的 书 签 管 理 器 6 |

Ease Bookmarks

7 | 8 | 9 |
10 | Source Code 11 | Chrome Web Store 12 | mv2 13 | Microsoft Edge Add-on 14 | Firefox Add-on 15 | Donate 16 |

17 |
18 | 19 | Ease Bookmarks 是一款为了替代浏览器原有书签栏的扩展 20 | 21 | 在此基础上,尽可能适应不同用户的书签使用习惯,同时保持简单性 22 | 23 | ![1-popup.png](./screenshots/1-popup.png) 24 | 25 | ## 主要功能 26 | 27 | 修改书签的默认打开方式 28 | 29 | 对书签的各种基本操作(编辑、删除、移动、搜索等) 30 | 31 | 书签多列显示 32 | 33 | 快捷键支持 34 | 35 | 另外,本扩展对 `JS 小书签` 进行了特别支持~ 36 | 37 | > 更新日志:[ChangeLog.md](ChangeLog.md) 38 | > 39 | > 查看所有截图:[Screenshots](./screenshots/README.md#所有截图) 40 | > 41 | > 常见问题:[FAQ](https://github.com/qinxs/Ease-Bookmarks/wiki/常见问题(FAQ)) 42 | 43 | ## 快捷键 44 | 45 | ### 打开/关闭 本扩展 46 | 47 | 默认快捷键是 `Ctrl + Q`,你可以在如下管理页面进行修改: 48 | - **Chrome**:`chrome://extensions/shortcuts` 49 | - **Edge**:`edge://extensions/shortcuts` 50 | - **Firefox**:`about:addons` `设置图标` `管理扩展快捷键` 51 | 52 | ### 功能键 53 | 54 | - `↑`、`↓`、`←`、`→`、`Home`、`End`:选择/切换 书签 55 | - `Enter`:打开选中的 书签/目录 56 | - `Space`:取消选中 57 | - `F2`:编辑 书签/目录(`Enter` 保存;`Esc`、`F2` 取消) 58 | - `Tab`:返回上一级目录 59 | - `Ctrl + Z`:切换到 书签栏/其他书签 60 | - `Ctrl + F`:激活搜索框 61 | - `Esc`:清除搜索框内容;关闭页面 62 | 63 | ### 修饰键 64 | 65 | - `Ctrl`:是否在后台打开页面 66 | - `Shift`:在 当前标签/新标签 打开页面 67 | 68 | ## 自定义 69 | 70 | - 别名(书签栏和其他书签,其他语言可能会需要) 71 | - 自定义样式(popup 页面,DOM 结构可在 header 区域 `右键 -> 检查` 查看) 72 | 73 | ## 内置参数 74 | 75 | 用于 开关/修改 本插件内的小众功能(或者浏览器本身的默认行为) 76 | 77 | 在该页面(`/options.html#configTable`)修改 78 | 79 | 80 | | 字段 / 功能 | 说明 | 81 | | ------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | 82 | | `bodyWidth_*`
修改多列布局时的popup窗口宽度 | 由于Chrome对扩展的限制,最大有效值为800px | 83 | | `compositionEvent`
是否开启搜索输入合成事件 | - `0`:不启用(默认)
- `1`:启用
注:中文输入法强制开启 | 84 | | `fastCreate`
中键点击书签favicon图标快速添加书签 | - `0`:禁用(默认)
- `1`:将书签添加到此位置后
- `2`:与1一样,但如果点击的是文件夹,则添加到文件夹内
(详见 [#15][issues-15]) | 85 | | `faviconAPI`
[ firefox only ] 获取 favicon 图标 API | 支持的占位符 {hostname} {origin}
([ 详细用法 ][issues-firefox]) | 86 | | `hotkeyCancelSeleted`
取消选择快捷键 | - `Space`:空格键,开启(默认)
- `-Space`:关闭
其他`hotkey*`快捷键同理(键值前加`-`表示关闭)
[快捷键键值查询][keycode](使用event.code值) | 87 | | `hotkeyDelete`
删除书签快捷键 | - `-Delete`:删除键,关闭(默认) | 88 | | `keepLastSearchValue`
恢复上次搜索关键字 | - `0`:不恢复(默认)
- `1`:打开 popup 窗口时,恢复上次的搜索关键字和搜索结果 | 89 | | `keepMaxCols`
页面保持最大宽度 | - `0`:页面宽度随内容变化
- `1`:保持最大打开宽度(默认,避免切换文件夹时页面跳动) | 90 | | `openBookmarkAfterCurrentTab`
在当前标签页右侧打开书签 | - `0`:禁用(默认)
- `1`:启用(仅通过本扩展打开时有效) | 91 | | `searchResultSort`
搜索结果排序 | - `1`:按名称升序(默认)
- `0`:不改变,与 `chrome://bookmarks/` 页面搜索一致
- `-1`:按名称降序 | 92 | | `updateBookmarkOpt`
菜单「更新为当前网址」的更新选项 | - `1`:仅更新URL(默认)
- `2`:同时更新URL和标题 | 93 | 94 | 95 | ## 翻译 96 | *通过以下方式翻译,如有不当,请提 [issue][issues-page] 指明,感谢~* 97 | 98 | - `chrome://bookmarks/` -> `F12`,[参考](docs/chrome_bookmarks.png) 99 | > `chrome://bookmarks/strings.m.js` 获得翻译字符串 100 | - Chrome 的 pak 文件,路径`Chromium\94.0.4606.81\Locales` 101 | 102 | > 使用 `ChromePAK解包打包工具.exe` 解包搜索对比 103 | - [Microsoft Translator](https://cn.bing.com/translator),并用其他翻译验证 104 | 105 | ## 第三方库 106 | 107 | [dragula.js](https://github.com/bevacqua/dragula)(进行了细微调整 [改动内容](https://github.com/qinxs/dragula2)) 108 | 109 | [marked](https://github.com/markedjs/marked)(动态渲染 markdown 文件) 110 | 111 | ## License 112 | 113 | [MIT](LICENSE) 114 | 115 | [issues-page]: https://github.com/qinxs/Ease-Bookmarks/issues 116 | [issues-15]: https://github.com/qinxs/Ease-Bookmarks/issues/15 117 | [issues-firefox]: https://github.com/qinxs/Ease-Bookmarks/issues/42 118 | [keycode]: https://www.toptal.com/developers/keycode 119 | -------------------------------------------------------------------------------- /README_en.md: -------------------------------------------------------------------------------- 1 | [//]: # 2 |
3 | 4 |
5 | A Simple and Easy-to-Use Bookmarks Manager 6 |

Ease Bookmarks

7 | 8 | 9 |
10 | Source Code 11 | Chrome Web Store 12 | mv2 13 | Microsoft Edge Add-on 14 | Firefox Add-on 15 | Donate 16 |

17 |
18 | 19 | Ease Bookmarks is a browser extension designed to replace the native bookmarks bar. 20 | 21 | It aims to accommodate the bookmark usage habits of various users while maintaining simplicity. 22 | 23 | ![1-popup.png](./screenshots/en/1-popup.png) 24 | 25 | ## Key Features 26 | 27 | Modify the default opening behavior of bookmarks 28 | 29 | Perform basic operations on bookmarks (edit, delete, move, search, etc.) 30 | 31 | Multi-column display for bookmarks 32 | 33 | Keyboard shortcut support 34 | 35 | Special support for `JS bookmarklets` 36 | 37 | > Changelog: [ChangeLog.md](ChangeLog.md) 38 | > 39 | > View all screenshots: [Screenshots](./screenshots/README.md#所有截图) 40 | > 41 | > FAQs: [FAQ](https://github.com/qinxs/Ease-Bookmarks/wiki/常见问题(FAQ)) 42 | 43 | ## Keyboard Shortcuts 44 | 45 | ### Turn this extension on/off 46 | 47 | The default shortcut is `Ctrl + Q`, You can modify it via: 48 | - **Chrome**: `chrome://extensions/shortcuts` 49 | - **Edge**: `edge://extensions/shortcuts` 50 | - **Firefox**: `about:addons` `Settings Icon` `Manage Extension Shortcuts` 51 | 52 | ### Functional keys 53 | 54 | - `↑`, `↓`, `←`, `→`, `Home`, `End`: select/toggle bookmarks 55 | - `Enter`: open the selected bookmark/folder 56 | - `Space`: cancel selection 57 | - `F2`: edit bookmark/folder (Enter to save; Esc/F2 to cancel) 58 | - `Tab`: go back to the previous folder 59 | - `Ctrl + Z`: switch between "Bookmarks bar" and "Other bookmarks" 60 | - `Ctrl + F`: activate the search box 61 | - `Esc`: clear the search box content; close the page 62 | 63 | ### Modifier keys 64 | 65 | - `Ctrl`: whether to open the page in the background 66 | - `Shift`: open page in current tab/new tab 67 | 68 | ## Customize 69 | 70 | - Aliases (bookmarks bar and other bookmarks, may be required for other languages) 71 | - Custom style (`popup` page, DOM structure can be viewed in the header area `Right click -> Inspect`) 72 | 73 | ## Built-in Parameters 74 | 75 | Used to switch/adjust niche features within this plugin (or the default behavior of the browser itself) 76 | 77 | Configure at (`/options.html#configTable`) 78 | 79 | 80 | | Parameter / Feature | Description | 81 | | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | 82 | | `bodyWidth_*`
Adjust popup width for multi-column layout | Maximum effective value is 800px due to Chrome extension limits | 83 | | `compositionEvent`
Enable composition event for search input | - `0`: Disabled (default)
- `1`: Enabled
Note: Always enabled for Chinese IME | 84 | | `fastCreate`
Middle-click favicon to quickly add bookmark | - `0`: Disabled (default)
- `1`: Add bookmark here
- `2`: Same as 1, but if a folder is clicked, it is added to the folder
(more details [#15][issues-15]) | 85 | | `faviconAPI`
[ firefox only ] Favicon fetching API | Supported placeholders: {hostname} {origin}
([ more details ][issues-firefox]) | 86 | | `hotkeyCancelSeleted`
Deselect hotkey | - `Space`: Space key, on (default)
- `-Space`: Disable
Other `hotkey*` params follow same pattern (prepend - to disable key)
[JavaScript Key Code][keycode](Use the event.code value) | 87 | | `hotkeyDelete`
Delete bookmark hotkey | - `-Delete`: Delete key, off (default) | 88 | | `keepLastSearchValue`
Restore last search keywords | - `0`: Disabled (default)
- `1`: When openning the popup window, the last search keywords and search results are restored. | 89 | | `keepMaxCols`
Maintain maximum popup width | - `0`: Dynamic width
- `1`: Maintain maximum opening width (default, prevents layout jumps) | 90 | | `openBookmarkAfterCurrentTab`
Open bookmarks after current tab | - `0`: Disabled (default)
- `1`: Enabled (only works when opened via this extension) | 91 | | `searchResultSort`
Search result sorting | - `1`: Name ascending (default)
- `0`: Native order, Same as searching in `chrome://bookmarks/`
- `-1`: Name descending | 92 | | `updateBookmarkOpt`
"Update to Current URL" menu behavior | - `1`: Update URL only (default)
- `2`: Update both URL and title | 93 | 94 | 95 | ## Localization 96 | *Translated in the following way, if there is any inaccuracy, please create [issue][issues-page] to point it out, thank you~* 97 | 98 | - `chrome://bookmarks/` -> `F12`,[reference](docs/chrome_bookmarks.png) 99 | > Access `chrome://bookmarks/strings.m.js` for translation strings 100 | - Chrome PAK files, path: `Chromium\94.0.4606.81\Locales` 101 | 102 | > Use `ChromePAK解包打包工具.exe` to unpack and search 103 | - [Microsoft Translator](https://cn.bing.com/translator), and verified with other tools 104 | 105 | ## Third-Party Libraries 106 | 107 | [dragula.js](https://github.com/bevacqua/dragula) (with minor adjustments [here](https://github.com/qinxs/dragula2)) 108 | 109 | [marked](https://github.com/markedjs/marked) (dynamic markdown rendering) 110 | 111 | ## License 112 | 113 | [MIT](LICENSE) 114 | 115 | [issues-page]: https://github.com/qinxs/Ease-Bookmarks/issues 116 | [issues-15]: https://github.com/qinxs/Ease-Bookmarks/issues/15 117 | [issues-firefox]: https://github.com/qinxs/Ease-Bookmarks/issues/42 118 | [keycode]: https://www.toptal.com/developers/keycode 119 | -------------------------------------------------------------------------------- /docs/bookmarks.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/chrome_bookmarks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qinxs/Ease-Bookmarks/c33aad0bc766f36a34eb615e0c18de64e358da08/docs/chrome_bookmarks.png -------------------------------------------------------------------------------- /docs/lang_pak.txt: -------------------------------------------------------------------------------- 1 | 翻译字符串(来至 Chromium 的 pak 包) 2 | 3 | 328 书签管理器(&B) 4 | 5 | 873 全部打开 6 | 7 | - 211 关闭 8 | 9 | - 5741 重置 10 | 11 | - 5831 选择文件 12 | 13 | - 6413 例如:1-5、8、11-13 14 | 15 | - 179 新标签页 16 | 17 | // 以后改成从书签管理器页面获取 18 | 5925 保存 19 | 20 | 187 取消 21 | 6919 22 | 23 | 199 名称 24 | 25 | - 200 网址 26 | -------------------------------------------------------------------------------- /screenshots/1-popup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qinxs/Ease-Bookmarks/c33aad0bc766f36a34eb615e0c18de64e358da08/screenshots/1-popup.png -------------------------------------------------------------------------------- /screenshots/4-duolie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qinxs/Ease-Bookmarks/c33aad0bc766f36a34eb615e0c18de64e358da08/screenshots/4-duolie.png -------------------------------------------------------------------------------- /screenshots/5-options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qinxs/Ease-Bookmarks/c33aad0bc766f36a34eb615e0c18de64e358da08/screenshots/5-options.png -------------------------------------------------------------------------------- /screenshots/README.md: -------------------------------------------------------------------------------- 1 | # 截图说明 2 | 3 | ## Chrome Web Store 要求 4 | 5 | 不得多于 5 张,不得少于 1 张 6 | 7 | 1280x800 或 640x400 8 | 9 | JPEG 或 24 位 PNG(无 alpha 透明层) 10 | 11 | ## 如何操作 12 | 13 | 暂时关闭其他扩展 14 | 15 | 把窗口大小调整到 1280x800(或者相似比例,如:1152x720) 16 | 17 | 然后依次截图 18 | 19 | 最后用 PS 调整大小 20 | 21 | ## 所有截图 22 | 23 | ![1-popup.png](1-popup.png) 24 | 25 | ![4-duolie.png](4-duolie.png) 26 | 27 | ![5-options.png](5-options.png) 28 | -------------------------------------------------------------------------------- /screenshots/en/1-popup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qinxs/Ease-Bookmarks/c33aad0bc766f36a34eb615e0c18de64e358da08/screenshots/en/1-popup.png -------------------------------------------------------------------------------- /screenshots/en/4-duolie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qinxs/Ease-Bookmarks/c33aad0bc766f36a34eb615e0c18de64e358da08/screenshots/en/4-duolie.png -------------------------------------------------------------------------------- /screenshots/en/5-options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qinxs/Ease-Bookmarks/c33aad0bc766f36a34eb615e0c18de64e358da08/screenshots/en/5-options.png -------------------------------------------------------------------------------- /src/_locales/ar/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginDesc": 3 | { 4 | "message": "Easy to use bookmark manager" 5 | }, 6 | 7 | "bookmarksManager": 8 | { 9 | "message": "م&دير الإشارات" 10 | }, 11 | 12 | "searchPlaceholder": 13 | { 14 | "message": "الإشارات المرجعية" 15 | }, 16 | "noBookmarksTip": 17 | { 18 | "message": "هذا المجلد فارغ" 19 | }, 20 | "noSearchResultsTip": 21 | { 22 | "message": "لم يتم العثور على أي نتائج بحث" 23 | }, 24 | 25 | "openAll": 26 | { 27 | "message": "فتح الكل" 28 | }, 29 | "openInNewTab": 30 | { 31 | "message": "الفتح في علامة تبويب جديدة" 32 | }, 33 | "openInBackgroundTab": 34 | { 35 | "message": "فتح في علامة تبويب في الخلفية" 36 | }, 37 | "openInIncognitoWindow": 38 | { 39 | "message": "فتح في نافذة للتصفُّح المتخفي" 40 | }, 41 | "addBookmark": 42 | { 43 | "message": "إضافة إشارة مرجعية جديدة" 44 | }, 45 | "addFolder": 46 | { 47 | "message": "إضافة مجلد جديد" 48 | }, 49 | "showInFolder": 50 | { 51 | "message": "العرض في المجلد" 52 | }, 53 | "setAsStartupFolder": 54 | { 55 | "message": "تعيين كدليل بدء التشغيل" 56 | }, 57 | "updateToCurrentURL": 58 | { 59 | "message": "تم التحديث إلى عنوان URL الحالي" 60 | }, 61 | "edit": 62 | { 63 | "message": "تعديل" 64 | }, 65 | "delete": 66 | { 67 | "message": "حذف" 68 | }, 69 | "deleteFolderConfirm": 70 | { 71 | "message": "سيؤدي هذا أيضًا إلى إزالة جميع الإشارات المرجعية ($1) في هذا مجلد، حسنًا؟", 72 | "placeholders": { 73 | "bookmarksNum": { 74 | "content": "$1" 75 | } 76 | } 77 | }, 78 | "rename": 79 | { 80 | "message": "إعادة تسمية" 81 | }, 82 | 83 | "editBookmark": 84 | { 85 | "message": "تعديل الإشارة المرجعية" 86 | }, 87 | "editFolderName": 88 | { 89 | "message": "إعادة تسمية المجلد" 90 | }, 91 | "save": 92 | { 93 | "message": "حفظ" 94 | }, 95 | "cancel": 96 | { 97 | "message": "إلغاء" 98 | }, 99 | "name": 100 | { 101 | "message": "الاسم" 102 | }, 103 | "URL": 104 | { 105 | "message": "‏عنوان URL" 106 | }, 107 | 108 | "themeColor": 109 | { 110 | "message": "Theme color:" 111 | }, 112 | "auto": 113 | { 114 | "message": "Auto" 115 | }, 116 | "light": 117 | { 118 | "message": "Light" 119 | }, 120 | "dark": 121 | { 122 | "message": "Dark" 123 | }, 124 | "openIn": 125 | { 126 | "message": "Open bookmarks in:" 127 | }, 128 | "currentTab": 129 | { 130 | "message": "Current tab" 131 | }, 132 | "newTab": 133 | { 134 | "message": "New tab" 135 | }, 136 | "backgroundTab": 137 | { 138 | "message": "Background tab" 139 | }, 140 | "hoverEnter": { 141 | "message": "Hover to open folder:" 142 | }, 143 | "off": { 144 | "message": "Off" 145 | }, 146 | "slow": { 147 | "message": "Slow" 148 | }, 149 | "moderate": { 150 | "message": "Moderate" 151 | }, 152 | "fast": { 153 | "message": "Fast" 154 | }, 155 | "startupFolder": { 156 | "message": "Startup folder:" 157 | }, 158 | "folderXTitle": { 159 | "message": "Please use the folder right-click menu to set or change startup from a subdirectory..." 160 | }, 161 | "lastFolder": { 162 | "message": "Last folder" 163 | }, 164 | "lastFolderAndScrollBar": { 165 | "message": "Last folder and scrollbar" 166 | }, 167 | "scrollDirection": { 168 | "message": "Scroll Direction:" 169 | }, 170 | "scrollDirectionX": { 171 | "message": "Horizontal" 172 | }, 173 | "scrollDirectionY": { 174 | "message": "Vertical" 175 | }, 176 | "layoutCols": { 177 | "message": "Layout column(s):" 178 | }, 179 | "minItemsEachCol": { 180 | "message": "Items each column:" 181 | }, 182 | "minItemsEachColTips": { 183 | "message": "Note: valid when the number of Layout columns is multiple.\n1 or 10 is recommended" 184 | }, 185 | "alias": { 186 | "message": "Alias:" 187 | }, 188 | "customIcon": { 189 | "message": "Custom icon:" 190 | }, 191 | "selectFile": { 192 | "message": "Select file" 193 | }, 194 | "reset": { 195 | "message": "Reset" 196 | }, 197 | "customIconTip": { 198 | "message": "Note: Please select square, transparent-background image (png format)" 199 | }, 200 | "customCSS": { 201 | "message": "Custom style:" 202 | }, 203 | "example": { 204 | "message": "For example:" 205 | }, 206 | "BackupAndRecovery": { 207 | "message": "Backup and Recovery:" 208 | }, 209 | "import": { 210 | "message": "Import" 211 | }, 212 | "export": { 213 | "message": "Export" 214 | }, 215 | "resetConfigTip": { 216 | "message": "This will clear all configurations, are you sure?" 217 | }, 218 | "setInvalidTips": { 219 | "message": "Invalid option! Optional Settings:" 220 | }, 221 | "Built_inVariables": { 222 | "message": "Built-in variables" 223 | }, 224 | "Built_inVariablesTip": { 225 | "message": "Note: Used to switch/modify minor features within this plugin (or the default behavior of the browser itself)" 226 | } 227 | } -------------------------------------------------------------------------------- /src/_locales/de/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginDesc": 3 | { 4 | "message": "Einfacher und benutzerfreundlicher Lesezeichen-Manager" 5 | }, 6 | 7 | "bookmarksManager": 8 | { 9 | "message": "Lesezeichen-Manager" 10 | }, 11 | 12 | "searchPlaceholder": 13 | { 14 | "message": "In Lesezeichen suchen..." 15 | }, 16 | "noBookmarksTip": 17 | { 18 | "message": "Dieser Ordner ist leer" 19 | }, 20 | "noSearchResultsTip": 21 | { 22 | "message": "Keine Suchergebnisse gefunden" 23 | }, 24 | 25 | "openAll": 26 | { 27 | "message": "Alle öffnen" 28 | }, 29 | "openInNewTab": 30 | { 31 | "message": "In neuem Tab öffnen" 32 | }, 33 | "openInBackgroundTab": 34 | { 35 | "message": "Registerkarte Im Hintergrund öffnen" 36 | }, 37 | "openInIncognitoWindow": 38 | { 39 | "message": "In Inkognitofenster öffnen" 40 | }, 41 | "addBookmark": 42 | { 43 | "message": "Neues Lesezeichen hinzufügen" 44 | }, 45 | "addFolder": 46 | { 47 | "message": "Neuen Ordner hinzufügen" 48 | }, 49 | "showInFolder": 50 | { 51 | "message": "In Ordner zeigen" 52 | }, 53 | "setAsStartupFolder": 54 | { 55 | "message": "Als Startverzeichnis festlegen" 56 | }, 57 | "updateToCurrentURL": 58 | { 59 | "message": "Aktualisieren auf die aktuelle URL" 60 | }, 61 | "edit": 62 | { 63 | "message": "Bearbeiten" 64 | }, 65 | "delete": 66 | { 67 | "message": "Löschen" 68 | }, 69 | "deleteFolderConfirm": 70 | { 71 | "message": "Dadurch werden auch alle Lesezeichen ($1) in diesem Ordner gelöscht, OK?", 72 | "placeholders": { 73 | "bookmarksNum": { 74 | "content": "$1" 75 | } 76 | } 77 | }, 78 | "rename": 79 | { 80 | "message": "Umbenennen" 81 | }, 82 | 83 | "editBookmark": 84 | { 85 | "message": "Lesezeichen bearbeiten" 86 | }, 87 | "editFolderName": 88 | { 89 | "message": "Ordner umbenennen" 90 | }, 91 | "save": 92 | { 93 | "message": "Speichern" 94 | }, 95 | "cancel": 96 | { 97 | "message": "Abbrechen" 98 | }, 99 | "name": 100 | { 101 | "message": "Name" 102 | }, 103 | "URL": 104 | { 105 | "message": "URL" 106 | }, 107 | 108 | "themeColor": 109 | { 110 | "message": "Theme color:" 111 | }, 112 | "auto": 113 | { 114 | "message": "Auto" 115 | }, 116 | "light": 117 | { 118 | "message": "Light" 119 | }, 120 | "dark": 121 | { 122 | "message": "Dark" 123 | }, 124 | "openIn": 125 | { 126 | "message": "Open bookmarks in:" 127 | }, 128 | "currentTab": 129 | { 130 | "message": "Current tab" 131 | }, 132 | "newTab": 133 | { 134 | "message": "New tab" 135 | }, 136 | "backgroundTab": 137 | { 138 | "message": "Background tab" 139 | }, 140 | "hoverEnter": { 141 | "message": "Hover to open folder:" 142 | }, 143 | "off": { 144 | "message": "Off" 145 | }, 146 | "slow": { 147 | "message": "Slow" 148 | }, 149 | "moderate": { 150 | "message": "Moderate" 151 | }, 152 | "fast": { 153 | "message": "Fast" 154 | }, 155 | "startupFolder": { 156 | "message": "Startup folder:" 157 | }, 158 | "folderXTitle": { 159 | "message": "Please use the folder right-click menu to set or change startup from a subdirectory..." 160 | }, 161 | "lastFolder": { 162 | "message": "Last folder" 163 | }, 164 | "lastFolderAndScrollBar": { 165 | "message": "Last folder and scrollbar" 166 | }, 167 | "scrollDirection": { 168 | "message": "Scroll Direction:" 169 | }, 170 | "scrollDirectionX": { 171 | "message": "Horizontal" 172 | }, 173 | "scrollDirectionY": { 174 | "message": "Vertical" 175 | }, 176 | "layoutCols": { 177 | "message": "Layout column(s):" 178 | }, 179 | "minItemsEachCol": { 180 | "message": "Items each column:" 181 | }, 182 | "minItemsEachColTips": { 183 | "message": "Note: valid when the number of Layout columns is multiple.\n1 or 10 is recommended" 184 | }, 185 | "alias": { 186 | "message": "Alias:" 187 | }, 188 | "customIcon": { 189 | "message": "Custom icon:" 190 | }, 191 | "selectFile": { 192 | "message": "Select file" 193 | }, 194 | "reset": { 195 | "message": "Reset" 196 | }, 197 | "customIconTip": { 198 | "message": "Note: Please select square, transparent-background image (png format)" 199 | }, 200 | "customCSS": { 201 | "message": "Custom style:" 202 | }, 203 | "example": { 204 | "message": "For example:" 205 | }, 206 | "BackupAndRecovery": { 207 | "message": "Backup and Recovery:" 208 | }, 209 | "import": { 210 | "message": "Import" 211 | }, 212 | "export": { 213 | "message": "Export" 214 | }, 215 | "resetConfigTip": { 216 | "message": "This will clear all configurations, are you sure?" 217 | }, 218 | "setInvalidTips": { 219 | "message": "Invalid option! Optional Settings:" 220 | }, 221 | "Built_inVariables": { 222 | "message": "Built-in variables" 223 | }, 224 | "Built_inVariablesTip": { 225 | "message": "Note: Used to switch/modify minor features within this plugin (or the default behavior of the browser itself)" 226 | } 227 | } -------------------------------------------------------------------------------- /src/_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginDesc": 3 | { 4 | "message": "Easy to use bookmark manager" 5 | }, 6 | 7 | "bookmarksManager": 8 | { 9 | "message": "Bookmarks Manager" 10 | }, 11 | 12 | "searchPlaceholder": 13 | { 14 | "message": "Search Bookmarks..." 15 | }, 16 | "noBookmarksTip": 17 | { 18 | "message": "This folder is empty" 19 | }, 20 | "noSearchResultsTip": 21 | { 22 | "message": "No search results found" 23 | }, 24 | 25 | "openAll": 26 | { 27 | "message": "Open all" 28 | }, 29 | "openInNewTab": 30 | { 31 | "message": "Open in new tab" 32 | }, 33 | "openInBackgroundTab": 34 | { 35 | "message": "Open in background tab" 36 | }, 37 | "openInIncognitoWindow": 38 | { 39 | "message": "Open in Incognito window" 40 | }, 41 | "addBookmark": 42 | { 43 | "message": "Add new bookmark" 44 | }, 45 | "addFolder": 46 | { 47 | "message": "Add new folder" 48 | }, 49 | "showInFolder": 50 | { 51 | "message": "Show in folder" 52 | }, 53 | "setAsStartupFolder": 54 | { 55 | "message": "Set as startup folder" 56 | }, 57 | "updateToCurrentURL": 58 | { 59 | "message": "Update to current URL" 60 | }, 61 | "edit": 62 | { 63 | "message": "Edit" 64 | }, 65 | "delete": 66 | { 67 | "message": "Delete" 68 | }, 69 | "deleteFolderConfirm": 70 | { 71 | "message": "This will also delete all bookmarks ($bookmarksNum$) in this folder, is it sure?", 72 | "placeholders": { 73 | "bookmarksNum": { 74 | "content": "$1" 75 | } 76 | } 77 | }, 78 | "rename": 79 | { 80 | "message": "Rename" 81 | }, 82 | 83 | "editBookmark": 84 | { 85 | "message": "Edit bookmark" 86 | }, 87 | "editFolderName": 88 | { 89 | "message": "Rename folder" 90 | }, 91 | "save": 92 | { 93 | "message": "Save" 94 | }, 95 | "cancel": 96 | { 97 | "message": "Cancel" 98 | }, 99 | "name": 100 | { 101 | "message": "Name" 102 | }, 103 | "URL": 104 | { 105 | "message": "URL" 106 | }, 107 | 108 | "themeColor": 109 | { 110 | "message": "Theme color:" 111 | }, 112 | "auto": 113 | { 114 | "message": "Auto" 115 | }, 116 | "light": 117 | { 118 | "message": "Light" 119 | }, 120 | "dark": 121 | { 122 | "message": "Dark" 123 | }, 124 | "openIn": 125 | { 126 | "message": "Open bookmarks in:" 127 | }, 128 | "currentTab": 129 | { 130 | "message": "Current tab" 131 | }, 132 | "newTab": 133 | { 134 | "message": "New tab" 135 | }, 136 | "backgroundTab": 137 | { 138 | "message": "Background tab" 139 | }, 140 | "hoverEnter": { 141 | "message": "Hover to open folder:" 142 | }, 143 | "off": { 144 | "message": "Off" 145 | }, 146 | "slow": { 147 | "message": "Slow" 148 | }, 149 | "moderate": { 150 | "message": "Moderate" 151 | }, 152 | "fast": { 153 | "message": "Fast" 154 | }, 155 | "startupFolder": { 156 | "message": "Startup folder:" 157 | }, 158 | "folderXTitle": { 159 | "message": "Please use the folder right-click menu to set or change startup from a subdirectory..." 160 | }, 161 | "lastFolder": { 162 | "message": "Last folder" 163 | }, 164 | "lastFolderAndScrollBar": { 165 | "message": "Last folder and scrollbar" 166 | }, 167 | "scrollDirection": { 168 | "message": "Scroll Direction:" 169 | }, 170 | "scrollDirectionX": { 171 | "message": "Horizontal" 172 | }, 173 | "scrollDirectionY": { 174 | "message": "Vertical" 175 | }, 176 | "layoutCols": { 177 | "message": "Layout column(s):" 178 | }, 179 | "minItemsEachCol": { 180 | "message": "Items each column:" 181 | }, 182 | "minItemsEachColTips": { 183 | "message": "Note: valid when the number of Layout columns is multiple.\n1 or 10 is recommended" 184 | }, 185 | "alias": { 186 | "message": "Alias:" 187 | }, 188 | "customIcon": { 189 | "message": "Custom icon:" 190 | }, 191 | "selectFile": { 192 | "message": "Select file" 193 | }, 194 | "reset": { 195 | "message": "Reset" 196 | }, 197 | "customIconTip": { 198 | "message": "Note: Please select square, transparent-background image (png format)" 199 | }, 200 | "customCSS": { 201 | "message": "Custom style:" 202 | }, 203 | "example": { 204 | "message": "For example:" 205 | }, 206 | "BackupAndRecovery": { 207 | "message": "Backup and Recovery:" 208 | }, 209 | "import": { 210 | "message": "Import" 211 | }, 212 | "export": { 213 | "message": "Export" 214 | }, 215 | "resetConfigTip": { 216 | "message": "This will clear all configurations, are you sure?" 217 | }, 218 | "setInvalidTips": { 219 | "message": "Invalid option! Optional Settings:" 220 | }, 221 | "Built_inVariables": { 222 | "message": "Built-in variables" 223 | }, 224 | "Built_inVariablesTip": { 225 | "message": "Note: Used to switch/modify minor features within this plugin (or the default behavior of the browser itself)" 226 | } 227 | } -------------------------------------------------------------------------------- /src/_locales/es/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginDesc": 3 | { 4 | "message": "Easy to use bookmark manager" 5 | }, 6 | 7 | "bookmarksManager": 8 | { 9 | "message": "Administrador de &marcadores" 10 | }, 11 | 12 | "searchPlaceholder": 13 | { 14 | "message": "Buscar marcadores..." 15 | }, 16 | "noBookmarksTip": 17 | { 18 | "message": "Esta carpeta está vacía" 19 | }, 20 | "noSearchResultsTip": 21 | { 22 | "message": "No se han encontrado resultados de búsqueda" 23 | }, 24 | 25 | "openAll": 26 | { 27 | "message": "Abrir todas" 28 | }, 29 | "openInNewTab": 30 | { 31 | "message": "Abrir en una pestaña nueva" 32 | }, 33 | "openInBackgroundTab": 34 | { 35 | "message": "Abrir en segundo plano" 36 | }, 37 | "openInIncognitoWindow": 38 | { 39 | "message": "Abrir en una ventana de incógnito" 40 | }, 41 | "addBookmark": 42 | { 43 | "message": "Añadir nuevo marcador" 44 | }, 45 | "addFolder": 46 | { 47 | "message": "Añadir nueva carpeta" 48 | }, 49 | "showInFolder": 50 | { 51 | "message": "Mostrar en carpeta" 52 | }, 53 | "setAsStartupFolder": 54 | { 55 | "message": "Establecer como directorio de inicio" 56 | }, 57 | "updateToCurrentURL": 58 | { 59 | "message": "Actualizar a la URL actual" 60 | }, 61 | "edit": 62 | { 63 | "message": "Edición" 64 | }, 65 | "delete": 66 | { 67 | "message": "Suprimir" 68 | }, 69 | "deleteFolderConfirm": 70 | { 71 | "message": "Esto también eliminará todos los marcadores ($1) en este carpeta, ¿de acuerdo?", 72 | "placeholders": { 73 | "bookmarksNum": { 74 | "content": "$1" 75 | } 76 | } 77 | }, 78 | "rename": 79 | { 80 | "message": "Cambiar nombre" 81 | }, 82 | 83 | "editBookmark": 84 | { 85 | "message": "Editar marcador" 86 | }, 87 | "editFolderName": 88 | { 89 | "message": "Cambia el nombre de la carpeta" 90 | }, 91 | "save": 92 | { 93 | "message": "Guardar" 94 | }, 95 | "cancel": 96 | { 97 | "message": "Cancelar" 98 | }, 99 | "name": 100 | { 101 | "message": "Nombre" 102 | }, 103 | "URL": 104 | { 105 | "message": "URL" 106 | }, 107 | 108 | "themeColor": 109 | { 110 | "message": "Theme color:" 111 | }, 112 | "auto": 113 | { 114 | "message": "Auto" 115 | }, 116 | "light": 117 | { 118 | "message": "Light" 119 | }, 120 | "dark": 121 | { 122 | "message": "Dark" 123 | }, 124 | "openIn": 125 | { 126 | "message": "Open bookmarks in:" 127 | }, 128 | "currentTab": 129 | { 130 | "message": "Current tab" 131 | }, 132 | "newTab": 133 | { 134 | "message": "New tab" 135 | }, 136 | "backgroundTab": 137 | { 138 | "message": "Background tab" 139 | }, 140 | "hoverEnter": { 141 | "message": "Hover to open folder:" 142 | }, 143 | "off": { 144 | "message": "Off" 145 | }, 146 | "slow": { 147 | "message": "Slow" 148 | }, 149 | "moderate": { 150 | "message": "Moderate" 151 | }, 152 | "fast": { 153 | "message": "Fast" 154 | }, 155 | "startupFolder": { 156 | "message": "Startup folder:" 157 | }, 158 | "folderXTitle": { 159 | "message": "Please use the folder right-click menu to set or change startup from a subdirectory..." 160 | }, 161 | "lastFolder": { 162 | "message": "Last folder" 163 | }, 164 | "lastFolderAndScrollBar": { 165 | "message": "Last folder and scrollbar" 166 | }, 167 | "scrollDirection": { 168 | "message": "Scroll Direction:" 169 | }, 170 | "scrollDirectionX": { 171 | "message": "Horizontal" 172 | }, 173 | "scrollDirectionY": { 174 | "message": "Vertical" 175 | }, 176 | "layoutCols": { 177 | "message": "Layout column(s):" 178 | }, 179 | "minItemsEachCol": { 180 | "message": "Items each column:" 181 | }, 182 | "minItemsEachColTips": { 183 | "message": "Note: valid when the number of Layout columns is multiple.\n1 or 10 is recommended" 184 | }, 185 | "alias": { 186 | "message": "Alias:" 187 | }, 188 | "customIcon": { 189 | "message": "Custom icon:" 190 | }, 191 | "selectFile": { 192 | "message": "Select file" 193 | }, 194 | "reset": { 195 | "message": "Reset" 196 | }, 197 | "customIconTip": { 198 | "message": "Note: Please select square, transparent-background image (png format)" 199 | }, 200 | "customCSS": { 201 | "message": "Custom style:" 202 | }, 203 | "example": { 204 | "message": "For example:" 205 | }, 206 | "BackupAndRecovery": { 207 | "message": "Backup and Recovery:" 208 | }, 209 | "import": { 210 | "message": "Import" 211 | }, 212 | "export": { 213 | "message": "Export" 214 | }, 215 | "resetConfigTip": { 216 | "message": "This will clear all configurations, are you sure?" 217 | }, 218 | "setInvalidTips": { 219 | "message": "Invalid option! Optional Settings:" 220 | }, 221 | "Built_inVariables": { 222 | "message": "Built-in variables" 223 | }, 224 | "Built_inVariablesTip": { 225 | "message": "Note: Used to switch/modify minor features within this plugin (or the default behavior of the browser itself)" 226 | } 227 | } -------------------------------------------------------------------------------- /src/_locales/fr/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginDesc": 3 | { 4 | "message": "Gestionnaire de signets simple et facile à utiliser" 5 | }, 6 | 7 | "bookmarksManager": 8 | { 9 | "message": "Gestionnaire de favoris" 10 | }, 11 | 12 | "searchPlaceholder": 13 | { 14 | "message": "Rechercher dans les favoris..." 15 | }, 16 | "noBookmarksTip": 17 | { 18 | "message": "Ce dossier est vide" 19 | }, 20 | "noSearchResultsTip": 21 | { 22 | "message": "Aucun résultat de recherche n'a été trouvé" 23 | }, 24 | 25 | "openAll": 26 | { 27 | "message": "Tout ouvrir" 28 | }, 29 | "openInNewTab": 30 | { 31 | "message": "Ouvrir dans un nouvel onglet" 32 | }, 33 | "openInBackgroundTab": 34 | { 35 | "message": "Ouvrir dans l’onglet d’arrière-plan" 36 | }, 37 | "openInIncognitoWindow": 38 | { 39 | "message": "Ouvrir dans une fenêtre de navigation privée" 40 | }, 41 | "addBookmark": 42 | { 43 | "message": "Ajouter un favori" 44 | }, 45 | "addFolder": 46 | { 47 | "message": "Ajouter un dossier" 48 | }, 49 | "showInFolder": 50 | { 51 | "message": "Afficher le dossier" 52 | }, 53 | "setAsStartupFolder": 54 | { 55 | "message": "Définir comme répertoire de démarrage" 56 | }, 57 | "updateToCurrentURL": 58 | { 59 | "message": "Mise à jour de l’URL actuelle" 60 | }, 61 | "edit": 62 | { 63 | "message": "Modifier" 64 | }, 65 | "delete": 66 | { 67 | "message": " Supprimer" 68 | }, 69 | "deleteFolderConfirm": 70 | { 71 | "message": "Cela supprimera également tous les signets ($1) de ce dossier, OK?", 72 | "placeholders": { 73 | "bookmarksNum": { 74 | "content": "$1" 75 | } 76 | } 77 | }, 78 | "rename": 79 | { 80 | "message": "Renommer" 81 | }, 82 | 83 | "editBookmark": 84 | { 85 | "message": "Modifier le favori" 86 | }, 87 | "editFolderName": 88 | { 89 | "message": "Renommer le dossier" 90 | }, 91 | "save": 92 | { 93 | "message": "Enregistrer" 94 | }, 95 | "cancel": 96 | { 97 | "message": "Annuler" 98 | }, 99 | "name": 100 | { 101 | "message": "Nom" 102 | }, 103 | "URL": 104 | { 105 | "message": "URL" 106 | }, 107 | 108 | "themeColor": 109 | { 110 | "message": "Theme color:" 111 | }, 112 | "auto": 113 | { 114 | "message": "Auto" 115 | }, 116 | "light": 117 | { 118 | "message": "Light" 119 | }, 120 | "dark": 121 | { 122 | "message": "Dark" 123 | }, 124 | "openIn": 125 | { 126 | "message": "Open bookmarks in:" 127 | }, 128 | "currentTab": 129 | { 130 | "message": "Current tab" 131 | }, 132 | "newTab": 133 | { 134 | "message": "New tab" 135 | }, 136 | "backgroundTab": 137 | { 138 | "message": "Background tab" 139 | }, 140 | "hoverEnter": { 141 | "message": "Hover to open folder:" 142 | }, 143 | "off": { 144 | "message": "Off" 145 | }, 146 | "slow": { 147 | "message": "Slow" 148 | }, 149 | "moderate": { 150 | "message": "Moderate" 151 | }, 152 | "fast": { 153 | "message": "Fast" 154 | }, 155 | "startupFolder": { 156 | "message": "Startup folder:" 157 | }, 158 | "folderXTitle": { 159 | "message": "Please use the folder right-click menu to set or change startup from a subdirectory..." 160 | }, 161 | "lastFolder": { 162 | "message": "Last folder" 163 | }, 164 | "lastFolderAndScrollBar": { 165 | "message": "Last folder and scrollbar" 166 | }, 167 | "scrollDirection": { 168 | "message": "Scroll Direction:" 169 | }, 170 | "scrollDirectionX": { 171 | "message": "Horizontal" 172 | }, 173 | "scrollDirectionY": { 174 | "message": "Vertical" 175 | }, 176 | "layoutCols": { 177 | "message": "Layout column(s):" 178 | }, 179 | "minItemsEachCol": { 180 | "message": "Items each column:" 181 | }, 182 | "minItemsEachColTips": { 183 | "message": "Note: valid when the number of Layout columns is multiple.\n1 or 10 is recommended" 184 | }, 185 | "alias": { 186 | "message": "Alias:" 187 | }, 188 | "customIcon": { 189 | "message": "Custom icon:" 190 | }, 191 | "selectFile": { 192 | "message": "Select file" 193 | }, 194 | "reset": { 195 | "message": "Reset" 196 | }, 197 | "customIconTip": { 198 | "message": "Note: Please select square, transparent-background image (png format)" 199 | }, 200 | "customCSS": { 201 | "message": "Custom style:" 202 | }, 203 | "example": { 204 | "message": "For example:" 205 | }, 206 | "BackupAndRecovery": { 207 | "message": "Backup and Recovery:" 208 | }, 209 | "import": { 210 | "message": "Import" 211 | }, 212 | "export": { 213 | "message": "Export" 214 | }, 215 | "resetConfigTip": { 216 | "message": "This will clear all configurations, are you sure?" 217 | }, 218 | "setInvalidTips": { 219 | "message": "Invalid option! Optional Settings:" 220 | }, 221 | "Built_inVariables": { 222 | "message": "Built-in variables" 223 | }, 224 | "Built_inVariablesTip": { 225 | "message": "Note: Used to switch/modify minor features within this plugin (or the default behavior of the browser itself)" 226 | } 227 | } -------------------------------------------------------------------------------- /src/_locales/it/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginDesc": 3 | { 4 | "message": "Easy to use bookmark manager" 5 | }, 6 | 7 | "bookmarksManager": 8 | { 9 | "message": "Gestione &Preferiti" 10 | }, 11 | 12 | "searchPlaceholder": 13 | { 14 | "message": "Cerca tra i Preferiti..." 15 | }, 16 | "noBookmarksTip": 17 | { 18 | "message": "Questa cartella è vuota" 19 | }, 20 | "noSearchResultsTip": 21 | { 22 | "message": "Nessun risultato di ricerca trovato" 23 | }, 24 | 25 | "openAll": 26 | { 27 | "message": "Apri tutte" 28 | }, 29 | "openInNewTab": 30 | { 31 | "message": "Apri in un'altra scheda" 32 | }, 33 | "openInBackgroundTab": 34 | { 35 | "message": "Apri in Scheda Sfondo" 36 | }, 37 | "openInIncognitoWindow": 38 | { 39 | "message": "Apri nella finestra in incognito" 40 | }, 41 | "addBookmark": 42 | { 43 | "message": "Aggiungi nuovo preferito" 44 | }, 45 | "addFolder": 46 | { 47 | "message": "Aggiungi nuova cartella" 48 | }, 49 | "showInFolder": 50 | { 51 | "message": "Mostra nella cartella" 52 | }, 53 | "setAsStartupFolder": 54 | { 55 | "message": "Imposta come cartella di avvio" 56 | }, 57 | "updateToCurrentURL": 58 | { 59 | "message": "Aggiornamento all'URL corrente" 60 | }, 61 | "edit": 62 | { 63 | "message": "Modifica" 64 | }, 65 | "delete": 66 | { 67 | "message": "Elimina" 68 | }, 69 | "deleteFolderConfirm": 70 | { 71 | "message": "Questo eliminerà anche tutti i segnalibri ($1) in questa cartella, è sicuro?", 72 | "placeholders": { 73 | "bookmarksNum": { 74 | "content": "$1" 75 | } 76 | } 77 | }, 78 | "rename": 79 | { 80 | "message": "Rinomina" 81 | }, 82 | 83 | "editBookmark": 84 | { 85 | "message": "Modifica preferito" 86 | }, 87 | "editFolderName": 88 | { 89 | "message": "Rinomina cartella" 90 | }, 91 | "save": 92 | { 93 | "message": "Salva" 94 | }, 95 | "cancel": 96 | { 97 | "message": "Annulla" 98 | }, 99 | "name": 100 | { 101 | "message": "Nome" 102 | }, 103 | "URL": 104 | { 105 | "message": "URL" 106 | }, 107 | 108 | "themeColor": 109 | { 110 | "message": "Theme color:" 111 | }, 112 | "auto": 113 | { 114 | "message": "Auto" 115 | }, 116 | "light": 117 | { 118 | "message": "Light" 119 | }, 120 | "dark": 121 | { 122 | "message": "Dark" 123 | }, 124 | "openIn": 125 | { 126 | "message": "Open bookmarks in:" 127 | }, 128 | "currentTab": 129 | { 130 | "message": "Current tab" 131 | }, 132 | "newTab": 133 | { 134 | "message": "New tab" 135 | }, 136 | "backgroundTab": 137 | { 138 | "message": "Background tab" 139 | }, 140 | "hoverEnter": { 141 | "message": "Hover to open folder:" 142 | }, 143 | "off": { 144 | "message": "Off" 145 | }, 146 | "slow": { 147 | "message": "Slow" 148 | }, 149 | "moderate": { 150 | "message": "Moderate" 151 | }, 152 | "fast": { 153 | "message": "Fast" 154 | }, 155 | "startupFolder": { 156 | "message": "Startup folder:" 157 | }, 158 | "folderXTitle": { 159 | "message": "Please use the folder right-click menu to set or change startup from a subdirectory..." 160 | }, 161 | "lastFolder": { 162 | "message": "Last folder" 163 | }, 164 | "lastFolderAndScrollBar": { 165 | "message": "Last folder and scrollbar" 166 | }, 167 | "scrollDirection": { 168 | "message": "Scroll Direction:" 169 | }, 170 | "scrollDirectionX": { 171 | "message": "Horizontal" 172 | }, 173 | "scrollDirectionY": { 174 | "message": "Vertical" 175 | }, 176 | "layoutCols": { 177 | "message": "Layout column(s):" 178 | }, 179 | "minItemsEachCol": { 180 | "message": "Items each column:" 181 | }, 182 | "minItemsEachColTips": { 183 | "message": "Note: valid when the number of Layout columns is multiple.\n1 or 10 is recommended" 184 | }, 185 | "alias": { 186 | "message": "Alias:" 187 | }, 188 | "customIcon": { 189 | "message": "Custom icon:" 190 | }, 191 | "selectFile": { 192 | "message": "Select file" 193 | }, 194 | "reset": { 195 | "message": "Reset" 196 | }, 197 | "customIconTip": { 198 | "message": "Note: Please select square, transparent-background image (png format)" 199 | }, 200 | "customCSS": { 201 | "message": "Custom style:" 202 | }, 203 | "example": { 204 | "message": "For example:" 205 | }, 206 | "BackupAndRecovery": { 207 | "message": "Backup and Recovery:" 208 | }, 209 | "import": { 210 | "message": "Import" 211 | }, 212 | "export": { 213 | "message": "Export" 214 | }, 215 | "resetConfigTip": { 216 | "message": "This will clear all configurations, are you sure?" 217 | }, 218 | "setInvalidTips": { 219 | "message": "Invalid option! Optional Settings:" 220 | }, 221 | "Built_inVariables": { 222 | "message": "Built-in variables" 223 | }, 224 | "Built_inVariablesTip": { 225 | "message": "Note: Used to switch/modify minor features within this plugin (or the default behavior of the browser itself)" 226 | } 227 | } -------------------------------------------------------------------------------- /src/_locales/ja/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginDesc": 3 | { 4 | "message": "使いやすいブックマーク・マネージャー" 5 | }, 6 | 7 | "bookmarksManager": 8 | { 9 | "message": "ブックマーク マネージャ" 10 | }, 11 | 12 | "searchPlaceholder": 13 | { 14 | "message": "ブックマークを検索..." 15 | }, 16 | "noBookmarksTip": 17 | { 18 | "message": "このフォルダは空です" 19 | }, 20 | "noSearchResultsTip": 21 | { 22 | "message": "一致する結果は見つかりませんでした" 23 | }, 24 | 25 | "openAll": 26 | { 27 | "message": "すべて開" 28 | }, 29 | "openInNewTab": 30 | { 31 | "message": "新しいタブで開く" 32 | }, 33 | "openInBackgroundTab": 34 | { 35 | "message": "バックグラウンドで開きます" 36 | }, 37 | "openInIncognitoWindow": 38 | { 39 | "message": "シークレット ウィンドウで開" 40 | }, 41 | "addBookmark": 42 | { 43 | "message": "新しいブックマークを追加" 44 | }, 45 | "addFolder": 46 | { 47 | "message": "新しいフォルダを追加" 48 | }, 49 | "showInFolder": 50 | { 51 | "message": "フォルダを開く" 52 | }, 53 | "setAsStartupFolder": 54 | { 55 | "message": "スタートアップディレクトリに設定" 56 | }, 57 | "updateToCurrentURL": 58 | { 59 | "message": "現在のURLに更新しました" 60 | }, 61 | "edit": 62 | { 63 | "message": "編集" 64 | }, 65 | "delete": 66 | { 67 | "message": "削除" 68 | }, 69 | "deleteFolderConfirm": 70 | { 71 | "message": "これで、そのフォルダ内のブックマーク($1)もすべて削除されますよね?", 72 | "placeholders": { 73 | "bookmarksNum": { 74 | "content": "$1" 75 | } 76 | } 77 | }, 78 | "rename": 79 | { 80 | "message": "名前を変更" 81 | }, 82 | 83 | "editBookmark": 84 | { 85 | "message": "ブックマークを編集" 86 | }, 87 | "editFolderName": 88 | { 89 | "message": "フォルダ名の変更" 90 | }, 91 | "save": 92 | { 93 | "message": "保存" 94 | }, 95 | "cancel": 96 | { 97 | "message": "キャンセル" 98 | }, 99 | "name": 100 | { 101 | "message": "名前" 102 | }, 103 | "URL": 104 | { 105 | "message": "URL" 106 | }, 107 | 108 | "themeColor": 109 | { 110 | "message": "テーマカラー:" 111 | }, 112 | "auto": 113 | { 114 | "message": "システムデフォルト" 115 | }, 116 | "light": 117 | { 118 | "message": "ライトカラー" 119 | }, 120 | "dark": 121 | { 122 | "message": "ダーク" 123 | }, 124 | "openIn": 125 | { 126 | "message": "ブックマークの開き方:" 127 | }, 128 | "currentTab": 129 | { 130 | "message": "現在のページ" 131 | }, 132 | "newTab": 133 | { 134 | "message": "新しいタブ" 135 | }, 136 | "backgroundTab": 137 | { 138 | "message": "新しいタブ(バックステージ)" 139 | }, 140 | "hoverEnter": { 141 | "message": "カタログホバーアクセス:" 142 | }, 143 | "off": { 144 | "message": "閉じます" 145 | }, 146 | "slow": { 147 | "message": "遅い" 148 | }, 149 | "moderate": { 150 | "message": "控えめ" 151 | }, 152 | "fast": { 153 | "message": "急げ!" 154 | }, 155 | "startupFolder": { 156 | "message": "起動場所:" 157 | }, 158 | "folderXTitle": { 159 | "message": "フォルダの右ボタンメニューの設定または変更によってサブディレクトリから起動してください..." 160 | }, 161 | "lastFolder": { 162 | "message": "最後のディレクトリ" 163 | }, 164 | "lastFolderAndScrollBar": { 165 | "message": "最後のディレクトリとスクロール バー" 166 | }, 167 | "scrollDirection": { 168 | "message": "スクロール方向:" 169 | }, 170 | "scrollDirectionX": { 171 | "message": "横" 172 | }, 173 | "scrollDirectionY": { 174 | "message": "縦" 175 | }, 176 | "layoutCols": { 177 | "message": "レイアウト列の数:" 178 | }, 179 | "minItemsEachCol": { 180 | "message": "列あたりのバーの数:" 181 | }, 182 | "minItemsEachColTips": { 183 | "message": "注: レイアウト列の数が複数ある場合に有効です。1 または 10 を推奨" 184 | }, 185 | "alias": { 186 | "message": "エイリアス:" 187 | }, 188 | "customIcon": { 189 | "message": "Custom icon:" 190 | }, 191 | "selectFile": { 192 | "message": "Select file" 193 | }, 194 | "reset": { 195 | "message": "Reset" 196 | }, 197 | "customIconTip": { 198 | "message": "注:正方形で透過性のある背景画像(png形式)を選択してください。" 199 | }, 200 | "customCSS": { 201 | "message": "Custom style:" 202 | }, 203 | "example": { 204 | "message": "For example:" 205 | }, 206 | "BackupAndRecovery": { 207 | "message": "Backup and Recovery:" 208 | }, 209 | "import": { 210 | "message": "Import" 211 | }, 212 | "export": { 213 | "message": "Export" 214 | }, 215 | "resetConfigTip": { 216 | "message": "This will clear all configurations, are you sure?" 217 | }, 218 | "setInvalidTips": { 219 | "message": "Invalid option! Optional Settings:" 220 | }, 221 | "Built_inVariables": { 222 | "message": "Built-in variables" 223 | }, 224 | "Built_inVariablesTip": { 225 | "message": "Note: Used to switch/modify minor features within this plugin (or the default behavior of the browser itself)" 226 | } 227 | } -------------------------------------------------------------------------------- /src/_locales/ko/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginDesc": 3 | { 4 | "message": "사용하기 쉬운 북마크 관리자" 5 | }, 6 | 7 | "bookmarksManager": 8 | { 9 | "message": "북마크 관리자" 10 | }, 11 | 12 | "searchPlaceholder": 13 | { 14 | "message": "북마크 검색..." 15 | }, 16 | "noBookmarksTip": 17 | { 18 | "message": "폴더가 비어 있습니다." 19 | }, 20 | "noSearchResultsTip": 21 | { 22 | "message": "검색결과 없음" 23 | }, 24 | 25 | "openAll": 26 | { 27 | "message": "모두 열기" 28 | }, 29 | "openInNewTab": 30 | { 31 | "message": "새 탭에서 열기" 32 | }, 33 | "openInBackgroundTab": 34 | { 35 | "message": "백그라운드 탭에서 열기" 36 | }, 37 | "openInIncognitoWindow": 38 | { 39 | "message": "시크릿 창으로 열기" 40 | }, 41 | "addBookmark": 42 | { 43 | "message": "새 북마크 추가" 44 | }, 45 | "addFolder": 46 | { 47 | "message": "새 폴더 추가" 48 | }, 49 | "showInFolder": 50 | { 51 | "message": "폴더 열기" 52 | }, 53 | "setAsStartupFolder": 54 | { 55 | "message": "시작 디렉토리로 설정합니다" 56 | }, 57 | "updateToCurrentURL": 58 | { 59 | "message": "현재 URL로 업데이트" 60 | }, 61 | "edit": 62 | { 63 | "message": "편집" 64 | }, 65 | "delete": 66 | { 67 | "message": "삭제" 68 | }, 69 | "deleteFolderConfirm": 70 | { 71 | "message": "그러면 해당 폴더의 모든 북마크($1)도 삭제되겠죠?", 72 | "placeholders": { 73 | "bookmarksNum": { 74 | "content": "$1" 75 | } 76 | } 77 | }, 78 | "rename": 79 | { 80 | "message": "이름 바꾸기" 81 | }, 82 | 83 | "editBookmark": 84 | { 85 | "message": "북마크 수정" 86 | }, 87 | "editFolderName": 88 | { 89 | "message": "폴더 이름 바꾸기" 90 | }, 91 | "save": 92 | { 93 | "message": "저장" 94 | }, 95 | "cancel": 96 | { 97 | "message": "취소" 98 | }, 99 | "name": 100 | { 101 | "message": "이름" 102 | }, 103 | "URL": 104 | { 105 | "message": "URL" 106 | }, 107 | 108 | "themeColor": 109 | { 110 | "message": "테마 색상:" 111 | }, 112 | "auto": 113 | { 114 | "message": "자동" 115 | }, 116 | "light": 117 | { 118 | "message": "낮에" 119 | }, 120 | "dark": 121 | { 122 | "message": "밤" 123 | }, 124 | "openIn": 125 | { 126 | "message": "북마크를 여는 방법:" 127 | }, 128 | "currentTab": 129 | { 130 | "message": "현재 탭" 131 | }, 132 | "newTab": 133 | { 134 | "message": "새 탭" 135 | }, 136 | "backgroundTab": 137 | { 138 | "message": "배경 탭" 139 | }, 140 | "hoverEnter": { 141 | "message": "디렉토리 호버 열기:" 142 | }, 143 | "off": { 144 | "message": "닫기" 145 | }, 146 | "slow": { 147 | "message": "천천히" 148 | }, 149 | "moderate": { 150 | "message": "적당합니다" 151 | }, 152 | "fast": { 153 | "message": "빨리" 154 | }, 155 | "startupFolder": { 156 | "message": "시작 폴더:" 157 | }, 158 | "folderXTitle": { 159 | "message": "폴더 오른쪽 클릭 메뉴를 사용하여 하위 디렉토리에서 시작을 설정하거나 변경하십시오..." 160 | }, 161 | "lastFolder": { 162 | "message": "마지막 디렉토리" 163 | }, 164 | "lastFolderAndScrollBar": { 165 | "message": "마지막 디렉토리 및 스크롤 막대" 166 | }, 167 | "scrollDirection": { 168 | "message": "스크롤 방향:" 169 | }, 170 | "scrollDirectionX": { 171 | "message": "가로" 172 | }, 173 | "scrollDirectionY": { 174 | "message": "세로" 175 | }, 176 | "layoutCols": { 177 | "message": "레이아웃 열 수입니다:" 178 | }, 179 | "minItemsEachCol": { 180 | "message": "열당 개수입니다:" 181 | }, 182 | "minItemsEachColTips": { 183 | "message": "참고: 레이아웃 열 수가 여러 열인 경우 유효합니다. 1 또는 10을 권장합니다" 184 | }, 185 | "alias": { 186 | "message": "별칭입니다:" 187 | }, 188 | "customIcon": { 189 | "message": "Custom icon:" 190 | }, 191 | "selectFile": { 192 | "message": "Select file" 193 | }, 194 | "reset": { 195 | "message": "Reset" 196 | }, 197 | "customIconTip": { 198 | "message": "참고: 정사각형 투명 배경 이미지(png 형식)를 선택합니다." 199 | }, 200 | "customCSS": { 201 | "message": "Custom style:" 202 | }, 203 | "example": { 204 | "message": "For example:" 205 | }, 206 | "BackupAndRecovery": { 207 | "message": "Backup and Recovery:" 208 | }, 209 | "import": { 210 | "message": "Import" 211 | }, 212 | "export": { 213 | "message": "Export" 214 | }, 215 | "resetConfigTip": { 216 | "message": "This will clear all configurations, are you sure?" 217 | }, 218 | "setInvalidTips": { 219 | "message": "Invalid option! Optional Settings:" 220 | }, 221 | "Built_inVariables": { 222 | "message": "Built-in variables" 223 | }, 224 | "Built_inVariablesTip": { 225 | "message": "Note: Used to switch/modify minor features within this plugin (or the default behavior of the browser itself)" 226 | } 227 | } -------------------------------------------------------------------------------- /src/_locales/pt_BR/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginDesc": 3 | { 4 | "message": "Easy to use bookmark manager" 5 | }, 6 | 7 | "bookmarksManager": 8 | { 9 | "message": "&Gerenciador de favoritos" 10 | }, 11 | 12 | "searchPlaceholder": 13 | { 14 | "message": "Pesquisar favoritos..." 15 | }, 16 | "noBookmarksTip": 17 | { 18 | "message": "Esta pasta está vazia" 19 | }, 20 | "noSearchResultsTip": 21 | { 22 | "message": "Nenhum resultado de pesquisa encontrado" 23 | }, 24 | 25 | "openAll": 26 | { 27 | "message": "Abrir todas" 28 | }, 29 | "openInNewTab": 30 | { 31 | "message": "Abrir em uma nova guia" 32 | }, 33 | "openInBackgroundTab": 34 | { 35 | "message": "Abrir em uma guia de fundo" 36 | }, 37 | "openInIncognitoWindow": 38 | { 39 | "message": "Abrir em uma janela anônima" 40 | }, 41 | "addBookmark": 42 | { 43 | "message": "Adicionar novo favorito" 44 | }, 45 | "addFolder": 46 | { 47 | "message": "Adicionar nova pasta" 48 | }, 49 | "showInFolder": 50 | { 51 | "message": "Mostrar na pasta" 52 | }, 53 | "setAsStartupFolder": 54 | { 55 | "message": "Definir como pasta de inicialização" 56 | }, 57 | "updateToCurrentURL": 58 | { 59 | "message": "Atualizar para a URL atual" 60 | }, 61 | "edit": 62 | { 63 | "message": "Editar" 64 | }, 65 | "delete": 66 | { 67 | "message": "Excluir" 68 | }, 69 | "deleteFolderConfirm": 70 | { 71 | "message": "Isso também excluirá todos os favoritos ($1) neste pasta, é certo?", 72 | "placeholders": { 73 | "bookmarksNum": { 74 | "content": "$1" 75 | } 76 | } 77 | }, 78 | "rename": 79 | { 80 | "message": "Renomear" 81 | }, 82 | 83 | "editBookmark": 84 | { 85 | "message": "Editar favorito" 86 | }, 87 | "editFolderName": 88 | { 89 | "message": "Renomear pasta" 90 | }, 91 | "save": 92 | { 93 | "message": "Salvar" 94 | }, 95 | "cancel": 96 | { 97 | "message": "Cancelar" 98 | }, 99 | "name": 100 | { 101 | "message": "Nome" 102 | }, 103 | "URL": 104 | { 105 | "message": "URL" 106 | }, 107 | 108 | "themeColor": 109 | { 110 | "message": "Theme color:" 111 | }, 112 | "auto": 113 | { 114 | "message": "Auto" 115 | }, 116 | "light": 117 | { 118 | "message": "Light" 119 | }, 120 | "dark": 121 | { 122 | "message": "Dark" 123 | }, 124 | "openIn": 125 | { 126 | "message": "Open bookmarks in:" 127 | }, 128 | "currentTab": 129 | { 130 | "message": "Current tab" 131 | }, 132 | "newTab": 133 | { 134 | "message": "New tab" 135 | }, 136 | "backgroundTab": 137 | { 138 | "message": "Background tab" 139 | }, 140 | "hoverEnter": { 141 | "message": "Hover to open folder:" 142 | }, 143 | "off": { 144 | "message": "Off" 145 | }, 146 | "slow": { 147 | "message": "Slow" 148 | }, 149 | "moderate": { 150 | "message": "Moderate" 151 | }, 152 | "fast": { 153 | "message": "Fast" 154 | }, 155 | "startupFolder": { 156 | "message": "Startup folder:" 157 | }, 158 | "folderXTitle": { 159 | "message": "Please use the folder right-click menu to set or change startup from a subdirectory..." 160 | }, 161 | "lastFolder": { 162 | "message": "Last folder" 163 | }, 164 | "lastFolderAndScrollBar": { 165 | "message": "Last folder and scrollbar" 166 | }, 167 | "scrollDirection": { 168 | "message": "Scroll Direction:" 169 | }, 170 | "scrollDirectionX": { 171 | "message": "Horizontal" 172 | }, 173 | "scrollDirectionY": { 174 | "message": "Vertical" 175 | }, 176 | "layoutCols": { 177 | "message": "Layout column(s):" 178 | }, 179 | "minItemsEachCol": { 180 | "message": "Items each column:" 181 | }, 182 | "minItemsEachColTips": { 183 | "message": "Note: valid when the number of Layout columns is multiple.\n1 or 10 is recommended" 184 | }, 185 | "alias": { 186 | "message": "Alias:" 187 | }, 188 | "customIcon": { 189 | "message": "Custom icon:" 190 | }, 191 | "selectFile": { 192 | "message": "Select file" 193 | }, 194 | "reset": { 195 | "message": "Reset" 196 | }, 197 | "customIconTip": { 198 | "message": "Note: Please select square, transparent-background image (png format)" 199 | }, 200 | "customCSS": { 201 | "message": "Custom style:" 202 | }, 203 | "example": { 204 | "message": "For example:" 205 | }, 206 | "BackupAndRecovery": { 207 | "message": "Backup and Recovery:" 208 | }, 209 | "import": { 210 | "message": "Import" 211 | }, 212 | "export": { 213 | "message": "Export" 214 | }, 215 | "resetConfigTip": { 216 | "message": "This will clear all configurations, are you sure?" 217 | }, 218 | "setInvalidTips": { 219 | "message": "Invalid option! Optional Settings:" 220 | }, 221 | "Built_inVariables": { 222 | "message": "Built-in variables" 223 | }, 224 | "Built_inVariablesTip": { 225 | "message": "Note: Used to switch/modify minor features within this plugin (or the default behavior of the browser itself)" 226 | } 227 | } -------------------------------------------------------------------------------- /src/_locales/pt_PT/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginDesc": 3 | { 4 | "message": "Easy to use bookmark manager" 5 | }, 6 | 7 | "bookmarksManager": 8 | { 9 | "message": "&Gestor de marcadores" 10 | }, 11 | 12 | "searchPlaceholder": 13 | { 14 | "message": "Pesquisar marcadores..." 15 | }, 16 | "noBookmarksTip": 17 | { 18 | "message": "Esta pasta está vazia" 19 | }, 20 | "noSearchResultsTip": 21 | { 22 | "message": "Não foram encontrados resultados da pesquis" 23 | }, 24 | 25 | "openAll": 26 | { 27 | "message": "Abrir tudo" 28 | }, 29 | "openInNewTab": 30 | { 31 | "message": "Abrir num novo separador" 32 | }, 33 | "openInBackgroundTab": 34 | { 35 | "message": "Abrir no separador de fundo" 36 | }, 37 | "openInIncognitoWindow": 38 | { 39 | "message": "Abra na janela de navegação anónima" 40 | }, 41 | "addBookmark": 42 | { 43 | "message": "Adicionar novo marcador" 44 | }, 45 | "addFolder": 46 | { 47 | "message": "Adicionar nova pasta" 48 | }, 49 | "showInFolder": 50 | { 51 | "message": "Mostrar numa pasta" 52 | }, 53 | "setAsStartupFolder": 54 | { 55 | "message": "Definir como pasta de inicialização" 56 | }, 57 | "updateToCurrentURL": 58 | { 59 | "message": "Atualizar para o URL atual" 60 | }, 61 | "edit": 62 | { 63 | "message": "Editar" 64 | }, 65 | "delete": 66 | { 67 | "message": "Eliminar" 68 | }, 69 | "deleteFolderConfirm": 70 | { 71 | "message": "Isso também excluirá todos os favoritos ($1) neste pasta, tem certeza?", 72 | "placeholders": { 73 | "bookmarksNum": { 74 | "content": "$1" 75 | } 76 | } 77 | }, 78 | "rename": 79 | { 80 | "message": "Mudar nome" 81 | }, 82 | 83 | "editBookmark": 84 | { 85 | "message": "Editar marcador" 86 | }, 87 | "editFolderName": 88 | { 89 | "message": "Mudar o nome da pasta" 90 | }, 91 | "save": 92 | { 93 | "message": "Guardar" 94 | }, 95 | "cancel": 96 | { 97 | "message": "Cancelar" 98 | }, 99 | "name": 100 | { 101 | "message": "Nome" 102 | }, 103 | "URL": 104 | { 105 | "message": "URL" 106 | }, 107 | 108 | "themeColor": 109 | { 110 | "message": "Theme color:" 111 | }, 112 | "auto": 113 | { 114 | "message": "Auto" 115 | }, 116 | "light": 117 | { 118 | "message": "Light" 119 | }, 120 | "dark": 121 | { 122 | "message": "Dark" 123 | }, 124 | "openIn": 125 | { 126 | "message": "Open bookmarks in:" 127 | }, 128 | "currentTab": 129 | { 130 | "message": "Current tab" 131 | }, 132 | "newTab": 133 | { 134 | "message": "New tab" 135 | }, 136 | "backgroundTab": 137 | { 138 | "message": "Background tab" 139 | }, 140 | "hoverEnter": { 141 | "message": "Hover to open folder:" 142 | }, 143 | "off": { 144 | "message": "Off" 145 | }, 146 | "slow": { 147 | "message": "Slow" 148 | }, 149 | "moderate": { 150 | "message": "Moderate" 151 | }, 152 | "fast": { 153 | "message": "Fast" 154 | }, 155 | "startupFolder": { 156 | "message": "Startup folder:" 157 | }, 158 | "folderXTitle": { 159 | "message": "Please use the folder right-click menu to set or change startup from a subdirectory..." 160 | }, 161 | "lastFolder": { 162 | "message": "Last folder" 163 | }, 164 | "lastFolderAndScrollBar": { 165 | "message": "Last folder and scrollbar" 166 | }, 167 | "scrollDirection": { 168 | "message": "Scroll Direction:" 169 | }, 170 | "scrollDirectionX": { 171 | "message": "Horizontal" 172 | }, 173 | "scrollDirectionY": { 174 | "message": "Vertical" 175 | }, 176 | "layoutCols": { 177 | "message": "Layout column(s):" 178 | }, 179 | "minItemsEachCol": { 180 | "message": "Items each column:" 181 | }, 182 | "minItemsEachColTips": { 183 | "message": "Note: valid when the number of Layout columns is multiple.\n1 or 10 is recommended" 184 | }, 185 | "alias": { 186 | "message": "Alias:" 187 | }, 188 | "customIcon": { 189 | "message": "Custom icon:" 190 | }, 191 | "selectFile": { 192 | "message": "Select file" 193 | }, 194 | "reset": { 195 | "message": "Reset" 196 | }, 197 | "customIconTip": { 198 | "message": "Note: Please select square, transparent-background image (png format)" 199 | }, 200 | "customCSS": { 201 | "message": "Custom style:" 202 | }, 203 | "example": { 204 | "message": "For example:" 205 | }, 206 | "BackupAndRecovery": { 207 | "message": "Backup and Recovery:" 208 | }, 209 | "import": { 210 | "message": "Import" 211 | }, 212 | "export": { 213 | "message": "Export" 214 | }, 215 | "resetConfigTip": { 216 | "message": "This will clear all configurations, are you sure?" 217 | }, 218 | "setInvalidTips": { 219 | "message": "Invalid option! Optional Settings:" 220 | }, 221 | "Built_inVariables": { 222 | "message": "Built-in variables" 223 | }, 224 | "Built_inVariablesTip": { 225 | "message": "Note: Used to switch/modify minor features within this plugin (or the default behavior of the browser itself)" 226 | } 227 | } -------------------------------------------------------------------------------- /src/_locales/ru/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginDesc": 3 | { 4 | "message": "Простой и удобный в использовании менеджер закладок" 5 | }, 6 | 7 | "bookmarksManager": 8 | { 9 | "message": "Диспетчер закладок" 10 | }, 11 | 12 | "searchPlaceholder": 13 | { 14 | "message": "Искать в закладках..." 15 | }, 16 | "noBookmarksTip": 17 | { 18 | "message": "Папка пуста" 19 | }, 20 | "noSearchResultsTip": 21 | { 22 | "message": "Ничего не найдено" 23 | }, 24 | 25 | "openAll": 26 | { 27 | "message": "Открыть все" 28 | }, 29 | "openInNewTab": 30 | { 31 | "message": "Открыть в новой вкладке" 32 | }, 33 | "openInBackgroundTab": 34 | { 35 | "message": "Открыть в фоновом режиме" 36 | }, 37 | "openInIncognitoWindow": 38 | { 39 | "message": "Открыть в режиме инкогнито" 40 | }, 41 | "addBookmark": 42 | { 43 | "message": "Новая закладка" 44 | }, 45 | "addFolder": 46 | { 47 | "message": "Новая папка" 48 | }, 49 | "showInFolder": 50 | { 51 | "message": "Показать в папке" 52 | }, 53 | "setAsStartupFolder": 54 | { 55 | "message": "Установить в качестве каталога автозагрузки" 56 | }, 57 | "updateToCurrentURL": 58 | { 59 | "message": "Обновление текущего URL-адреса" 60 | }, 61 | "edit": 62 | { 63 | "message": "Изменить" 64 | }, 65 | "delete": 66 | { 67 | "message": "Удалить" 68 | }, 69 | "deleteFolderConfirm": 70 | { 71 | "message": "Это также удалит все закладки ($1) в этом папка, это точно?", 72 | "placeholders": { 73 | "bookmarksNum": { 74 | "content": "$1" 75 | } 76 | } 77 | }, 78 | "rename": 79 | { 80 | "message": "Переименовать" 81 | }, 82 | 83 | "editBookmark": 84 | { 85 | "message": "Изменить закладку" 86 | }, 87 | "editFolderName": 88 | { 89 | "message": "Переименование папки" 90 | }, 91 | "save": 92 | { 93 | "message": "Сохранить" 94 | }, 95 | "cancel": 96 | { 97 | "message": "Отмена" 98 | }, 99 | "name": 100 | { 101 | "message": "Название" 102 | }, 103 | "URL": 104 | { 105 | "message": "URL" 106 | }, 107 | 108 | "themeColor": 109 | { 110 | "message": "Theme color:" 111 | }, 112 | "auto": 113 | { 114 | "message": "Auto" 115 | }, 116 | "light": 117 | { 118 | "message": "Light" 119 | }, 120 | "dark": 121 | { 122 | "message": "Dark" 123 | }, 124 | "openIn": 125 | { 126 | "message": "Open bookmarks in:" 127 | }, 128 | "currentTab": 129 | { 130 | "message": "Current tab" 131 | }, 132 | "newTab": 133 | { 134 | "message": "New tab" 135 | }, 136 | "backgroundTab": 137 | { 138 | "message": "Background tab" 139 | }, 140 | "hoverEnter": { 141 | "message": "Hover to open folder:" 142 | }, 143 | "off": { 144 | "message": "Off" 145 | }, 146 | "slow": { 147 | "message": "Slow" 148 | }, 149 | "moderate": { 150 | "message": "Moderate" 151 | }, 152 | "fast": { 153 | "message": "Fast" 154 | }, 155 | "startupFolder": { 156 | "message": "Startup folder:" 157 | }, 158 | "folderXTitle": { 159 | "message": "Please use the folder right-click menu to set or change startup from a subdirectory..." 160 | }, 161 | "lastFolder": { 162 | "message": "Last folder" 163 | }, 164 | "lastFolderAndScrollBar": { 165 | "message": "Last folder and scrollbar" 166 | }, 167 | "scrollDirection": { 168 | "message": "Scroll Direction:" 169 | }, 170 | "scrollDirectionX": { 171 | "message": "Horizontal" 172 | }, 173 | "scrollDirectionY": { 174 | "message": "Vertical" 175 | }, 176 | "layoutCols": { 177 | "message": "Layout column(s):" 178 | }, 179 | "minItemsEachCol": { 180 | "message": "Items each column:" 181 | }, 182 | "minItemsEachColTips": { 183 | "message": "Note: valid when the number of Layout columns is multiple.\n1 or 10 is recommended" 184 | }, 185 | "alias": { 186 | "message": "Alias:" 187 | }, 188 | "customIcon": { 189 | "message": "Custom icon:" 190 | }, 191 | "selectFile": { 192 | "message": "Select file" 193 | }, 194 | "reset": { 195 | "message": "Reset" 196 | }, 197 | "customIconTip": { 198 | "message": "Note: Please select square, transparent-background image (png format)" 199 | }, 200 | "customCSS": { 201 | "message": "Custom style:" 202 | }, 203 | "example": { 204 | "message": "For example:" 205 | }, 206 | "BackupAndRecovery": { 207 | "message": "Backup and Recovery:" 208 | }, 209 | "import": { 210 | "message": "Import" 211 | }, 212 | "export": { 213 | "message": "Export" 214 | }, 215 | "resetConfigTip": { 216 | "message": "This will clear all configurations, are you sure?" 217 | }, 218 | "setInvalidTips": { 219 | "message": "Invalid option! Optional Settings:" 220 | }, 221 | "Built_inVariables": { 222 | "message": "Built-in variables" 223 | }, 224 | "Built_inVariablesTip": { 225 | "message": "Note: Used to switch/modify minor features within this plugin (or the default behavior of the browser itself)" 226 | } 227 | } -------------------------------------------------------------------------------- /src/_locales/zh_CN/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginDesc": 3 | { 4 | "message": "简单易用的书签管理器" 5 | }, 6 | 7 | "bookmarksManager": 8 | { 9 | "message": "书签管理器" 10 | }, 11 | 12 | "searchPlaceholder": 13 | { 14 | "message": "搜索书签..." 15 | }, 16 | "noBookmarksTip": 17 | { 18 | "message": "此文件夹中没有内容" 19 | }, 20 | "noSearchResultsTip": 21 | { 22 | "message": "未找到任何搜索结果" 23 | }, 24 | 25 | "openAll": 26 | { 27 | "message": "打开全部" 28 | }, 29 | "openInNewTab": 30 | { 31 | "message": "在新标签中打开" 32 | }, 33 | "openInBackgroundTab": 34 | { 35 | "message": "在后台打开" 36 | }, 37 | "openInIncognitoWindow": 38 | { 39 | "message": "在隐身窗口中打开" 40 | }, 41 | "addBookmark": 42 | { 43 | "message": "新建书签" 44 | }, 45 | "addFolder": 46 | { 47 | "message": "新建文件夹" 48 | }, 49 | "showInFolder": 50 | { 51 | "message": "在文件夹中显示" 52 | }, 53 | "setAsStartupFolder": 54 | { 55 | "message": "设为启动目录" 56 | }, 57 | "updateToCurrentURL": 58 | { 59 | "message": "更新为当前网址" 60 | }, 61 | "edit": 62 | { 63 | "message": "编辑" 64 | }, 65 | "delete": 66 | { 67 | "message": "删除" 68 | }, 69 | "deleteFolderConfirm": 70 | { 71 | "message": "这还将删除此文件夹下的所有书签($bookmarksNum$个)\n确定吗?", 72 | "placeholders": { 73 | "bookmarksNum": { 74 | "content": "$1" 75 | } 76 | } 77 | }, 78 | "rename": 79 | { 80 | "message": "重命名" 81 | }, 82 | 83 | "editBookmark": 84 | { 85 | "message": "编辑书签" 86 | }, 87 | "editFolderName": 88 | { 89 | "message": "重命名文件夹" 90 | }, 91 | "save": 92 | { 93 | "message": "保存" 94 | }, 95 | "cancel": 96 | { 97 | "message": "取消" 98 | }, 99 | "name": 100 | { 101 | "message": "名称" 102 | }, 103 | "URL": 104 | { 105 | "message": "网址" 106 | }, 107 | 108 | "themeColor": 109 | { 110 | "message": "主题色:" 111 | }, 112 | "auto": 113 | { 114 | "message": "跟随系统" 115 | }, 116 | "light": 117 | { 118 | "message": "浅色" 119 | }, 120 | "dark": 121 | { 122 | "message": "深色" 123 | }, 124 | "openIn": 125 | { 126 | "message": "书签打开方式:" 127 | }, 128 | "currentTab": 129 | { 130 | "message": "当前页" 131 | }, 132 | "newTab": 133 | { 134 | "message": "新标签" 135 | }, 136 | "backgroundTab": 137 | { 138 | "message": "新标签(后台)" 139 | }, 140 | "hoverEnter": { 141 | "message": "目录悬停进入:" 142 | }, 143 | "off": { 144 | "message": "关闭" 145 | }, 146 | "slow": { 147 | "message": "缓慢" 148 | }, 149 | "moderate": { 150 | "message": "适中" 151 | }, 152 | "fast": { 153 | "message": "快速" 154 | }, 155 | "startupFolder": { 156 | "message": "启动位置:" 157 | }, 158 | "folderXTitle": { 159 | "message": "请通过文件夹右键菜单设置或更改从子目录启动..." 160 | }, 161 | "lastFolder": { 162 | "message": "上次目录" 163 | }, 164 | "lastFolderAndScrollBar": { 165 | "message": "上次目录和滚动条" 166 | }, 167 | "scrollDirection": { 168 | "message": "滚动方向:" 169 | }, 170 | "scrollDirectionX": { 171 | "message": "横向" 172 | }, 173 | "scrollDirectionY": { 174 | "message": "纵向" 175 | }, 176 | "layoutCols": { 177 | "message": "布局列数:" 178 | }, 179 | "minItemsEachCol": { 180 | "message": "每列条数:" 181 | }, 182 | "minItemsEachColTips": { 183 | "message": "注:布局列数为多列时有效;推荐1或者10" 184 | }, 185 | "alias": { 186 | "message": "别名:" 187 | }, 188 | "customIcon": { 189 | "message": "自定义图标:" 190 | }, 191 | "selectFile": { 192 | "message": "选择文件" 193 | }, 194 | "reset": { 195 | "message": "重置" 196 | }, 197 | "customIconTip": { 198 | "message": "注:请选择方形、透明背景图片(png格式)" 199 | }, 200 | "customCSS": { 201 | "message": "自定义样式:" 202 | }, 203 | "example": { 204 | "message": "例如:" 205 | }, 206 | "BackupAndRecovery": { 207 | "message": "备份与恢复:" 208 | }, 209 | "import": { 210 | "message": "导入" 211 | }, 212 | "export": { 213 | "message": "导出" 214 | }, 215 | "resetConfigTip": { 216 | "message": "这将清空所有配置,确定吗?" 217 | }, 218 | "setInvalidTips": { 219 | "message": "无效选项!可选设置:" 220 | }, 221 | "Built_inVariables": { 222 | "message": "内置参数:" 223 | }, 224 | "Built_inVariablesTip": { 225 | "message": "注:用于 开关/修改 本插件内的小众功能(或者浏览器本身的默认行为)" 226 | } 227 | } -------------------------------------------------------------------------------- /src/_locales/zh_TW/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginDesc": 3 | { 4 | "message": "簡單易用的書籤管理器" 5 | }, 6 | 7 | "bookmarksManager": 8 | { 9 | "message": "書籤管理器" 10 | }, 11 | 12 | "searchPlaceholder": 13 | { 14 | "message": "搜索書籤..." 15 | }, 16 | "noBookmarksTip": 17 | { 18 | "message": "此文件夾中沒有內容" 19 | }, 20 | "noSearchResultsTip": 21 | { 22 | "message": "未找到任何搜索結果" 23 | }, 24 | 25 | "openAll": 26 | { 27 | "message": "打開全部" 28 | }, 29 | "openInNewTab": 30 | { 31 | "message": "在新標籤中打開" 32 | }, 33 | "openInBackgroundTab": 34 | { 35 | "message": "在後台打開" 36 | }, 37 | "openInIncognitoWindow": 38 | { 39 | "message": "在隱身窗口中打開" 40 | }, 41 | "addBookmark": 42 | { 43 | "message": "新建書籤" 44 | }, 45 | "addFolder": 46 | { 47 | "message": "新建文件夾" 48 | }, 49 | "showInFolder": 50 | { 51 | "message": "在文件夾中顯示" 52 | }, 53 | "setAsStartupFolder": 54 | { 55 | "message": "設為啟動目錄" 56 | }, 57 | "updateToCurrentURL": 58 | { 59 | "message": "更新為當前網址" 60 | }, 61 | "edit": 62 | { 63 | "message": "編輯" 64 | }, 65 | "delete": 66 | { 67 | "message": "刪除" 68 | }, 69 | "deleteFolderConfirm": 70 | { 71 | "message": "這還將刪除此文件夾下的所有書籤($bookmarksNum$個)\n確定嗎?", 72 | "placeholders": { 73 | "bookmarksNum": { 74 | "content": "$1" 75 | } 76 | } 77 | }, 78 | "rename": 79 | { 80 | "message": "重命名" 81 | }, 82 | 83 | "editBookmark": 84 | { 85 | "message": "編輯書籤" 86 | }, 87 | "editFolderName": 88 | { 89 | "message": "重命名文件夾" 90 | }, 91 | "save": 92 | { 93 | "message": "保存" 94 | }, 95 | "cancel": 96 | { 97 | "message": "取消" 98 | }, 99 | "name": 100 | { 101 | "message": "名稱" 102 | }, 103 | "URL": 104 | { 105 | "message": "網址" 106 | }, 107 | 108 | "themeColor": 109 | { 110 | "message": "主題色:" 111 | }, 112 | "auto": 113 | { 114 | "message": "跟隨系統" 115 | }, 116 | "light": 117 | { 118 | "message": "淺色" 119 | }, 120 | "dark": 121 | { 122 | "message": "深色" 123 | }, 124 | "openIn": 125 | { 126 | "message": "書籤打開方式:" 127 | }, 128 | "currentTab": 129 | { 130 | "message": "當前頁" 131 | }, 132 | "newTab": 133 | { 134 | "message": "新標籤" 135 | }, 136 | "backgroundTab": 137 | { 138 | "message": "新標籤(後臺)" 139 | }, 140 | "hoverEnter": { 141 | "message": "目錄懸停進入:" 142 | }, 143 | "off": { 144 | "message": "關閉" 145 | }, 146 | "slow": { 147 | "message": "緩慢" 148 | }, 149 | "moderate": { 150 | "message": "適中" 151 | }, 152 | "fast": { 153 | "message": "快速" 154 | }, 155 | "startupFolder": { 156 | "message": "啟動位置:" 157 | }, 158 | "folderXTitle": { 159 | "message": "請通過文件夾右鍵菜單設置或更改從子目錄啟動..." 160 | }, 161 | "lastFolder": { 162 | "message": "上次目錄" 163 | }, 164 | "lastFolderAndScrollBar": { 165 | "message": "上次目錄和滾動條" 166 | }, 167 | "scrollDirection": { 168 | "message": "滾動方向:" 169 | }, 170 | "scrollDirectionX": { 171 | "message": "橫向" 172 | }, 173 | "scrollDirectionY": { 174 | "message": "縱向" 175 | }, 176 | "layoutCols": { 177 | "message": "佈局列數:" 178 | }, 179 | "minItemsEachCol": { 180 | "message": "每列條數:" 181 | }, 182 | "minItemsEachColTips": { 183 | "message": "注:佈局列數為多列時有效;推薦1或者10" 184 | }, 185 | "alias": { 186 | "message": "別名:" 187 | }, 188 | "customIcon": { 189 | "message": "自定義圖標:" 190 | }, 191 | "selectFile": { 192 | "message": "選擇文件" 193 | }, 194 | "reset": { 195 | "message": "重置" 196 | }, 197 | "customIconTip": { 198 | "message": "注:請選擇方形、透明背景圖片(png格式)" 199 | }, 200 | "customCSS": { 201 | "message": "自定義樣式:" 202 | }, 203 | "example": { 204 | "message": "例如:" 205 | }, 206 | "BackupAndRecovery": { 207 | "message": "備份與恢復:" 208 | }, 209 | "import": { 210 | "message": "匯入" 211 | }, 212 | "export": { 213 | "message": "匯出" 214 | }, 215 | "resetConfigTip": { 216 | "message": "這將清空所有配置,確定嗎?" 217 | }, 218 | "setInvalidTips": { 219 | "message": "無效選項!可選設置:" 220 | }, 221 | "Built_inVariables": { 222 | "message": "內置參數:" 223 | }, 224 | "Built_inVariablesTip": { 225 | "message": "注:用於 開關/修改 本插件內的小衆功能(或者瀏覽器本身的默認行爲)" 226 | } 227 | } -------------------------------------------------------------------------------- /src/css/common.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --body-width: 720px; 3 | --base-height: 42px; 4 | --primary-color: #1abc9c; 5 | } 6 | html::-webkit-scrollbar { 7 | display: none; 8 | } 9 | /* 页面内的输入框滚动条需要显示 */ 10 | textarea::-webkit-scrollbar { 11 | width: 6px; 12 | } 13 | #customCSS::-webkit-scrollbar-thumb { 14 | border-radius: 6px; 15 | background-color: rgba(50, 50, 50, .4); 16 | } 17 | * { 18 | margin: 0; 19 | box-sizing: border-box; 20 | } 21 | body { 22 | margin: 0 auto; 23 | width: var(--body-width); 24 | min-height: 100vh; 25 | font-size: 16px; 26 | box-shadow: 0 0 60px rgb(0 0 0 / 10%); 27 | } 28 | .btns { 29 | position: fixed; 30 | top: 4px; 31 | display: flex; 32 | gap: 9px; 33 | } 34 | /* inset-inline-end不兼容chrome87以下版本 */ 35 | html:not([dir="rtl"]) .btns { 36 | right: calc(50vw - var(--body-width) / 2 + 30px); 37 | } 38 | html[dir="rtl"] .btns { 39 | left: calc(50vw - var(--body-width) / 2 + 30px); 40 | } 41 | 42 | .btns img { 43 | width: 24px; 44 | } 45 | 46 | .btns:hover > .item { 47 | display: block; 48 | } 49 | .list, 50 | .item { 51 | user-select: none; 52 | opacity: .5; 53 | } 54 | .item { 55 | display: none; 56 | border-bottom: 2px solid transparent; 57 | } 58 | .item.active { 59 | opacity: 1; 60 | pointer-events: none; 61 | } 62 | .item:hover { 63 | opacity: 1; 64 | border-color: var(--primary-color); 65 | } 66 | 67 | .hash-tab { 68 | display: none; 69 | padding: 15px 30px; 70 | min-height: 100vh; 71 | } 72 | .hash-tab.active { 73 | display: block; 74 | } 75 | -------------------------------------------------------------------------------- /src/css/configTable.css: -------------------------------------------------------------------------------- 1 | .page-title { 2 | margin-bottom: 4px; 3 | font-size: 1.4em; 4 | font-weight: bold; 5 | color: #333; 6 | } 7 | 8 | table { 9 | margin-top: 12px; 10 | table-layout: fixed; 11 | width: 100%; 12 | min-width: 644px; 13 | font-family: sans-serif; 14 | font-size: 15px; 15 | text-align: center; 16 | border: 1px solid #dfdedf; 17 | border-radius: 4px; 18 | border-spacing: 0; 19 | } 20 | 21 | thead { 22 | background: #c7c6c6; 23 | } 24 | tr { 25 | line-height: 1.5; 26 | } 27 | tbody > tr:hover { 28 | background-color: #ccdff9; 29 | } 30 | tr:nth-child(even) { 31 | background-color: #f2f2f2; 32 | } 33 | 34 | th, td { 35 | padding: 3px; 36 | border: 1px solid transparent; 37 | } 38 | td:first-child { 39 | padding-inline-start: 30px; 40 | text-align: start; 41 | } 42 | 43 | .cell-info { 44 | display: flex; 45 | align-items: center; 46 | justify-content: center; 47 | text-indent: 1.6em; 48 | } 49 | .cell-info > a { 50 | display: flex; 51 | transform: scale(0.7); 52 | opacity: .5; 53 | } 54 | 55 | .cell-operation, 56 | .cell-reset { 57 | width: 40px; 58 | } 59 | 60 | .has-user-value { 61 | font-weight: bold; 62 | } 63 | 64 | .has-user-value > .cell-reset { 65 | background: url("/icons/undo.svg") no-repeat center; 66 | cursor: pointer; 67 | } 68 | .has-user-value > .cell-reset:hover { 69 | background-color: #93bbf2; 70 | } 71 | 72 | .cell-value { 73 | position: relative; 74 | } 75 | 76 | #cTable-input { 77 | position: absolute; 78 | top: 0; 79 | left: 0; 80 | width: 100%; 81 | height: 100%; 82 | font-family: inherit; 83 | font-size: inherit; 84 | text-align: inherit; 85 | border: none; 86 | outline: 1px solid var(--primary-color); 87 | } 88 | 89 | -------------------------------------------------------------------------------- /src/css/markdown.css: -------------------------------------------------------------------------------- 1 | /* 根据该字体大小,统一调整文章整体大小 */ 2 | /* 间距,其他字体,代码块以及注脚 */ 3 | /* 不会控制微信代码块 */ 4 | #usage { 5 | margin-top: -15px; 6 | font-size: 16px; 7 | line-height: 1.6; 8 | letter-spacing: 0px; 9 | word-spacing: 0px; 10 | word-break: break-word; 11 | word-wrap: break-word; 12 | text-align: justify; 13 | font-family: -apple-system,system-ui,BlinkMacSystemFont,Helvetica Neue,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Arial,sans-serif; 14 | } 15 | 16 | /* 颜色管理 */ 17 | #usage a { 18 | color: #3e64ff; 19 | } 20 | #usage code { 21 | color: var(--primary-color); 22 | background-color: #efefef; 23 | font-size: 14px; 24 | word-wrap: break-word; 25 | padding: 2px 4px; 26 | border-radius: 4px; 27 | margin: 0 2px; 28 | font-family: Operator Mono, Consolas, Monaco, Menlo, monospace; 29 | } 30 | /* 避免被翻译(但保持原来样式) */ 31 | #usage strong > code { 32 | color: unset; 33 | background: none; 34 | font-size: 16px; 35 | } 36 | #usage .footnote-word, 37 | #usage .footnote-ref { 38 | color: #004a7c; 39 | } 40 | #usage .footnote-item em { 41 | color: #004a7c; 42 | } 43 | 44 | /* 统一间距 */ 45 | #usage p, 46 | #usage section, 47 | #usage h1, 48 | #usage h2, 49 | #usage h3, 50 | #usage h4, 51 | #usage h5, 52 | #usage h6, 53 | #usage pre, 54 | #usage figure, 55 | #usage ul, 56 | #usage hr { 57 | margin: 1em 0; 58 | color: #333; 59 | } 60 | 61 | #usage * { 62 | line-height: 1.6 !important; 63 | } 64 | 65 | #usage hr { 66 | border-top: 1px solid #dfe2e5 67 | } 68 | 69 | /* 标题调整 */ 70 | #usage h1 { 71 | font-size: 1.4em; 72 | } 73 | #usage h2 { 74 | font-size: 1.3em; 75 | } 76 | #usage h1, 77 | #usage h2 { 78 | padding-bottom: .3em; 79 | border-bottom: 1px solid #dfe2e5; 80 | } 81 | #usage h3 { 82 | font-size: 1.2em; 83 | } 84 | #usage h4 { 85 | font-size: 1.1em; 86 | } 87 | #usage h5 { 88 | font-size: 1em; 89 | } 90 | #usage h6 { 91 | font-size: 1em; 92 | } 93 | 94 | /* 段落调整 */ 95 | #usage p { 96 | padding: 0; 97 | font-size: inherit; 98 | color: #333; 99 | } 100 | #usage blockquote { 101 | background: none; 102 | border-left: 4px solid #ddd; 103 | padding: 0 1em; 104 | } 105 | #usage blockquote p { 106 | margin: 0; 107 | color: #666; 108 | } 109 | #usage strong { 110 | color: #333; 111 | } 112 | 113 | #usage a { 114 | font-weight: normal; 115 | border-color: inherit; 116 | text-decoration: none; 117 | border-bottom: 1px dotted; 118 | padding-bottom: 1px; 119 | } 120 | 121 | /* 列表调整 */ 122 | #usage li section { 123 | margin-top: .3em; 124 | margin-bottom: .3em; 125 | font-weight: normal; 126 | } 127 | #usage li ul { 128 | margin: 0; 129 | } 130 | 131 | /* 代码块调整 */ 132 | #usage pre { 133 | border-radius: 4px; 134 | } 135 | #usage section pre { 136 | margin: 0; 137 | padding-top: 0; 138 | padding-bottom: 0; 139 | } 140 | #usage p code { 141 | font-size: .875em; 142 | } 143 | #usage pre code { 144 | font-size: .875em; 145 | } 146 | 147 | #usage figure img { 148 | max-width: 100%; 149 | width: auto; 150 | margin: 0 auto; 151 | } 152 | 153 | /* table 调整 */ 154 | #usage table tr th, 155 | #usage table tr td { 156 | font-size: 1em; 157 | } 158 | 159 | /* 注脚调整 */ 160 | #usage .footnotes-sep { 161 | font-size: 1.3em; 162 | } 163 | #usage .footnote-item { 164 | margin: .4em 0; 165 | } 166 | #usage .footnote-item * { 167 | line-height: 1.4 !important; 168 | } 169 | #usage .footnote-item p { 170 | margin: 0; 171 | font-size: .9em; 172 | } 173 | #usage .footnote-item em { 174 | font-style: normal; 175 | padding-inline-start: .5em; 176 | } 177 | #usage .footnote-item span { 178 | flex: 0 0 1.5em; 179 | margin-inline-end: 10px; 180 | font-size: .9em; 181 | } 182 | -------------------------------------------------------------------------------- /src/css/options.css: -------------------------------------------------------------------------------- 1 | #options { 2 | padding-bottom: 0; 3 | } 4 | hr { 5 | width: 100%; 6 | border-width: 0; 7 | } 8 | .ul_box { 9 | list-style: none; 10 | } 11 | .ul_box > li { 12 | display: flex; 13 | padding: 5px; 14 | border-bottom: 1px solid #e5e5e5; 15 | align-items: center; 16 | } 17 | .ul_box > li:last-child { 18 | border-bottom: none; 19 | } 20 | .ul_box > li > div:nth-child(1) { 21 | min-width: 160px; 22 | text-align: right; 23 | padding-inline-end: 5px; 24 | } 25 | 26 | .label_box { 27 | display: flex; 28 | flex-wrap: wrap; 29 | } 30 | .label_box > label { 31 | display: block; 32 | float: left; 33 | margin: 5px; 34 | position: relative; 35 | } 36 | .label_box > label > input { 37 | position: absolute; 38 | display: none; 39 | } 40 | .label_box > label > span { 41 | display: block; 42 | min-width: 85px; 43 | width: fit-content; 44 | padding: 0 10px; 45 | text-align: center; 46 | border: 1px solid #ddd; 47 | height: var(--base-height); 48 | line-height: var(--base-height); 49 | color: #666; 50 | user-select: none; 51 | position: relative; 52 | } 53 | .compact > label > span { 54 | min-width: 40px; 55 | } 56 | 57 | .disabled { 58 | background-color: #ccc; 59 | } 60 | .label_box > label > input:checked + span { 61 | border: 1px solid var(--primary-color); 62 | color: var(--primary-color) !important; 63 | } 64 | .label_box > label > input:checked + span:after { 65 | content: ''; 66 | position: absolute; 67 | bottom: -15px; 68 | right: -15px; 69 | border: 15px solid; 70 | border-color: transparent transparent var(--primary-color); 71 | z-index: 1; 72 | transform: rotate(135deg); 73 | } 74 | .label_box > label > input:checked + span:before { 75 | content: ''; 76 | position: absolute; 77 | bottom: 2px; 78 | right: 4px; 79 | width: 3px; 80 | height: 8px; 81 | border-right: 2px solid #fff; 82 | border-bottom: 2px solid #fff; 83 | transform: rotate(35deg); 84 | z-index: 2; 85 | } 86 | 87 | .input_box > input, 88 | .input_box > textarea { 89 | outline: none; 90 | padding: 0 .25em; 91 | font-size: 14px; 92 | border: 1px solid #ddd; 93 | font-family: 'Fira Code,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace'; 94 | } 95 | .input_box > input { 96 | margin: 5px; 97 | height: 30px; 98 | width: 190px; 99 | } 100 | .input_box > textarea { 101 | margin: 0 5px; 102 | width: 465px; 103 | } 104 | #minItemsPerCol { 105 | width: 90px; 106 | } 107 | 108 | .icon_preview { 109 | float: left; 110 | margin: 5px; 111 | width: 50px; 112 | height: 50px; 113 | background-position: center; 114 | background-image: repeating-linear-gradient(45deg, #999, #eee 1%, #eee 6%); 115 | background-size: 50px; 116 | background-repeat: no-repeat; 117 | } 118 | .button { 119 | user-select: none; 120 | float: left; 121 | margin: 15px 5px; 122 | padding: .15em .5em; 123 | border: 1px solid #777; 124 | border-radius: 4px; 125 | background-color: #f5f5f5; 126 | cursor: pointer; 127 | } 128 | .button:hover { 129 | background-color: #1abc9c48; 130 | } 131 | html[lang=en] .button { 132 | margin: 1px 5px !important; 133 | } 134 | .note { 135 | font-size: 13px; 136 | color: #555; 137 | } 138 | .whitespace-pre { 139 | white-space: pre; 140 | } 141 | 142 | .tooltip ~ .tooltiptext { 143 | visibility: hidden; 144 | position: absolute; 145 | bottom: 120%; 146 | padding: 0 10px; 147 | width: max-content; 148 | border-radius: 6px; 149 | font-size: 13px; 150 | color: #fff; 151 | background-color: #444; 152 | z-index: 9999; 153 | transition: visibility .2s ease-in .3s; 154 | } 155 | .tooltip ~ .tooltiptext::after { 156 | content: ""; 157 | position: absolute; 158 | top: 100%; 159 | left: 40px; 160 | border-width: 5px; 161 | border-style: solid; 162 | border-color: #444 transparent transparent transparent; 163 | } 164 | .tooltip:hover ~ .tooltiptext { 165 | visibility: visible; 166 | } 167 | 168 | #customCSS { 169 | resize: vertical; 170 | } 171 | 172 | #resetBtn { 173 | position: absolute; 174 | right: 1.25em; 175 | } 176 | html[dir="rtl"] #resetBtn { 177 | right: unset; 178 | left: 1.25em; 179 | } -------------------------------------------------------------------------------- /src/css/popup.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --height-item: 28px; 3 | --body-width-cols: 1; 4 | --list-cols: 1; 5 | --list-rows: 10; 6 | --base-font-size: 14px; 7 | --scrollbar-width: 4px; 8 | --direction-ratio: 1; 9 | 10 | /* 浅色模式 */ 11 | --color-theme: #4db6ac; 12 | --color-text: #15141a; 13 | --color-nav-text: #fff; 14 | --color-background: #fff; 15 | --color-border: #ddd; 16 | --color-hover: #cbede9; 17 | --color-scrollbar-thumb: #b8c8c6; 18 | --color-textbox-border: #a0b3d6; 19 | --color-textbox-focus-border: #34538b; 20 | --color-input-background: #fff; 21 | --color-footer-background: #eee; 22 | --color-mask-background: rgba(0, 0, 0, .5); 23 | } 24 | .dark { 25 | --color-theme: #369; 26 | --color-text: #cbc8c4; 27 | --color-nav-text: #e3e3e3; 28 | --color-background: #1c1b22; 29 | --color-border: #666; 30 | --color-hover: #263f59; 31 | --color-scrollbar-thumb: #8ac; 32 | --color-textbox-border: #666; 33 | --color-textbox-focus-border: #8cb4ff; 34 | --color-input-background: #000; 35 | --color-footer-background: #333; 36 | --color-mask-background: rgba(0, 0, 0, .66); 37 | } 38 | :root[dir="rtl"] { 39 | --direction-ratio: -1; 40 | } 41 | body::-webkit-scrollbar { 42 | width: 0; 43 | } 44 | ::-webkit-scrollbar { 45 | width: var(--scrollbar-width); 46 | height: var(--scrollbar-width); 47 | } 48 | ::-webkit-scrollbar-track { 49 | background: transparent; 50 | } 51 | ::-webkit-scrollbar-thumb { 52 | border-radius: 1px; 53 | background-color: var(--color-scrollbar-thumb); 54 | } 55 | ::selection { 56 | background: var(--color-hover); 57 | } 58 | * { 59 | box-sizing: border-box; 60 | } 61 | .hidden { 62 | display: none !important; 63 | } 64 | 65 | body { 66 | margin: 0; 67 | padding: 0; 68 | width: 280px; 69 | color: var(--color-text); 70 | background-color: var(--color-background); 71 | user-select: none; 72 | transition: width .05s; 73 | } 74 | header, 75 | footer { 76 | padding: 0 8px; 77 | } 78 | header { 79 | position: relative; 80 | padding-bottom: 4px; 81 | border-bottom: 1px solid var(--color-border); 82 | } 83 | main { 84 | position: relative; 85 | padding: 0; 86 | max-height: 476px; 87 | min-height: 400px; 88 | overflow: auto; 89 | } 90 | footer { 91 | width: inherit; 92 | height: 30px; 93 | display: flex; 94 | align-items: center; 95 | gap: 4px; 96 | color: var(--color-text); 97 | background-color: var(--color-footer-background); 98 | } 99 | 100 | .mask { 101 | position: fixed; 102 | top: 0; 103 | right: 0; 104 | bottom: 0; 105 | left: 0; 106 | background-color: var(--color-mask-background); 107 | transition: all .3s ease-out; 108 | z-index: 1; 109 | } 110 | 111 | input { 112 | outline: none; 113 | color: var(--color-text); 114 | border: 1px solid; 115 | background-color: var(--color-input-background); 116 | } 117 | 118 | input::placeholder, 119 | .textbox:empty::before { 120 | color: currentColor; 121 | opacity: .5; 122 | } 123 | 124 | button { 125 | outline: none; 126 | cursor: pointer; 127 | padding: .15em .5em; 128 | border: 1px solid var(--color-textbox-border); 129 | border-bottom-width: 2px; 130 | border-radius: 3px; 131 | background-color: transparent; 132 | color: var(--color-text); 133 | letter-spacing: 1px; 134 | text-indent: 1px; 135 | } 136 | button:hover, 137 | button:focus { 138 | color: #f0e9f6; 139 | background-color: #1a73e8; 140 | } 141 | button:active { 142 | outline: 1px solid var(--color-textbox-focus-border); 143 | } 144 | 145 | a { 146 | text-decoration: none; 147 | cursor: pointer; 148 | } 149 | 150 | nav { 151 | /* 8px 扩大点击区域 */ 152 | margin: 5px -8px; 153 | padding: 0 8px; 154 | min-height: 24px; 155 | line-height: 1.5em; 156 | display: flex; 157 | flex-wrap: wrap; 158 | gap: 4px; 159 | font-size: 16px; 160 | color: #789; 161 | word-break: keep-all; 162 | } 163 | #bookmark-manager, 164 | .nav { 165 | font-size: 15px; 166 | font-weight: bold; 167 | color: var(--color-theme); 168 | } 169 | .nav:empty, 170 | .nav:not(:empty) + #bookmark-manager { 171 | display: none; 172 | } 173 | nav > a { 174 | padding: 0 4px; 175 | border-radius: 4px; 176 | color: var(--color-nav-text); 177 | background-color: var(--color-theme); 178 | } 179 | 180 | nav > a:last-of-type { 181 | pointer-events: none; 182 | } 183 | i { 184 | display: inline-block; 185 | width: .65em; 186 | font-style: normal; 187 | text-align: center; 188 | } 189 | .folder-list, 190 | #search-list { 191 | display: grid; 192 | grid-auto-flow: column; 193 | grid-template-rows: repeat(var(--list-rows), 1fr); 194 | grid-template-columns: repeat(var(--list-cols), calc(100% / var(--body-width-cols))); 195 | font-size: var(--base-font-size); 196 | } 197 | .folder-list[hidden], 198 | #search-list[hidden] { 199 | display: none; 200 | } 201 | 202 | .folder-list:empty::after, 203 | #search-list:empty::after { 204 | position: absolute; 205 | right: 0; 206 | top: 0; 207 | left: 0; 208 | bottom: 0; 209 | margin: 0; 210 | display: flex; 211 | justify-content: center; 212 | align-items: center; 213 | box-sizing: border-box; 214 | opacity: .8; 215 | } 216 | .folder-list.show-tip:empty::after { 217 | content: "__MSG_noBookmarksTip__"; 218 | } 219 | #search-list:empty::after { 220 | content: "__MSG_noSearchResultsTip__"; 221 | } 222 | 223 | #search-input { 224 | width: 100%; 225 | height: 24px; 226 | border: 1px solid var(--color-border); 227 | border-radius: 2px; 228 | } 229 | #search-input::-webkit-search-cancel-button { 230 | transform: translateY(-1px); 231 | cursor: pointer; 232 | } 233 | 234 | .item { 235 | position: relative; 236 | padding: 0 8px; 237 | height: var(--height-item); 238 | line-height: var(--height-item); 239 | } 240 | .item:hover, 241 | .item:focus, 242 | .item.active, 243 | .item.selected { 244 | background-color: var(--color-hover); 245 | } 246 | 247 | .favicon { 248 | position: absolute; 249 | height: 16px; 250 | top: 50%; 251 | transform: translateY(-50%); 252 | } 253 | .folder-list .favicon { 254 | cursor: move; 255 | } 256 | .item > a { 257 | display: block; 258 | padding-inline-start: 24px; 259 | /* 防止空名称文件夹不能打开 */ 260 | height: 100%; 261 | overflow:hidden; 262 | text-overflow:ellipsis; 263 | white-space:nowrap; 264 | } 265 | #bookmark-manager + a { 266 | margin-inline-start: auto; 267 | } 268 | .btn { 269 | display: flex; 270 | align-items: center; 271 | outline: none; 272 | } 273 | 274 | .btn svg { 275 | opacity: .51; 276 | width: 20px; 277 | height: 20px; 278 | } 279 | .btn:hover svg { 280 | opacity: 1; 281 | } 282 | #star-url { 283 | transform: translateX(calc(-2px * var(--direction-ratio))); 284 | } 285 | 286 | .box { 287 | position: absolute; 288 | margin: 0; 289 | padding: 5px; 290 | font-size: var(--base-font-size); 291 | border-radius: 5px; 292 | background-color: var(--color-background); 293 | box-shadow: 0 2px 6px rgba(0, 0, 0, .2); 294 | outline: none; 295 | } 296 | 297 | #context-menu { 298 | top:0; 299 | left:0; 300 | min-width: 120px; 301 | max-width: 100%; 302 | font-size: calc(var(--base-font-size) - 1px); 303 | list-style: none; 304 | border: 1px solid #bbb; 305 | } 306 | #dialog, 307 | #confirm { 308 | left: 50%; 309 | width: 96%; 310 | max-width: 360px; 311 | line-height: 2em; 312 | border: 1px solid var(--color-border); 313 | transform: translateX(-50%); 314 | z-index: 9999; 315 | } 316 | #confirm { 317 | top: 25vh; 318 | } 319 | 320 | #context-menu > li { 321 | display: none; 322 | padding: 0 8px; 323 | line-height: 22px; 324 | overflow: hidden; 325 | white-space: nowrap; 326 | text-overflow: ellipsis; 327 | text-shadow: none; 328 | } 329 | #context-menu > li:hover { 330 | background: var(--color-hover); 331 | } 332 | hr { 333 | display: none; 334 | border: 0; 335 | padding: 0; 336 | height: 1px; 337 | margin: 2px 0; 338 | background-color: var(--color-border); 339 | pointer-events: none; 340 | } 341 | 342 | .folder > #bookmark-add-bookmark, 343 | .folder > #bookmark-add-folder, 344 | .folder > #bookmark-set-as-startup, 345 | .folder > #bookmark-open-all, 346 | .folder > #bookmark-edit-folder, 347 | .folder > #bookmark-delete, 348 | .folder > hr { 349 | display: block; 350 | } 351 | .link > #bookmark-new-tab, 352 | .link > #bookmark-new-tab-background, 353 | .link > #bookmark-new-incognito-window, 354 | .link > #bookmark-add-bookmark, 355 | .link > #bookmark-add-folder, 356 | .link > #bookmark-update-url, 357 | .link > #bookmark-edit, 358 | .link > #bookmark-delete, 359 | .link > hr { 360 | display: block; 361 | } 362 | .nodata > #bookmark-add-bookmark, 363 | .nodata > #bookmark-add-folder { 364 | display: block; 365 | } 366 | 367 | [type=search] > #bookmark-add-bookmark, 368 | [type=search] > #bookmark-add-folder { 369 | display: none; 370 | } 371 | #bookmark-location { 372 | display: none !important; 373 | } 374 | [type=search] > #bookmark-location { 375 | display: block !important; 376 | } 377 | .nodata[type=search] { 378 | display: none; 379 | } 380 | 381 | 382 | .textbox { 383 | outline: none; 384 | margin: 10px 0; 385 | width: 100%; 386 | border: 1px solid var(--color-border); 387 | border-radius: 3px; 388 | color: var(--color-text); 389 | background-color: var(--color-input-background); 390 | word-break: break-all; 391 | overflow-x: hidden; 392 | overflow-y: auto; 393 | -webkit-user-modify: read-write-plaintext-only; 394 | } 395 | .textbox:empty::before { 396 | content: attr(placeholder); 397 | } 398 | 399 | .dialog-btns { 400 | display: flex; 401 | justify-content: space-evenly; 402 | } 403 | #dialog input:focus, 404 | #dialog .textbox:focus { 405 | border-bottom-color: var(--color-textbox-focus-border); 406 | } 407 | 408 | #edit-dialog-text, 409 | #confirm-title { 410 | text-align: center; 411 | font-size: 1.3em; 412 | } 413 | #edit-dialog-name { 414 | padding: 1px 3px; 415 | max-height: 15vh; 416 | } 417 | #edit-dialog-url { 418 | padding: 3px; 419 | max-height: 60vh; 420 | } 421 | .title { 422 | padding: 1px 10px; 423 | border-radius: 5px; 424 | background-color: var(--color-hover); 425 | } 426 | #confirm-content { 427 | margin: 10px 3px; 428 | text-align: center; 429 | white-space: pre-line; 430 | } 431 | 432 | /* 深色模式 */ 433 | .dark .favicon { 434 | filter: brightness(0.8) contrast(1.25); 435 | } 436 | 437 | .dark .item:hover, 438 | .dark .item.active { 439 | color: #eee; 440 | } 441 | .dark svg { 442 | fill: var(--color-text); 443 | } -------------------------------------------------------------------------------- /src/eventPage.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | // console.log('eventPage'); 3 | 4 | // default_icon 设为透明图标,但效果不好 5 | chrome.runtime.onStartup.addListener(() => { 6 | chrome.storage.local.get('customIcon', items => { 7 | var iconBase64 = items.customIcon; 8 | if (iconBase64) { 9 | chrome.action.setIcon({path: iconBase64}); 10 | } 11 | }); 12 | }); 13 | 14 | function setRootInfo() { 15 | return new Promise(resolve => { 16 | chrome.bookmarks.getChildren('0', resolve) 17 | }).then(results => { 18 | const rootInfo = { 19 | 1: results[0].title, 20 | 2: results[1].title, 21 | } 22 | 23 | return new Promise(resolve => 24 | chrome.storage.sync.set({ rootInfo }, resolve) 25 | ); 26 | }) 27 | } 28 | 29 | function updataOldData() { 30 | // v1.7.2 ↑ 31 | // 从上次位置启动 改为 从任意目录启动 32 | localStorage.removeItem('version'); 33 | var iconBase64 = localStorage.customIcon; 34 | if (iconBase64) { 35 | // console.log(iconBase64); 36 | chrome.storage.local.set({customIcon: iconBase64}); 37 | localStorage.removeItem('customIcon'); 38 | } 39 | } 40 | 41 | // 安装、更新 42 | chrome.runtime.onInstalled.addListener((details) => { 43 | if (!['install', 'update'].includes(details.reason)) return; 44 | 45 | if (details.reason === 'install') { 46 | setRootInfo(); 47 | } else { 48 | try { 49 | updataOldData(); 50 | } catch {} 51 | } 52 | }); 53 | 54 | chrome.runtime.onInstalled.addListener(() => { 55 | chrome.contextMenus.create({ 56 | id: 'open-bookmarks-manager', 57 | title: chrome.i18n.getMessage('bookmarksManager'), 58 | contexts: ['action'], 59 | }); 60 | }); 61 | 62 | chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { 63 | // console.log(message, sender); 64 | let task = message.task; 65 | if (task === 'reset') { 66 | setRootInfo().then(() => { 67 | sendResponse(); // chrome 必须显式发送响应? 68 | }); 69 | return true; // 异步返回true 70 | } else { 71 | console.log('[invalid task: ]', message); 72 | } 73 | 74 | return false; 75 | }); 76 | 77 | 78 | chrome.contextMenus.onClicked.addListener((data) => { 79 | switch(data.menuItemId) { 80 | case 'open-bookmarks-manager': 81 | chrome.tabs.create({ 82 | url: 'chrome://bookmarks/', 83 | }); 84 | } 85 | }); 86 | -------------------------------------------------------------------------------- /src/icons/edit.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/favicon/folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qinxs/Ease-Bookmarks/c33aad0bc766f36a34eb615e0c18de64e358da08/src/icons/favicon/folder.png -------------------------------------------------------------------------------- /src/icons/favicon/js.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qinxs/Ease-Bookmarks/c33aad0bc766f36a34eb615e0c18de64e358da08/src/icons/favicon/js.png -------------------------------------------------------------------------------- /src/icons/i.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/icons/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qinxs/Ease-Bookmarks/c33aad0bc766f36a34eb615e0c18de64e358da08/src/icons/icon128.png -------------------------------------------------------------------------------- /src/icons/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qinxs/Ease-Bookmarks/c33aad0bc766f36a34eb615e0c18de64e358da08/src/icons/icon16.png -------------------------------------------------------------------------------- /src/icons/icon32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qinxs/Ease-Bookmarks/c33aad0bc766f36a34eb615e0c18de64e358da08/src/icons/icon32.png -------------------------------------------------------------------------------- /src/icons/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qinxs/Ease-Bookmarks/c33aad0bc766f36a34eb615e0c18de64e358da08/src/icons/icon48.png -------------------------------------------------------------------------------- /src/icons/list.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/manager.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 17 | 18 | -------------------------------------------------------------------------------- /src/icons/options.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/undo.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /src/icons/usage.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/js/changeTabByHash.js: -------------------------------------------------------------------------------- 1 | var lastNavA = null; 2 | var lastPage = null; 3 | var defaultHash = '#options'; 4 | 5 | window.addEventListener('hashchange', function() { 6 | var page = location.hash || defaultHash; 7 | 8 | if (lastNavA) lastNavA.classList.remove('active'); 9 | if (lastPage) lastPage.classList.remove('active'); 10 | 11 | var newNavA = $(`.btns > a[href="${page}"]`); 12 | newNavA.classList.add('active'); 13 | 14 | var newPage = $(`${page}`); 15 | newPage.classList.add('active'); 16 | 17 | lastPage && window.scrollTo(0, 0); 18 | 19 | lastNavA = newNavA; 20 | lastPage = newPage; 21 | }, false); 22 | 23 | window.dispatchEvent(new Event('hashchange')); 24 | -------------------------------------------------------------------------------- /src/js/config.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | window.BM = { 4 | // 选项必须与input的name和value一致 5 | default: { 6 | themeColor: 'light', 7 | openIn: 3, // 0b11, 0-3 详细说明参见 openUrl 方法 8 | hoverEnter: 500, // {0,300,500,800} 9 | scrollDirection: 'y', 10 | layoutCols: 1, 11 | minItemsPerCol: 10, // 1-16;避免滚动条 12 | // 1 书签栏 2 其他书签(根目录为0) 13 | // -1 目录,-2 目录和滚动条(从上次位置启动) 14 | startup: 1, 15 | customCSS: '', // 自定义css 16 | }, 17 | defaultSys: { 18 | bodyWidth_1: '280px', 19 | bodyWidth_2: '400px', 20 | bodyWidth_3: '530px', 21 | bodyWidth_4: '660px', 22 | bodyWidth_5: '800px', 23 | compositionEvent: 0, 24 | fastCreate: 0, // 0-2 25 | hotkeyCancelSeleted: 'Space', 26 | hotkeyDelete: '-Delete', 27 | keepLastSearchValue: 0, 28 | keepMaxCols: 0, // 0-1 保持最大宽度 29 | // 1-2 30 | // 1:仅url 31 | // 2:url和title 32 | openBookmarkAfterCurrentTab: 0, // 当前标签页右边打开书签 33 | searchResultSort: 1, // 搜索结果排序 34 | updateBookmarkOpt: 1, 35 | }, 36 | set: function(name, value) { 37 | if (this.defaultSys.hasOwnProperty(name)) { 38 | if (value == BM.defaultSys[name]) { 39 | chrome.storage.sync.remove(name); 40 | } else { 41 | chrome.storage.sync.set({[name]: value}); 42 | } 43 | } else { 44 | console.log(L('setInvalidTips'), Object.keys(BM.defaultSys).join()); 45 | } 46 | } 47 | } 48 | 49 | // @TODO 改为 localStorage? 50 | // option页面自动同步到 storage,实现有点麻烦 51 | var loadSettings = new Promise(function(resolve, reject) { 52 | chrome.storage.sync.get(null, function(items) { 53 | // console.log(items); 54 | BM.settings = Object.assign({}, BM.default, BM.defaultSys, items); 55 | resolve(); 56 | }); 57 | }); 58 | 59 | BM.startupReal = localStorage.getItem('startupID') || BM.default.startup; 60 | 61 | var loadPreItems; 62 | 63 | // 提前读取bookmarks数据,优化启动速度 64 | if (location.pathname === '/popup.html') { 65 | loadPreItems = new Promise(function(resolve, reject) { 66 | chrome.bookmarks.getChildren(BM.startupReal.toString(), (results) => { 67 | // console.log(results); 68 | // 启动文件夹被删除了 69 | if (typeof results === 'undefined') { 70 | localStorage.setItem('startupID', BM.default.startup); 71 | chrome.storage.sync.set({startup: BM.default.startup}, () => { 72 | location.reload(); 73 | }); 74 | } 75 | BM.preItems = results; 76 | resolve(); 77 | }); 78 | }); 79 | } 80 | 81 | const $ = (css, d = document) => d.querySelector(css); 82 | const $$ = (css, d = document) => d.querySelectorAll(css); 83 | const L = chrome.i18n.getMessage; 84 | 85 | var lang = chrome.i18n.getUILanguage(); 86 | if (['ar', 'he', 'fa', 'ur', 'ku', 'ba', 'dv', 'hy'].includes(lang)) { 87 | document.dir = 'rtl'; 88 | } 89 | 90 | function setStartupID(folderID) { 91 | folderID < 0 && localStorage.setItem('startupFromLast', folderID); 92 | folderID > 0 && localStorage.setItem('startupID', folderID); 93 | folderID > -1 && localStorage.removeItem('startupFromLast'); 94 | folderID > -2 && localStorage.removeItem('LastScrollPos'); 95 | } 96 | -------------------------------------------------------------------------------- /src/js/configTable.js: -------------------------------------------------------------------------------- 1 | JSON.stringify2 = value => typeof value === 'object' 2 | ? JSON.stringify(value) 3 | : value; 4 | JSON.parse2 = (...args) => { 5 | try { 6 | return JSON.parse(...args); 7 | } catch { 8 | return args[0]; 9 | } 10 | } 11 | 12 | class TableRenderer { 13 | constructor(container, defaultSys, userSettings) { 14 | this.container = container; 15 | this.defaultSys = defaultSys; 16 | this.userSettings = userSettings; 17 | this.editInput = null; 18 | this.editing = false; 19 | this.init(); 20 | } 21 | 22 | init() { 23 | this.table = this.render(); 24 | this.editInput = this.table.querySelector('#cTable-input'); 25 | this._events(); 26 | 27 | return this; 28 | } 29 | 30 | render() { 31 | let table = document.createElement('table'); 32 | 33 | // 添加表头 34 | let thead = document.createElement('thead'); 35 | let headTr = document.createElement('tr'); 36 | 37 | let parametersUrl = lang.startsWith('zh') 38 | ? 'https://github.com/qinxs/Ease-Bookmarks#内置参数' 39 | : 'https://github.com/qinxs/Ease-Bookmarks/blob/main/README_en.md#built-in-parameters'; 40 | headTr.innerHTML = `Key 41 | Value 42 | 43 | 44 | 45 | 46 | 47 | `; 48 | thead.appendChild(headTr); 49 | table.appendChild(thead); 50 | 51 | // 添加表格内容 52 | let tbody = document.createElement('tbody'); 53 | table.appendChild(tbody); 54 | 55 | let trTemple = createTrTemple(); 56 | 57 | for (const [key, value] of Object.entries(this.defaultSys)) { 58 | let tr = trTemple.cloneNode(true); 59 | 60 | if (this.userSettings[key] != value) { 61 | tr.classList.add('has-user-value'); 62 | } 63 | 64 | let tdKey = tr.firstChild; 65 | tdKey.textContent = key; 66 | 67 | let tdValue = tdKey.nextSibling; 68 | tdValue.setAttribute('name', key); 69 | tdValue.textContent = JSON.stringify2(this.userSettings[key]); 70 | 71 | let tdReset = tdValue.nextSibling; 72 | tdReset.className = 'cell-reset'; 73 | 74 | tbody.appendChild(tr); 75 | } 76 | 77 | this.container.appendChild(table); 78 | 79 | return table; 80 | 81 | function createTrTemple() { 82 | let tr = document.createElement('tr'); 83 | 84 | let tdKey = document.createElement('td'); 85 | 86 | let tdValue = document.createElement('td'); 87 | tdValue.className = 'cell-value'; 88 | 89 | let tdReset = document.createElement('td'); 90 | tdReset.className = 'cell-reset'; 91 | 92 | tr.append(tdKey, tdValue, tdReset); 93 | 94 | return tr; 95 | } 96 | } 97 | 98 | setEditStatus(trEle, cellReset = false) { 99 | trEle = trEle.closest('tr'); 100 | if (!trEle) return; 101 | let tdValue = trEle.querySelector('.cell-value'); 102 | 103 | this.editInput.setAttribute('name', tdValue.getAttribute('name')); 104 | this.editInput.value = tdValue.textContent; 105 | 106 | tdValue.insertAdjacentElement('beforeend', this.editInput); 107 | if (!cellReset) { 108 | this.editInput.hidden = false; 109 | this.editInput.focus(); 110 | this.editing = true; 111 | } 112 | } 113 | 114 | cancelEditStatus() { 115 | this.editInput.hidden = true; 116 | this.editing = false; 117 | } 118 | _events() { 119 | this.table.addEventListener('click', event => { 120 | let target = event.target; 121 | if (target.classList.contains('cell-value')) { 122 | event.preventDefault(); 123 | event.stopPropagation(); 124 | this.setEditStatus(target); 125 | } 126 | }); 127 | 128 | document.addEventListener('click', event => { 129 | let target = event.target; 130 | 131 | if (target.classList.contains('cell-reset')) { 132 | let tdValue = target.closest('tr').querySelector('.cell-value'); 133 | 134 | let name = tdValue.getAttribute('name'); 135 | let defaultValue = JSON.stringify2(this.defaultSys[name]); 136 | 137 | this.setEditStatus(target, true); 138 | this.editInput.value = defaultValue; 139 | this.editInput.dispatchEvent(new UIEvent('change')); 140 | } 141 | 142 | if (this.editing && target !== this.editInput) { 143 | this.cancelEditStatus(); 144 | } 145 | }); 146 | 147 | this.editInput.addEventListener('change', event => { 148 | let { name, value } = event.target; 149 | let tr = this.editInput.closest('tr'); 150 | 151 | // console.log(name, value); 152 | this.editInput.closest('td').textContent = JSON.stringify2(value); 153 | 154 | if ( value != this.defaultSys[name]) { 155 | tr.classList.add('has-user-value'); 156 | } else { 157 | tr.classList.remove('has-user-value'); 158 | } 159 | 160 | if (name == 'keepLastSearchValue' && value == 0) { 161 | localStorage.removeItem('LastSearchValue'); 162 | } 163 | 164 | this.trigger('configCanged', { name, value }); 165 | }); 166 | } 167 | 168 | on(eventType, callback) { 169 | this.table.addEventListener(eventType, evt => { 170 | // console.log(evt); 171 | callback.call(this, evt.detail); 172 | }); 173 | 174 | return this; 175 | } 176 | 177 | trigger(eventType, detail) { 178 | let evt = new CustomEvent(eventType, { detail }); 179 | this.table.dispatchEvent(evt); 180 | 181 | return this; 182 | } 183 | } 184 | 185 | loadSettings.then(() => { 186 | let container = document.querySelector('#configTable'); 187 | 188 | new TableRenderer(container, BM.defaultSys, BM.settings) 189 | .on('configCanged', detail => { 190 | // console.log(detail); 191 | BM.set(detail.name, detail.value); 192 | }); 193 | }); 194 | -------------------------------------------------------------------------------- /src/js/functions.js: -------------------------------------------------------------------------------- 1 | // 扩展通用 且相对独立的功能 2 | 'use strict'; 3 | 4 | /** 5 | * [多语言] 6 | * USE:(函数依赖 L、$$) 7 | * data-i18n="themeColor" 8 | * data-i18n="placeholder:prefix=example" 9 | * data-i18n="placeholder:suffix=example,title=titleTip" 10 | */ 11 | function i18nLocalize() { 12 | // 普通翻译 13 | const i18nDefault = (ele, i18nValue) => { 14 | switch (ele.tagName) { 15 | case 'INPUT': 16 | case 'TEXTAREA': 17 | ele.placeholder = L(i18nValue); 18 | break; 19 | default: 20 | ele.textContent = L(i18nValue); 21 | } 22 | } 23 | 24 | // 多属性或者非常规属性 25 | const i18nMulti = (ele, i18nValue) => { 26 | // console.log(i18nValue); 27 | i18nValue.replaceAll(' ', '').split(',').forEach(unit => { 28 | const [key, value] = unit.split('='); 29 | const [attr, modifier] = key.split(':'); 30 | 31 | let translated = L(value); 32 | if (modifier === 'prefix') { 33 | translated = translated + ele[attr]; 34 | } else if (modifier === 'suffix') { 35 | translated = ele[attr] + translated; 36 | } 37 | 38 | ele.setAttribute(attr, translated); 39 | }); 40 | } 41 | 42 | $$('[data-i18n]').forEach(ele => { 43 | // console.log(ele); 44 | let i18nValue = ele.dataset.i18n; 45 | i18nValue.includes('=') 46 | ? i18nMulti(ele, i18nValue) 47 | : i18nDefault(ele, i18nValue); 48 | }); 49 | } 50 | 51 | // 兼容mv2 不要直接使用chromeAPI.then 52 | function exportConfig() { 53 | Promise.all([ 54 | new Promise(resolve => chrome.storage.sync.get(null, resolve)), 55 | new Promise(resolve => chrome.storage.local.get(null, resolve)), 56 | ]).then(([syncItems, localItems]) => { 57 | const { name, version } = chrome.runtime.getManifest(); 58 | const lang = chrome.i18n.getUILanguage(); 59 | 60 | const options = { 61 | year: 'numeric', month: '2-digit', day: '2-digit', 62 | hour: '2-digit', minute: '2-digit', second: '2-digit', 63 | }; 64 | let time = new Date().toLocaleString(lang, options); 65 | time = time.replace(/\//g, '-').replace(/ /, '_').replace(/:/g, '.'); 66 | 67 | const localStorageData = {}; 68 | for (let i = 0; i < localStorage.length; i++) { 69 | const key = localStorage.key(i); 70 | localStorageData[key] = localStorage.getItem(key); 71 | } 72 | const config = { 73 | version: version, 74 | sync: syncItems, 75 | local: localItems, 76 | localStorage: localStorageData, 77 | }; 78 | 79 | const blob = new Blob([JSON.stringify(config, null, 2)], { type: 'application/json' }); 80 | const url = URL.createObjectURL(blob); 81 | 82 | const a = document.createElement("a"); 83 | a.href = url; 84 | a.download = `${name}_${time}.json`; 85 | a.click(); 86 | URL.revokeObjectURL(url); // 清除临时URL 87 | }); 88 | } 89 | 90 | function importConfig(file) { 91 | return new Promise((resolve, reject) => { 92 | const reader = new FileReader(); 93 | 94 | reader.onload = function(e) { 95 | try { 96 | const config = JSON.parse(e.target.result); 97 | 98 | resetConfig().then(() => Promise.all([ 99 | new Promise(resolve => chrome.storage.sync.set(config.sync, resolve)), 100 | new Promise(resolve => chrome.storage.local.set(config.local, resolve)), 101 | ])) 102 | .then(() => { 103 | for (const key in config.localStorage) { 104 | localStorage.setItem(key, config.localStorage[key]); 105 | } 106 | 107 | resolve('Import completed'); 108 | }) 109 | .catch(reject); 110 | } catch (err) { 111 | alert(`Failed to parse JSON: ${err.message}`); 112 | reject(err); 113 | } 114 | }; 115 | reader.onerror = (error) => { 116 | reject(error); 117 | }; 118 | reader.readAsText(file); 119 | }); 120 | } 121 | 122 | function resetConfig() { 123 | return Promise.all([ 124 | new Promise(resolve => chrome.storage.sync.clear(resolve)), 125 | new Promise(resolve => chrome.storage.local.clear(resolve)), 126 | localStorage.clear(), 127 | ]); 128 | } 129 | 130 | function handleFileSelect(options = {}) { 131 | const { 132 | onFileSelected, // 必须参数:文件选择后的回调函数 133 | accept = '', // 可选参数:文件类型限制 134 | multiple = false // 可选参数:是否允许多选 135 | } = options; 136 | 137 | const input = document.createElement('input'); 138 | input.type = 'file'; 139 | input.style.display = 'none'; 140 | input.accept = accept; 141 | input.multiple = multiple; 142 | 143 | input.addEventListener('change', async (event) => { 144 | const files = Array.from(event.target.files); 145 | if (files.length === 0) { 146 | cleanup(); 147 | return; 148 | } 149 | 150 | try { 151 | await Promise.resolve(onFileSelected(files)); 152 | } finally { 153 | cleanup(); 154 | } 155 | }); 156 | 157 | document.body.appendChild(input); 158 | input.click(); 159 | 160 | function cleanup() { 161 | input.removeEventListener('change', this); 162 | document.body.removeChild(input); 163 | } 164 | } -------------------------------------------------------------------------------- /src/js/loadmd.js: -------------------------------------------------------------------------------- 1 | var mdUrl, 2 | mdEnUrl = `md/usage-en.md`; 3 | 4 | if (lang.startsWith('zh')) { 5 | mdUrl = 'md/usage-zh.md'; 6 | document.documentElement.lang = 'zh-CN'; 7 | } else { 8 | mdUrl = `md/usage-${lang}.md`; 9 | } 10 | 11 | // 不存在对应语言的文档 则加载英文文档 12 | chrome.runtime.getPackageDirectoryEntry(function(rootDir) { 13 | rootDir.getFile(mdUrl, {}, function(fileEntry) { 14 | renderMd(mdUrl, document.querySelector('#usage')); 15 | }, function(error) { 16 | renderMd(mdEnUrl, document.querySelector('#usage')); 17 | }); 18 | }); 19 | 20 | function renderMd(url, ele) { 21 | fetch(url) 22 | .then((response) => response.text()) 23 | .then(data => { 24 | ele.innerHTML = marked.parse(data); 25 | }) 26 | .then(() => { 27 | $$('a[href^="chrome://"]').forEach(function(a) { 28 | a.addEventListener('click', function(event) { 29 | event.preventDefault(); 30 | chrome.tabs.create({ url: a.href }); 31 | }); 32 | }); 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /src/js/options.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var $bookmarksBar = $('#bookmarksBar'); 4 | var $otherBookmarks = $('#otherBookmarks'); 5 | var $customCSS = $('#customCSS'); 6 | var $minItemsPerCol = $('#minItemsPerCol'); 7 | 8 | var $iconPreview = $('.icon_preview'); 9 | 10 | if (lang.startsWith('zh')) { 11 | document.documentElement.lang = 'zh'; 12 | } 13 | 14 | function setSyncItem(name, value) { 15 | if (value == BM.default[name] || !value) { 16 | chrome.storage.sync.remove(name); 17 | } else { 18 | chrome.storage.sync.set({[name]: value}); 19 | } 20 | } 21 | 22 | function bookmarksAlias() { 23 | var rootInfo = { 24 | 1: $bookmarksBar.value, 25 | 2: $otherBookmarks.value, 26 | } 27 | chrome.storage.sync.set({rootInfo: rootInfo}); 28 | } 29 | 30 | function resetIcon() { 31 | chrome.storage.local.remove('customIcon', () =>{ 32 | chrome.action.setIcon({path: 'icons/icon32.png'}); 33 | $iconPreview.removeAttribute('style'); 34 | }); 35 | } 36 | 37 | // 备份与恢复 38 | $('#exportBtn').addEventListener('click', exportConfig); 39 | 40 | $('#importBtn').addEventListener('click', () => { 41 | handleFileSelect({ 42 | accept: '.json', // 限制 json 文件 43 | onFileSelected: (files) => { 44 | const [file] = files; 45 | return importConfig(file).then(() => { 46 | location.reload(); 47 | }); 48 | } 49 | }); 50 | }); 51 | 52 | $('#resetBtn').addEventListener('click', () => { 53 | if (confirm(L('resetConfigTip'))) { 54 | resetIcon(); 55 | resetConfig() 56 | .then(() => { 57 | chrome.runtime.sendMessage({ task: 'reset' }, () => { 58 | localStorage.version = chrome.runtime.getManifest().version; 59 | 60 | location.reload(); 61 | }); 62 | }) 63 | } 64 | }); 65 | 66 | i18nLocalize(); 67 | loadSettings.then(() => { 68 | // 必须在最前面 #folderX的数据通过后面for写入 69 | $('#_1').textContent = BM.settings.rootInfo[1]; 70 | $('#_2').textContent = BM.settings.rootInfo[2]; 71 | var folderX = $('#folderX'); 72 | // folderX.title = L('folderXTitle'); 73 | if (BM.settings.startup > 2) { 74 | folderX.previousElementSibling.value = BM.settings.startup; 75 | chrome.bookmarks.get(BM.settings.startup, (results) => { 76 | folderX.textContent = results[0].title; 77 | }); 78 | } else { 79 | folderX.previousElementSibling.disabled = true; 80 | folderX.classList.add('disabled'); 81 | } 82 | 83 | // 读数据 84 | // console.log(BM.settings); 85 | for (var key in BM.default) { 86 | // [value=]会报错 单独处理 87 | if (key === 'customCSS') continue; 88 | 89 | var value = BM.settings[key]; 90 | // console.log(`${key}: ${value}`); 91 | var ele = $(`input[name=${key}][value="${value}"]`); 92 | if (ele) { 93 | ele.checked = true; 94 | } else { 95 | // console.log(`[未设置选项] ${key}: ${value}`) 96 | } 97 | } 98 | 99 | $minItemsPerCol.value = BM.settings.minItemsPerCol; 100 | 101 | $bookmarksBar.value = BM.settings.rootInfo[1]; 102 | $otherBookmarks.value = BM.settings.rootInfo[2]; 103 | chrome.bookmarks.getChildren('0', (results) => { 104 | // console.log(results); 105 | $bookmarksBar.placeholder = results[0].title; 106 | $otherBookmarks.placeholder = results[1].title; 107 | }); 108 | 109 | $customCSS.value = BM.settings.customCSS || ''; 110 | 111 | // 写数据 112 | for (var ele of $$('input[type=radio]')) { 113 | // console.log(ele); 114 | ele.addEventListener('change', (event) => { 115 | // console.log(event.target); 116 | var {name, value} = event.target; 117 | // if (name == 'startup') debugger 118 | setSyncItem(name, value); 119 | }, false); 120 | } 121 | 122 | $$('input[name=startup]').forEach((ele) => { 123 | ele.addEventListener('change', function() { 124 | setStartupID(this.value); 125 | }); 126 | }); 127 | 128 | // 只允许数字 129 | $minItemsPerCol.addEventListener('input', function() { 130 | this.value = this.value.replace(/[^0-9]/g, ''); 131 | }); 132 | $minItemsPerCol.addEventListener('change', function() { 133 | if (this.value < 1) this.value = BM.default[this.name]; 134 | setSyncItem(this.name, this.value); 135 | }); 136 | 137 | $bookmarksBar.addEventListener('change', bookmarksAlias); 138 | $otherBookmarks.addEventListener('change', bookmarksAlias); 139 | 140 | $customCSS.addEventListener('change', function() { 141 | setSyncItem(this.name, this.value); 142 | }); 143 | 144 | // 预览自定义头像 145 | chrome.storage.local.get('customIcon', items => { 146 | var iconBase64 = items.customIcon; 147 | if (iconBase64) { 148 | $iconPreview.style.backgroundImage = `url(${iconBase64})`; 149 | $iconPreview.style.backgroundSize = `19px`; 150 | } 151 | }); 152 | 153 | // 压缩图片需要的一些元素和对象 154 | var reader = new FileReader(), img = new Image(), file = null; 155 | 156 | var canvas = document.createElement("canvas"); 157 | var context = canvas.getContext("2d", { willReadFrequently: true }); 158 | 159 | $('#uploadIcon').addEventListener('click', () => { 160 | handleFileSelect({ 161 | accept: 'image/*', 162 | onFileSelected: (files) => { 163 | const [file] = files; 164 | // 选择的文件是图片 165 | if (file.type.indexOf("image") == 0) { 166 | reader.readAsDataURL(file); 167 | } 168 | } 169 | }); 170 | }); 171 | 172 | reader.onload = function (event) { 173 | // base64码 174 | img.src = event.target.result; 175 | } 176 | 177 | img.onload = function() { 178 | var imgBase64 = image2Base64(img); 179 | $iconPreview.style.backgroundImage = `url(${imgBase64})`; 180 | $iconPreview.style.backgroundSize = `19px`; 181 | // localStorage.customIcon = imgBase64; 182 | chrome.storage.local.set({customIcon: imgBase64}, () => { 183 | var imageData = context.getImageData(0, 0, 19, 19); 184 | chrome.action.setIcon({imageData: imageData}); 185 | }); 186 | 187 | function image2Base64(img, width = 19, height = 19) { 188 | canvas.width = width; 189 | canvas.height = height; 190 | // 清除画布 191 | context.clearRect(0, 0, width, height); 192 | // 图片压缩 193 | context.drawImage(img, 0, 0, width, height); 194 | var dataURL = canvas.toDataURL("image/png"); 195 | // console.log(dataURL); 196 | return dataURL; 197 | } 198 | }; 199 | 200 | $('#resetIcon').addEventListener('click', resetIcon); 201 | }); 202 | -------------------------------------------------------------------------------- /src/libs/dragula.css: -------------------------------------------------------------------------------- 1 | .gu-mirror { 2 | position: fixed !important; 3 | margin: 0 !important; 4 | z-index: 9999 !important; 5 | opacity: .8 6 | } 7 | .gu-hide { 8 | display: none !important 9 | } 10 | .gu-unselectable { 11 | -webkit-user-select: none !important; 12 | -moz-user-select: none !important; 13 | -ms-user-select: none !important; 14 | user-select: none !important 15 | } 16 | .gu-transit { 17 | opacity: .2 18 | } 19 | .gu-drop-overlay { 20 | background-color: var(--color-hover); 21 | border: 1px solid var(--color-theme); 22 | border-radius: 2px 23 | } -------------------------------------------------------------------------------- /src/libs/dragula.min.js: -------------------------------------------------------------------------------- 1 | var dragula=function(){"use strict";const e=document.documentElement;var t,n=window.requestAnimationFrame||function(e){return setTimeout(e,1e3/60)},i=window.cancelAnimationFrame||function(e){clearTimeout(e)};class o extends EventTarget{constructor(f,p){super(),1===arguments.length&&!1===Array.isArray(f)&&([p,f]=[f,[]]);var g,m,v,h,w,y,E,S,b,L,C,x=this.options=Object.assign({},o.defaultOptions,p);if(this.containers=x.containers=x.containers||f||[],"function"!=typeof x.copy){let e=x.copy;x.copy=t=>e}this.dragging=!1;var O,X,Y=null;let T=this;function N(e){return T.containers.includes(e)||T.options.isContainer(e)}function B(t){e[(t?"remove":"add")+"EventListener"]("pointermove",A)}function F(t){e[(t?"remove":"add")+"EventListener"]("click",R)}function R(e){O&&e.preventDefault()}function P(e){if(y=e.clientX,E=e.clientY,!(1!==c(e)||e.metaKey||e.ctrlKey)){var t=e.target,n=M(t);n&&(O=n,B(),"pointerdown"===e.type&&(d(t)?t.focus():e.preventDefault()))}}function A(t){if(O)if(0!==c(t)){if(!(void 0!==t.clientX&&Math.abs(t.clientX-y)<=(T.options.slideFactorX||0)&&void 0!==t.clientY&&Math.abs(t.clientY-E)<=(T.options.slideFactorY||0))){if(T.options.ignoreInputTextSelection){var n=t.clientX||0,i=t.clientY||0;if(d(document.elementFromPoint(n,i)))return}var o=O;B(!0),F(),D(),j(o);var r=u(v);h=t.pageX-r.left,w=t.pageY-r.top;var l=L||v;l&&l.classList.add("gu-transit"),function(){if(g)return;var t=v.getBoundingClientRect();(g=v.cloneNode(!0)).style.width=t.width+"px",g.style.height=t.height+"px",g.classList.remove("gu-transit"),g.classList.add("gu-mirror"),T.options.mirrorContainer.appendChild(g),e.addEventListener("pointermove",G),T.options.mirrorContainer.classList.add("gu-unselectable"),T.emit("cloned",{clone:g,original:v,type:"mirror"})}(),G(t)}}else V({})}function M(e){if(!(T.dragging&&g||N(e))){for(var t=e;a(e)&&!1===N(a(e));){if(T.options.invalid(e,t))return;if(!(e=a(e)))return}var n=a(e);if(n)if(!T.options.invalid(e,t))if(T.options.moves(e,n,t,e.nextElementSibling))return{item:e,source:n}}}function j(e){x.copy(e.item,e.source)&&(L=e.item.cloneNode(!0),T.emit("cloned",{clone:L,original:e.item,type:"copy"})),m=e.source,v=e.item,S=b=e.item.nextElementSibling,T.dragging=!0,T.emit("drag",{element:v,source:m})}function D(){if(T.dragging){var e=L||v;q(e,a(e))}else i(t)}function H(){O=!1,B(!0),F(!0)}function V(e){if(H(),T.dragging){var t=L||v,n=e.clientX||0,i=e.clientY||0,o=W(s(g,n,i),n,i);o&&(L&&T.options.copySortSource||!L||o!==m)?q(t,o):T.options.removeOnSpill?I():K()}}function q(e,t){L&&T.options.copySortSource&&t===m&&v.remove(),z(t)&&!X?T.emit("cancel",{element:e,container:m,source:m}):T.emit("drop",{element:e,target:t,source:m,sibling:b,isHover:X}),k()}function I(){if(T.dragging){var e=L||v,t=a(e);t&&e.remove(),T.emit(L?"cancel":"remove",{element:e,container:t,source:m}),k()}}function K(e){if(T.dragging){var t=arguments.length>0?e:T.options.revertOnSpill,n=L||v,i=a(n),o=z(i);!1===o&&t&&(L?i&&L.remove():m.insertBefore(n,S)),o||t?T.emit("cancel",{element:n,container:m,source:m}):T.emit("drop",{element:n,target:i,source:m,sibling:b}),k()}}function k(){var n=L||v;i(t),H(),g&&(T.options.mirrorContainer.classList.remove("gu-unselectable"),e.removeEventListener("pointermove",G),g.remove(),g=null),n&&(n.classList.remove("gu-transit"),X&&(b.classList.remove("gu-drop-overlay"),n.remove())),C&&clearTimeout(C),T.dragging=!1,Y&&T.emit("out",{element:n,container:Y,source:m}),T.emit("dragend",{element:n}),m=v=L=S=b=C=Y=null}function z(e,t){var n;return n=void 0!==t?t:g?b:(L||v).nextElementSibling,e===m&&n===S}function W(e,t,n){for(var i=e;i&&!o();)i=a(i);return i;function o(){if(!1===N(i))return!1;var o=r(i,e),c=l(i,o,t,n,T.options.direction);return!!z(i,c)||T.options.accepts(v,i,m,c)}}function G(e){if(g){e.preventDefault();var o=e.clientX||0,c=e.clientY||0,d=o-h,f=c-w;g.style.left=d+"px",g.style.top=f+"px";var p=L||v,y=s(g,o,c),E=W(y,o,c),C=null!==E&&E!==Y;(C||null===E)&&(Y&&F("out"),Y=E,C&&F("over"));var O=a(p);if(E!==m||!L||T.options.copySortSource){var N,B=r(E,y);if(null!==B)N=l(E,B,o,c,T.options.direction);else{if(!0!==T.options.revertOnSpill||L)return void(L&&O&&p.remove());N=S,E=m}(null===N&&C||N!==p&&(N!==p.nextElementSibling||R(N)))&&(R(N)&&function(e){var t=(e.previousElementSibling||e.nextElementSibling||e).offsetTop===e.offsetTop,n=e.getBoundingClientRect();if(t)return o>n.left+n.width/4&&on.top+n.height/4&&cMath.ceil(t)?e:new RegExp("(body|html)","i").test(e.parentNode.tagName)?null:P(e.parentNode)}function A(e,i,o){return t=n((function(){T.dragging&&A(e,i,o)})),e[o]+=.25*i}}!0===this.options.removeOnSpill&&this.on("over",(function(e){e&&e.classList.remove("gu-hide")})).on("out",(function(e){T.dragging&&e&&e.classList.add("gu-hide")})),e.addEventListener("pointerdown",P),e.addEventListener("pointerup",V),Object.assign(this,{start:function(e){var t=M(e);t&&j(t)},end:D,cancel:K,remove:I,destroy:function(){e.removeEventListener("pointerdown",P),e.removeEventListener("pointerup",V),V({})},canMove:function(e){return!!M(e)}})}on(e,t){return this.addEventListener(e,(e=>{t.call(this,...Object.values(e.detail))})),this}off(e,t){return this.removeEventListener(e,t),this}emit(e,t,...n){t instanceof Node&&(t=[t,...n]);let i=new CustomEvent(e,{detail:t});return this.dispatchEvent(i),this}static defaultOptions={moves:e=>!0,accepts:e=>!0,invalid:e=>!1,isContainer:e=>!1,copy:!1,copySortSource:!1,revertOnSpill:!1,removeOnSpill:!1,direction:"vertical",ignoreInputTextSelection:!0,mirrorContainer:document.body,folderCss:"[type=folder]",scrollEdge:36}}function r(t,n){for(var i=n;i!==t&&a(i)!==t;)i=a(i);return i===e?null:i}function l(e,t,n,i,o){var r="horizontal"===o;return t!==e?function(){var e=t.getBoundingClientRect();if(r)return l(n>e.left+e.width/2);return l(i>e.top+e.height/2)}():function(){var t,o,l,c=e.children.length;for(t=0;tn)return o;if(!r&&l.top+l.height/2>i)return o}return null}();function l(e){return e?t.nextElementSibling:t}}function c(e){if(void 0!==e.touches)return e.touches.length;if(void 0!==e.which&&0!==e.which)return e.which;if(void 0!==e.buttons)return e.buttons;var t=e.button;return void 0!==t?1&t?1:2&t?3:4&t?2:0:void 0}function u(e){var t=e.getBoundingClientRect();return{left:t.left+window.scrollX,top:t.top+window.scrollY}}function s(e,t,n){var i,o=(e=e||{}).className||"";return e.className+=" gu-hide",i=document.elementFromPoint(t,n),e.className=o,i}function a(e){return e.parentNode===document?null:e.parentNode}function d(e){return e?.matches("input, select, textarea")||f(e)}function f(e){return!!e&&("false"!==e.contentEditable&&("true"===e.contentEditable||f(a(e))))}return function(...e){return new o(...e)}}(); 2 | -------------------------------------------------------------------------------- /src/libs/marked.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * marked v12.0.2 - a markdown parser 3 | * Copyright (c) 2011-2024, Christopher Jeffrey. (MIT Licensed) 4 | * https://github.com/markedjs/marked 5 | */ 6 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).marked={})}(this,(function(e){"use strict";function t(){return{async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null}}function n(t){e.defaults=t}e.defaults={async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null};const s=/[&<>"']/,r=new RegExp(s.source,"g"),i=/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/,l=new RegExp(i.source,"g"),o={"&":"&","<":"<",">":">",'"':""","'":"'"},a=e=>o[e];function c(e,t){if(t){if(s.test(e))return e.replace(r,a)}else if(i.test(e))return e.replace(l,a);return e}const h=/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/gi;function p(e){return e.replace(h,((e,t)=>"colon"===(t=t.toLowerCase())?":":"#"===t.charAt(0)?"x"===t.charAt(1)?String.fromCharCode(parseInt(t.substring(2),16)):String.fromCharCode(+t.substring(1)):""))}const u=/(^|[^\[])\^/g;function k(e,t){let n="string"==typeof e?e:e.source;t=t||"";const s={replace:(e,t)=>{let r="string"==typeof t?t:t.source;return r=r.replace(u,"$1"),n=n.replace(e,r),s},getRegex:()=>new RegExp(n,t)};return s}function g(e){try{e=encodeURI(e).replace(/%25/g,"%")}catch(e){return null}return e}const f={exec:()=>null};function d(e,t){const n=e.replace(/\|/g,((e,t,n)=>{let s=!1,r=t;for(;--r>=0&&"\\"===n[r];)s=!s;return s?"|":" |"})).split(/ \|/);let s=0;if(n[0].trim()||n.shift(),n.length>0&&!n[n.length-1].trim()&&n.pop(),t)if(n.length>t)n.splice(t);else for(;n.length0)return{type:"space",raw:t[0]}}code(e){const t=this.rules.block.code.exec(e);if(t){const e=t[0].replace(/^ {1,4}/gm,"");return{type:"code",raw:t[0],codeBlockStyle:"indented",text:this.options.pedantic?e:x(e,"\n")}}}fences(e){const t=this.rules.block.fences.exec(e);if(t){const e=t[0],n=function(e,t){const n=e.match(/^(\s+)(?:```)/);if(null===n)return t;const s=n[1];return t.split("\n").map((e=>{const t=e.match(/^\s+/);if(null===t)return e;const[n]=t;return n.length>=s.length?e.slice(s.length):e})).join("\n")}(e,t[3]||"");return{type:"code",raw:e,lang:t[2]?t[2].trim().replace(this.rules.inline.anyPunctuation,"$1"):t[2],text:n}}}heading(e){const t=this.rules.block.heading.exec(e);if(t){let e=t[2].trim();if(/#$/.test(e)){const t=x(e,"#");this.options.pedantic?e=t.trim():t&&!/ $/.test(t)||(e=t.trim())}return{type:"heading",raw:t[0],depth:t[1].length,text:e,tokens:this.lexer.inline(e)}}}hr(e){const t=this.rules.block.hr.exec(e);if(t)return{type:"hr",raw:t[0]}}blockquote(e){const t=this.rules.block.blockquote.exec(e);if(t){let e=t[0].replace(/\n {0,3}((?:=+|-+) *)(?=\n|$)/g,"\n $1");e=x(e.replace(/^ *>[ \t]?/gm,""),"\n");const n=this.lexer.state.top;this.lexer.state.top=!0;const s=this.lexer.blockTokens(e);return this.lexer.state.top=n,{type:"blockquote",raw:t[0],tokens:s,text:e}}}list(e){let t=this.rules.block.list.exec(e);if(t){let n=t[1].trim();const s=n.length>1,r={type:"list",raw:"",ordered:s,start:s?+n.slice(0,-1):"",loose:!1,items:[]};n=s?`\\d{1,9}\\${n.slice(-1)}`:`\\${n}`,this.options.pedantic&&(n=s?n:"[*+-]");const i=new RegExp(`^( {0,3}${n})((?:[\t ][^\\n]*)?(?:\\n|$))`);let l="",o="",a=!1;for(;e;){let n=!1;if(!(t=i.exec(e)))break;if(this.rules.block.hr.test(e))break;l=t[0],e=e.substring(l.length);let s=t[2].split("\n",1)[0].replace(/^\t+/,(e=>" ".repeat(3*e.length))),c=e.split("\n",1)[0],h=0;this.options.pedantic?(h=2,o=s.trimStart()):(h=t[2].search(/[^ ]/),h=h>4?1:h,o=s.slice(h),h+=t[1].length);let p=!1;if(!s&&/^ *$/.test(c)&&(l+=c+"\n",e=e.substring(c.length+1),n=!0),!n){const t=new RegExp(`^ {0,${Math.min(3,h-1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ \t][^\\n]*)?(?:\\n|$))`),n=new RegExp(`^ {0,${Math.min(3,h-1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`),r=new RegExp(`^ {0,${Math.min(3,h-1)}}(?:\`\`\`|~~~)`),i=new RegExp(`^ {0,${Math.min(3,h-1)}}#`);for(;e;){const a=e.split("\n",1)[0];if(c=a,this.options.pedantic&&(c=c.replace(/^ {1,4}(?=( {4})*[^ ])/g," ")),r.test(c))break;if(i.test(c))break;if(t.test(c))break;if(n.test(e))break;if(c.search(/[^ ]/)>=h||!c.trim())o+="\n"+c.slice(h);else{if(p)break;if(s.search(/[^ ]/)>=4)break;if(r.test(s))break;if(i.test(s))break;if(n.test(s))break;o+="\n"+c}p||c.trim()||(p=!0),l+=a+"\n",e=e.substring(a.length+1),s=c.slice(h)}}r.loose||(a?r.loose=!0:/\n *\n *$/.test(l)&&(a=!0));let u,k=null;this.options.gfm&&(k=/^\[[ xX]\] /.exec(o),k&&(u="[ ] "!==k[0],o=o.replace(/^\[[ xX]\] +/,""))),r.items.push({type:"list_item",raw:l,task:!!k,checked:u,loose:!1,text:o,tokens:[]}),r.raw+=l}r.items[r.items.length-1].raw=l.trimEnd(),r.items[r.items.length-1].text=o.trimEnd(),r.raw=r.raw.trimEnd();for(let e=0;e"space"===e.type)),n=t.length>0&&t.some((e=>/\n.*\n/.test(e.raw)));r.loose=n}if(r.loose)for(let e=0;e$/,"$1").replace(this.rules.inline.anyPunctuation,"$1"):"",s=t[3]?t[3].substring(1,t[3].length-1).replace(this.rules.inline.anyPunctuation,"$1"):t[3];return{type:"def",tag:e,raw:t[0],href:n,title:s}}}table(e){const t=this.rules.block.table.exec(e);if(!t)return;if(!/[:|]/.test(t[2]))return;const n=d(t[1]),s=t[2].replace(/^\||\| *$/g,"").split("|"),r=t[3]&&t[3].trim()?t[3].replace(/\n[ \t]*$/,"").split("\n"):[],i={type:"table",raw:t[0],header:[],align:[],rows:[]};if(n.length===s.length){for(const e of s)/^ *-+: *$/.test(e)?i.align.push("right"):/^ *:-+: *$/.test(e)?i.align.push("center"):/^ *:-+ *$/.test(e)?i.align.push("left"):i.align.push(null);for(const e of n)i.header.push({text:e,tokens:this.lexer.inline(e)});for(const e of r)i.rows.push(d(e,i.header.length).map((e=>({text:e,tokens:this.lexer.inline(e)}))));return i}}lheading(e){const t=this.rules.block.lheading.exec(e);if(t)return{type:"heading",raw:t[0],depth:"="===t[2].charAt(0)?1:2,text:t[1],tokens:this.lexer.inline(t[1])}}paragraph(e){const t=this.rules.block.paragraph.exec(e);if(t){const e="\n"===t[1].charAt(t[1].length-1)?t[1].slice(0,-1):t[1];return{type:"paragraph",raw:t[0],text:e,tokens:this.lexer.inline(e)}}}text(e){const t=this.rules.block.text.exec(e);if(t)return{type:"text",raw:t[0],text:t[0],tokens:this.lexer.inline(t[0])}}escape(e){const t=this.rules.inline.escape.exec(e);if(t)return{type:"escape",raw:t[0],text:c(t[1])}}tag(e){const t=this.rules.inline.tag.exec(e);if(t)return!this.lexer.state.inLink&&/^/i.test(t[0])&&(this.lexer.state.inLink=!1),!this.lexer.state.inRawBlock&&/^<(pre|code|kbd|script)(\s|>)/i.test(t[0])?this.lexer.state.inRawBlock=!0:this.lexer.state.inRawBlock&&/^<\/(pre|code|kbd|script)(\s|>)/i.test(t[0])&&(this.lexer.state.inRawBlock=!1),{type:"html",raw:t[0],inLink:this.lexer.state.inLink,inRawBlock:this.lexer.state.inRawBlock,block:!1,text:t[0]}}link(e){const t=this.rules.inline.link.exec(e);if(t){const e=t[2].trim();if(!this.options.pedantic&&/^$/.test(e))return;const t=x(e.slice(0,-1),"\\");if((e.length-t.length)%2==0)return}else{const e=function(e,t){if(-1===e.indexOf(t[1]))return-1;let n=0;for(let s=0;s-1){const n=(0===t[0].indexOf("!")?5:4)+t[1].length+e;t[2]=t[2].substring(0,e),t[0]=t[0].substring(0,n).trim(),t[3]=""}}let n=t[2],s="";if(this.options.pedantic){const e=/^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(n);e&&(n=e[1],s=e[3])}else s=t[3]?t[3].slice(1,-1):"";return n=n.trim(),/^$/.test(e)?n.slice(1):n.slice(1,-1)),b(t,{href:n?n.replace(this.rules.inline.anyPunctuation,"$1"):n,title:s?s.replace(this.rules.inline.anyPunctuation,"$1"):s},t[0],this.lexer)}}reflink(e,t){let n;if((n=this.rules.inline.reflink.exec(e))||(n=this.rules.inline.nolink.exec(e))){const e=t[(n[2]||n[1]).replace(/\s+/g," ").toLowerCase()];if(!e){const e=n[0].charAt(0);return{type:"text",raw:e,text:e}}return b(n,e,n[0],this.lexer)}}emStrong(e,t,n=""){let s=this.rules.inline.emStrongLDelim.exec(e);if(!s)return;if(s[3]&&n.match(/[\p{L}\p{N}]/u))return;if(!(s[1]||s[2]||"")||!n||this.rules.inline.punctuation.exec(n)){const n=[...s[0]].length-1;let r,i,l=n,o=0;const a="*"===s[0][0]?this.rules.inline.emStrongRDelimAst:this.rules.inline.emStrongRDelimUnd;for(a.lastIndex=0,t=t.slice(-1*e.length+n);null!=(s=a.exec(t));){if(r=s[1]||s[2]||s[3]||s[4]||s[5]||s[6],!r)continue;if(i=[...r].length,s[3]||s[4]){l+=i;continue}if((s[5]||s[6])&&n%3&&!((n+i)%3)){o+=i;continue}if(l-=i,l>0)continue;i=Math.min(i,i+l+o);const t=[...s[0]][0].length,a=e.slice(0,n+s.index+t+i);if(Math.min(n,i)%2){const e=a.slice(1,-1);return{type:"em",raw:a,text:e,tokens:this.lexer.inlineTokens(e)}}const c=a.slice(2,-2);return{type:"strong",raw:a,text:c,tokens:this.lexer.inlineTokens(c)}}}}codespan(e){const t=this.rules.inline.code.exec(e);if(t){let e=t[2].replace(/\n/g," ");const n=/[^ ]/.test(e),s=/^ /.test(e)&&/ $/.test(e);return n&&s&&(e=e.substring(1,e.length-1)),e=c(e,!0),{type:"codespan",raw:t[0],text:e}}}br(e){const t=this.rules.inline.br.exec(e);if(t)return{type:"br",raw:t[0]}}del(e){const t=this.rules.inline.del.exec(e);if(t)return{type:"del",raw:t[0],text:t[2],tokens:this.lexer.inlineTokens(t[2])}}autolink(e){const t=this.rules.inline.autolink.exec(e);if(t){let e,n;return"@"===t[2]?(e=c(t[1]),n="mailto:"+e):(e=c(t[1]),n=e),{type:"link",raw:t[0],text:e,href:n,tokens:[{type:"text",raw:e,text:e}]}}}url(e){let t;if(t=this.rules.inline.url.exec(e)){let e,n;if("@"===t[2])e=c(t[0]),n="mailto:"+e;else{let s;do{s=t[0],t[0]=this.rules.inline._backpedal.exec(t[0])?.[0]??""}while(s!==t[0]);e=c(t[0]),n="www."===t[1]?"http://"+t[0]:t[0]}return{type:"link",raw:t[0],text:e,href:n,tokens:[{type:"text",raw:e,text:e}]}}}inlineText(e){const t=this.rules.inline.text.exec(e);if(t){let e;return e=this.lexer.state.inRawBlock?t[0]:c(t[0]),{type:"text",raw:t[0],text:e}}}}const m=/^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/,y=/(?:[*+-]|\d{1,9}[.)])/,$=k(/^(?!bull |blockCode|fences|blockquote|heading|html)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html))+?)\n {0,3}(=+|-+) *(?:\n+|$)/).replace(/bull/g,y).replace(/blockCode/g,/ {4}/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).getRegex(),z=/^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/,T=/(?!\s*\])(?:\\.|[^\[\]\\])+/,R=k(/^ {0,3}\[(label)\]: *(?:\n *)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n *)?| *\n *)(title))? *(?:\n+|$)/).replace("label",T).replace("title",/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex(),_=k(/^( {0,3}bull)([ \t][^\n]+?)?(?:\n|$)/).replace(/bull/g,y).getRegex(),A="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",S=/|$))/,I=k("^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|\\n*|$)|\\n*|$)|)[\\s\\S]*?(?:(?:\\n *)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n *)+\\n|$)|(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n *)+\\n|$))","i").replace("comment",S).replace("tag",A).replace("attribute",/ +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(),E=k(z).replace("hr",m).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("|table","").replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",A).getRegex(),q={blockquote:k(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/).replace("paragraph",E).getRegex(),code:/^( {4}[^\n]+(?:\n(?: *(?:\n|$))*)?)+/,def:R,fences:/^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/,heading:/^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/,hr:m,html:I,lheading:$,list:_,newline:/^(?: *(?:\n|$))+/,paragraph:E,table:f,text:/^[^\n]+/},Z=k("^ *([^\\n ].*)\\n {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)").replace("hr",m).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("blockquote"," {0,3}>").replace("code"," {4}[^\\n]").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",A).getRegex(),L={...q,table:Z,paragraph:k(z).replace("hr",m).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("table",Z).replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",A).getRegex()},P={...q,html:k("^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+? *(?:\\n{2,}|\\s*$)|\\s]*)*?/?> *(?:\\n{2,}|\\s*$))").replace("comment",S).replace(/tag/g,"(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),def:/^ *\[([^\]]+)\]: *]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,heading:/^(#{1,6})(.*)(?:\n+|$)/,fences:f,lheading:/^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/,paragraph:k(z).replace("hr",m).replace("heading"," *#{1,6} *[^\n]").replace("lheading",$).replace("|table","").replace("blockquote"," {0,3}>").replace("|fences","").replace("|list","").replace("|html","").replace("|tag","").getRegex()},Q=/^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,v=/^( {2,}|\\)\n(?!\s*$)/,B="\\p{P}\\p{S}",C=k(/^((?![*_])[\spunctuation])/,"u").replace(/punctuation/g,B).getRegex(),M=k(/^(?:\*+(?:((?!\*)[punct])|[^\s*]))|^_+(?:((?!_)[punct])|([^\s_]))/,"u").replace(/punct/g,B).getRegex(),O=k("^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)[punct](\\*+)(?=[\\s]|$)|[^punct\\s](\\*+)(?!\\*)(?=[punct\\s]|$)|(?!\\*)[punct\\s](\\*+)(?=[^punct\\s])|[\\s](\\*+)(?!\\*)(?=[punct])|(?!\\*)[punct](\\*+)(?!\\*)(?=[punct])|[^punct\\s](\\*+)(?=[^punct\\s])","gu").replace(/punct/g,B).getRegex(),D=k("^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)[punct](_+)(?=[\\s]|$)|[^punct\\s](_+)(?!_)(?=[punct\\s]|$)|(?!_)[punct\\s](_+)(?=[^punct\\s])|[\\s](_+)(?!_)(?=[punct])|(?!_)[punct](_+)(?!_)(?=[punct])","gu").replace(/punct/g,B).getRegex(),j=k(/\\([punct])/,"gu").replace(/punct/g,B).getRegex(),H=k(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/).replace("scheme",/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/).replace("email",/[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/).getRegex(),U=k(S).replace("(?:--\x3e|$)","--\x3e").getRegex(),X=k("^comment|^|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^|^").replace("comment",U).replace("attribute",/\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/).getRegex(),F=/(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/,N=k(/^!?\[(label)\]\(\s*(href)(?:\s+(title))?\s*\)/).replace("label",F).replace("href",/<(?:\\.|[^\n<>\\])+>|[^\s\x00-\x1f]*/).replace("title",/"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/).getRegex(),G=k(/^!?\[(label)\]\[(ref)\]/).replace("label",F).replace("ref",T).getRegex(),J=k(/^!?\[(ref)\](?:\[\])?/).replace("ref",T).getRegex(),K={_backpedal:f,anyPunctuation:j,autolink:H,blockSkip:/\[[^[\]]*?\]\([^\(\)]*?\)|`[^`]*?`|<[^<>]*?>/g,br:v,code:/^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,del:f,emStrongLDelim:M,emStrongRDelimAst:O,emStrongRDelimUnd:D,escape:Q,link:N,nolink:J,punctuation:C,reflink:G,reflinkSearch:k("reflink|nolink(?!\\()","g").replace("reflink",G).replace("nolink",J).getRegex(),tag:X,text:/^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\t+" ".repeat(n.length)));e;)if(!(this.options.extensions&&this.options.extensions.block&&this.options.extensions.block.some((s=>!!(n=s.call({lexer:this},e,t))&&(e=e.substring(n.raw.length),t.push(n),!0)))))if(n=this.tokenizer.space(e))e=e.substring(n.raw.length),1===n.raw.length&&t.length>0?t[t.length-1].raw+="\n":t.push(n);else if(n=this.tokenizer.code(e))e=e.substring(n.raw.length),s=t[t.length-1],!s||"paragraph"!==s.type&&"text"!==s.type?t.push(n):(s.raw+="\n"+n.raw,s.text+="\n"+n.text,this.inlineQueue[this.inlineQueue.length-1].src=s.text);else if(n=this.tokenizer.fences(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.heading(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.hr(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.blockquote(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.list(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.html(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.def(e))e=e.substring(n.raw.length),s=t[t.length-1],!s||"paragraph"!==s.type&&"text"!==s.type?this.tokens.links[n.tag]||(this.tokens.links[n.tag]={href:n.href,title:n.title}):(s.raw+="\n"+n.raw,s.text+="\n"+n.raw,this.inlineQueue[this.inlineQueue.length-1].src=s.text);else if(n=this.tokenizer.table(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.lheading(e))e=e.substring(n.raw.length),t.push(n);else{if(r=e,this.options.extensions&&this.options.extensions.startBlock){let t=1/0;const n=e.slice(1);let s;this.options.extensions.startBlock.forEach((e=>{s=e.call({lexer:this},n),"number"==typeof s&&s>=0&&(t=Math.min(t,s))})),t<1/0&&t>=0&&(r=e.substring(0,t+1))}if(this.state.top&&(n=this.tokenizer.paragraph(r)))s=t[t.length-1],i&&"paragraph"===s.type?(s.raw+="\n"+n.raw,s.text+="\n"+n.text,this.inlineQueue.pop(),this.inlineQueue[this.inlineQueue.length-1].src=s.text):t.push(n),i=r.length!==e.length,e=e.substring(n.raw.length);else if(n=this.tokenizer.text(e))e=e.substring(n.raw.length),s=t[t.length-1],s&&"text"===s.type?(s.raw+="\n"+n.raw,s.text+="\n"+n.text,this.inlineQueue.pop(),this.inlineQueue[this.inlineQueue.length-1].src=s.text):t.push(n);else if(e){const t="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(t);break}throw new Error(t)}}return this.state.top=!0,t}inline(e,t=[]){return this.inlineQueue.push({src:e,tokens:t}),t}inlineTokens(e,t=[]){let n,s,r,i,l,o,a=e;if(this.tokens.links){const e=Object.keys(this.tokens.links);if(e.length>0)for(;null!=(i=this.tokenizer.rules.inline.reflinkSearch.exec(a));)e.includes(i[0].slice(i[0].lastIndexOf("[")+1,-1))&&(a=a.slice(0,i.index)+"["+"a".repeat(i[0].length-2)+"]"+a.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex))}for(;null!=(i=this.tokenizer.rules.inline.blockSkip.exec(a));)a=a.slice(0,i.index)+"["+"a".repeat(i[0].length-2)+"]"+a.slice(this.tokenizer.rules.inline.blockSkip.lastIndex);for(;null!=(i=this.tokenizer.rules.inline.anyPunctuation.exec(a));)a=a.slice(0,i.index)+"++"+a.slice(this.tokenizer.rules.inline.anyPunctuation.lastIndex);for(;e;)if(l||(o=""),l=!1,!(this.options.extensions&&this.options.extensions.inline&&this.options.extensions.inline.some((s=>!!(n=s.call({lexer:this},e,t))&&(e=e.substring(n.raw.length),t.push(n),!0)))))if(n=this.tokenizer.escape(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.tag(e))e=e.substring(n.raw.length),s=t[t.length-1],s&&"text"===n.type&&"text"===s.type?(s.raw+=n.raw,s.text+=n.text):t.push(n);else if(n=this.tokenizer.link(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.reflink(e,this.tokens.links))e=e.substring(n.raw.length),s=t[t.length-1],s&&"text"===n.type&&"text"===s.type?(s.raw+=n.raw,s.text+=n.text):t.push(n);else if(n=this.tokenizer.emStrong(e,a,o))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.codespan(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.br(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.del(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.autolink(e))e=e.substring(n.raw.length),t.push(n);else if(this.state.inLink||!(n=this.tokenizer.url(e))){if(r=e,this.options.extensions&&this.options.extensions.startInline){let t=1/0;const n=e.slice(1);let s;this.options.extensions.startInline.forEach((e=>{s=e.call({lexer:this},n),"number"==typeof s&&s>=0&&(t=Math.min(t,s))})),t<1/0&&t>=0&&(r=e.substring(0,t+1))}if(n=this.tokenizer.inlineText(r))e=e.substring(n.raw.length),"_"!==n.raw.slice(-1)&&(o=n.raw.slice(-1)),l=!0,s=t[t.length-1],s&&"text"===s.type?(s.raw+=n.raw,s.text+=n.text):t.push(n);else if(e){const t="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(t);break}throw new Error(t)}}else e=e.substring(n.raw.length),t.push(n);return t}}class se{options;constructor(t){this.options=t||e.defaults}code(e,t,n){const s=(t||"").match(/^\S*/)?.[0];return e=e.replace(/\n$/,"")+"\n",s?'
'+(n?e:c(e,!0))+"
\n":"
"+(n?e:c(e,!0))+"
\n"}blockquote(e){return`
\n${e}
\n`}html(e,t){return e}heading(e,t,n){return`${e}\n`}hr(){return"
\n"}list(e,t,n){const s=t?"ol":"ul";return"<"+s+(t&&1!==n?' start="'+n+'"':"")+">\n"+e+"\n"}listitem(e,t,n){return`
  • ${e}
  • \n`}checkbox(e){return"'}paragraph(e){return`

    ${e}

    \n`}table(e,t){return t&&(t=`${t}`),"\n\n"+e+"\n"+t+"
    \n"}tablerow(e){return`\n${e}\n`}tablecell(e,t){const n=t.header?"th":"td";return(t.align?`<${n} align="${t.align}">`:`<${n}>`)+e+`\n`}strong(e){return`${e}`}em(e){return`${e}`}codespan(e){return`${e}`}br(){return"
    "}del(e){return`${e}`}link(e,t,n){const s=g(e);if(null===s)return n;let r='
    ",r}image(e,t,n){const s=g(e);if(null===s)return n;let r=`${n}0&&"paragraph"===n.tokens[0].type?(n.tokens[0].text=e+" "+n.tokens[0].text,n.tokens[0].tokens&&n.tokens[0].tokens.length>0&&"text"===n.tokens[0].tokens[0].type&&(n.tokens[0].tokens[0].text=e+" "+n.tokens[0].tokens[0].text)):n.tokens.unshift({type:"text",text:e+" "}):o+=e+" "}o+=this.parse(n.tokens,i),l+=this.renderer.listitem(o,r,!!s)}n+=this.renderer.list(l,t,s);continue}case"html":{const e=r;n+=this.renderer.html(e.text,e.block);continue}case"paragraph":{const e=r;n+=this.renderer.paragraph(this.parseInline(e.tokens));continue}case"text":{let i=r,l=i.tokens?this.parseInline(i.tokens):i.text;for(;s+1{const r=e[s].flat(1/0);n=n.concat(this.walkTokens(r,t))})):e.tokens&&(n=n.concat(this.walkTokens(e.tokens,t)))}}return n}use(...e){const t=this.defaults.extensions||{renderers:{},childTokens:{}};return e.forEach((e=>{const n={...e};if(n.async=this.defaults.async||n.async||!1,e.extensions&&(e.extensions.forEach((e=>{if(!e.name)throw new Error("extension name required");if("renderer"in e){const n=t.renderers[e.name];t.renderers[e.name]=n?function(...t){let s=e.renderer.apply(this,t);return!1===s&&(s=n.apply(this,t)),s}:e.renderer}if("tokenizer"in e){if(!e.level||"block"!==e.level&&"inline"!==e.level)throw new Error("extension level must be 'block' or 'inline'");const n=t[e.level];n?n.unshift(e.tokenizer):t[e.level]=[e.tokenizer],e.start&&("block"===e.level?t.startBlock?t.startBlock.push(e.start):t.startBlock=[e.start]:"inline"===e.level&&(t.startInline?t.startInline.push(e.start):t.startInline=[e.start]))}"childTokens"in e&&e.childTokens&&(t.childTokens[e.name]=e.childTokens)})),n.extensions=t),e.renderer){const t=this.defaults.renderer||new se(this.defaults);for(const n in e.renderer){if(!(n in t))throw new Error(`renderer '${n}' does not exist`);if("options"===n)continue;const s=n,r=e.renderer[s],i=t[s];t[s]=(...e)=>{let n=r.apply(t,e);return!1===n&&(n=i.apply(t,e)),n||""}}n.renderer=t}if(e.tokenizer){const t=this.defaults.tokenizer||new w(this.defaults);for(const n in e.tokenizer){if(!(n in t))throw new Error(`tokenizer '${n}' does not exist`);if(["options","rules","lexer"].includes(n))continue;const s=n,r=e.tokenizer[s],i=t[s];t[s]=(...e)=>{let n=r.apply(t,e);return!1===n&&(n=i.apply(t,e)),n}}n.tokenizer=t}if(e.hooks){const t=this.defaults.hooks||new le;for(const n in e.hooks){if(!(n in t))throw new Error(`hook '${n}' does not exist`);if("options"===n)continue;const s=n,r=e.hooks[s],i=t[s];le.passThroughHooks.has(n)?t[s]=e=>{if(this.defaults.async)return Promise.resolve(r.call(t,e)).then((e=>i.call(t,e)));const n=r.call(t,e);return i.call(t,n)}:t[s]=(...e)=>{let n=r.apply(t,e);return!1===n&&(n=i.apply(t,e)),n}}n.hooks=t}if(e.walkTokens){const t=this.defaults.walkTokens,s=e.walkTokens;n.walkTokens=function(e){let n=[];return n.push(s.call(this,e)),t&&(n=n.concat(t.call(this,e))),n}}this.defaults={...this.defaults,...n}})),this}setOptions(e){return this.defaults={...this.defaults,...e},this}lexer(e,t){return ne.lex(e,t??this.defaults)}parser(e,t){return ie.parse(e,t??this.defaults)}#e(e,t){return(n,s)=>{const r={...s},i={...this.defaults,...r};!0===this.defaults.async&&!1===r.async&&(i.silent||console.warn("marked(): The async option was set to true by an extension. The async: false option sent to parse will be ignored."),i.async=!0);const l=this.#t(!!i.silent,!!i.async);if(null==n)return l(new Error("marked(): input parameter is undefined or null"));if("string"!=typeof n)return l(new Error("marked(): input parameter is of type "+Object.prototype.toString.call(n)+", string expected"));if(i.hooks&&(i.hooks.options=i),i.async)return Promise.resolve(i.hooks?i.hooks.preprocess(n):n).then((t=>e(t,i))).then((e=>i.hooks?i.hooks.processAllTokens(e):e)).then((e=>i.walkTokens?Promise.all(this.walkTokens(e,i.walkTokens)).then((()=>e)):e)).then((e=>t(e,i))).then((e=>i.hooks?i.hooks.postprocess(e):e)).catch(l);try{i.hooks&&(n=i.hooks.preprocess(n));let s=e(n,i);i.hooks&&(s=i.hooks.processAllTokens(s)),i.walkTokens&&this.walkTokens(s,i.walkTokens);let r=t(s,i);return i.hooks&&(r=i.hooks.postprocess(r)),r}catch(e){return l(e)}}}#t(e,t){return n=>{if(n.message+="\nPlease report this to https://github.com/markedjs/marked.",e){const e="

    An error occurred:

    "+c(n.message+"",!0)+"
    ";return t?Promise.resolve(e):e}if(t)return Promise.reject(n);throw n}}}const ae=new oe;function ce(e,t){return ae.parse(e,t)}ce.options=ce.setOptions=function(e){return ae.setOptions(e),ce.defaults=ae.defaults,n(ce.defaults),ce},ce.getDefaults=t,ce.defaults=e.defaults,ce.use=function(...e){return ae.use(...e),ce.defaults=ae.defaults,n(ce.defaults),ce},ce.walkTokens=function(e,t){return ae.walkTokens(e,t)},ce.parseInline=ae.parseInline,ce.Parser=ie,ce.parser=ie.parse,ce.Renderer=se,ce.TextRenderer=re,ce.Lexer=ne,ce.lexer=ne.lex,ce.Tokenizer=w,ce.Hooks=le,ce.parse=ce;const he=ce.options,pe=ce.setOptions,ue=ce.use,ke=ce.walkTokens,ge=ce.parseInline,fe=ce,de=ie.parse,xe=ne.lex;e.Hooks=le,e.Lexer=ne,e.Marked=oe,e.Parser=ie,e.Renderer=se,e.TextRenderer=re,e.Tokenizer=w,e.getDefaults=t,e.lexer=xe,e.marked=ce,e.options=he,e.parse=fe,e.parseInline=ge,e.parser=de,e.setOptions=pe,e.use=ue,e.walkTokens=ke})); 7 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "Ease Bookmarks mv3", 4 | "description": "__MSG_pluginDesc__", 5 | "version": "3.1.4", 6 | "minimum_chrome_version": "120", 7 | "offline_enabled": true, 8 | "default_locale": "en", 9 | "icons": { 10 | "16": "icons/icon16.png", 11 | "32": "icons/icon32.png", 12 | "48": "icons/icon48.png", 13 | "128": "icons/icon128.png" 14 | }, 15 | 16 | "author": "qinxs", 17 | "homepage_url": "https://github.com/qinxs/Ease-Bookmarks", 18 | 19 | "action": { 20 | "default_title": "Ease Bookmarks", 21 | "default_popup": "popup.html", 22 | "default_icon": "icons/icon32.png" 23 | }, 24 | "background": { 25 | "service_worker": "eventPage.js" 26 | }, 27 | "options_page": "options.html", 28 | "commands": { 29 | "_execute_action": { 30 | "suggested_key": { 31 | "default": "Ctrl+Q", 32 | "mac": "Command+Q" 33 | }, 34 | "description": "Activate the extension" 35 | } 36 | }, 37 | 38 | "permissions": [ 39 | "storage", 40 | "tabs", 41 | "activeTab", 42 | "bookmarks", 43 | "contextMenus", 44 | "favicon" 45 | ], 46 | 47 | "web_accessible_resources": [{ 48 | "resources": [ "_favicon/*" ], 49 | "matches": [""], 50 | "extension_ids": [ "*" ] 51 | }] 52 | } -------------------------------------------------------------------------------- /src/md/usage-en.md: -------------------------------------------------------------------------------- 1 | # Instructions for use 2 | 3 | **Ease Bookmarks** is a browser extension designed to replace the native bookmarks bar 4 | 5 | It aims to accommodate the bookmark usage habits of various users while maintaining simplicity 6 | 7 | ## Key Features 8 | 9 | Modify the default opening behavior of bookmarks 10 | 11 | Perform basic operations on bookmarks (edit, delete, move, search, etc.) 12 | 13 | Multi-column display for bookmarks 14 | 15 | Keyboard shortcut support 16 | 17 | Special support for `JS bookmarklets` 18 | 19 | ## Keyboard Shortcuts 20 | 21 | ### Turn this extension on/off 22 | 23 | The default shortcut is `Ctrl + Q`, you can 24 | [modify it](chrome://extensions/shortcuts) 25 | in the following management page: 26 | - **Chrome**: `chrome://extensions/shortcuts` 27 | - **Edge**: `edge://extensions/shortcuts` 28 | - **Firefox**: `about:addons` `Settings Icon` `Manage Extension Shortcuts` 29 | 30 | ### Function keys 31 | 32 | - `↑`, `↓`, `←`, `→`, `Home`, `End`: select/toggle bookmarks 33 | - `Enter`: open the selected bookmark/folder 34 | - `Space`: cancel selection 35 | - `F2`: edit bookmark/folder(`Enter` Save; `Esc`、`F2` Cancel) 36 | - `Tab`: go back to the previous folder 37 | - `Ctrl + Z`: switch between "Bookmarks bar" and "Other bookmarks" 38 | - `Ctrl + F`: activate the search box 39 | - `Esc`: clear the search box content; close the page 40 | 41 | ### Modifier keys 42 | 43 | - `Ctrl`: whether to open the page in the background 44 | - `Shift`: open page in current tab/new tab 45 | 46 | ## Customize 47 | 48 | - Aliases (bookmarks bar and other bookmarks, may be required for other languages) 49 | - Custom style (`popup` page, DOM structure can be viewed in the header area `Right click -> Inspect`) 50 | 51 | ## Support 52 | 53 | [GitHub Issues](https://github.com/qinxs/Ease-Bookmarks/issues) - 54 | [Blog Message](https://7bxing.com/posts/beb3fd2a/) - 55 | Email: qinxs87@gmail.com 56 | 57 | [GitHub Star](https://github.com/qinxs/Ease-Bookmarks "If it's convenient, give a Star, thanks!") - 58 | [FAQ](https://github.com/qinxs/Ease-Bookmarks/wiki/常见问题(FAQ)) - 59 | [Change Log](https://github.com/qinxs/Ease-Bookmarks/blob/master/ChangeLog.md) - 60 | [Donate Welcome](https://7bxing.com/donate/) 61 | -------------------------------------------------------------------------------- /src/md/usage-zh.md: -------------------------------------------------------------------------------- 1 | # 使用说明 2 | 3 | **Ease Bookmarks** 是一款为了替代浏览器原有书签栏的扩展 4 | 5 | 在此基础上,尽可能适应不同用户的书签使用习惯,同时保持简单性 6 | 7 | ## 主要功能 8 | 9 | 修改书签的默认打开方式 10 | 11 | 对书签的各种基本操作(编辑、删除、移动、搜索等) 12 | 13 | 书签多列显示 14 | 15 | 快捷键支持 16 | 17 | 另外,本扩展对 `JS 小书签` 进行了特别支持~ 18 | 19 | ## 快捷键 20 | 21 | ### 打开/关闭 本扩展 22 | 23 | 默认快捷键是 `Ctrl + Q`,你可以在如下管理页面进行 24 | [修改](chrome://extensions/shortcuts): 25 | - **Chrome**:`chrome://extensions/shortcuts` 26 | - **Edge**:`edge://extensions/shortcuts` 27 | - **Firefox**:`about:addons` `设置图标` `管理扩展快捷键` 28 | 29 | ### 功能键 30 | 31 | - `↑`、`↓`、`←`、`→`、`Home`、`End`:选择/切换 书签 32 | - `Enter`:打开选中的 书签/目录 33 | - `Space`:取消选中 34 | - `F2`:编辑 书签/目录(`Enter` 保存;`Esc`、`F2` 取消) 35 | - `Tab`:返回上一级目录 36 | - `Ctrl + Z`:切换到 书签栏/其他书签 37 | - `Ctrl + F`:激活搜索框 38 | - `Esc`:清除搜索框内容;关闭页面 39 | 40 | ### 修饰键 41 | 42 | - `Ctrl`:是否在后台打开页面 43 | - `Shift`:在 当前标签/新标签 打开页面 44 | 45 | ## 自定义 46 | 47 | - 别名(书签栏和其他书签,其他语言可能会需要) 48 | - 自定义样式(`popup` 页面,DOM 结构可在 header 区域 `右键 -> 检查` 查看) 49 | 50 | ## 支持 51 | 52 | [GitHub Issues](https://github.com/qinxs/Ease-Bookmarks/issues) - 53 | [博客留言](https://7bxing.com/posts/beb3fd2a/) - 54 | 邮件:qin_xs@qq.com 55 | 56 | [GitHub Star](https://github.com/qinxs/Ease-Bookmarks "方便的话,给个 Star,感谢!") - 57 | [常见问题](https://github.com/qinxs/Ease-Bookmarks/wiki/常见问题(FAQ)) - 58 | [更新日志](https://github.com/qinxs/Ease-Bookmarks/blob/master/ChangeLog.md) - 59 | [欢迎捐赠](https://7bxing.com/donate/) 60 | -------------------------------------------------------------------------------- /src/options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Ease Bookmarks 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
    18 | 19 | 20 | 21 | 22 |
    23 |
    24 |
      25 |
    • 26 |
      27 |
      28 | 32 | 36 | 40 |
      41 |
    • 42 |
    • 43 |
      44 |
      45 | 49 | 53 | 57 |
      58 |
    • 59 |
    • 60 |
      61 |
      62 | 66 | 70 | 74 | 78 |
      79 |
    • 80 |
    • 81 |
      82 |
      83 | 87 | 91 | 96 |
      97 | 101 | 105 |
      106 |
    • 107 |
    • 108 |
      109 |
      110 | 114 | 118 |
      119 |
    • 120 |
    • 121 |
      122 |
      123 | 127 | 131 | 135 | 139 | 143 | 147 | 151 | 155 |
      156 |
    • 157 |
    • 158 |
      159 |
      160 | 161 |
      162 | 163 |
    • 164 |
    • 165 |
      166 |
      167 | 168 | 169 |
      170 |
    • 171 |
    • 172 |
      173 |
      174 | 175 | 176 | 177 |
      178 | 179 |
    • 180 |
    • 181 |
      182 |
      183 | 184 |
      185 |
    • 186 |
    • 187 |
      188 |
      189 | 190 | 191 | 192 |
      193 |
    • 194 |
    195 |
    196 |

    197 | 198 |
    199 |
    200 |
    201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | -------------------------------------------------------------------------------- /src/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Ease Bookmarks 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 43 |
    44 | 45 | 46 | 47 | 48 |
    49 | 50 |
    51 | 57 |
    58 | 59 | 60 |
    61 | 80 |
    81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /zip.bat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qinxs/Ease-Bookmarks/c33aad0bc766f36a34eb615e0c18de64e358da08/zip.bat --------------------------------------------------------------------------------