mirror of
https://github.com/Jonnyan404/memos-bber.git
synced 2026-04-24 19:48:37 +09:00
Add Edge extension and update release workflow
Add a complete Edge extension under edge/ (manifest, popup, CSS, JS, locales, assets, LICENSE) and a change.log. Update README with Edge/mobile notes and simplify content. Update GitHub Actions workflow to read edge/manifest.json version, package Edge builds, and include Edge asset in the release summary (also bump default release_tag). Minor update to firefox/js/oper.js and include .DS_Store change.
This commit is contained in:
@@ -9,7 +9,7 @@ on:
|
|||||||
release_tag:
|
release_tag:
|
||||||
description: Release tag used for direct asset upload, for example v2026.04.23
|
description: Release tag used for direct asset upload, for example v2026.04.23
|
||||||
required: true
|
required: true
|
||||||
default: 'v2026.04.23'
|
default: 'v2026.04.24'
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- 'v*'
|
- 'v*'
|
||||||
@@ -48,8 +48,15 @@ jobs:
|
|||||||
print(json.load(fp)['version'])
|
print(json.load(fp)['version'])
|
||||||
PY
|
PY
|
||||||
)
|
)
|
||||||
|
edge_version=$(python - <<'PY'
|
||||||
|
import json
|
||||||
|
with open('edge/manifest.json', 'r', encoding='utf-8') as fp:
|
||||||
|
print(json.load(fp)['version'])
|
||||||
|
PY
|
||||||
|
)
|
||||||
echo "chrome_version=$chrome_version" >> "$GITHUB_OUTPUT"
|
echo "chrome_version=$chrome_version" >> "$GITHUB_OUTPUT"
|
||||||
echo "firefox_version=$firefox_version" >> "$GITHUB_OUTPUT"
|
echo "firefox_version=$firefox_version" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "edge_version=$edge_version" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Build package files
|
- name: Build package files
|
||||||
run: |
|
run: |
|
||||||
@@ -59,6 +66,10 @@ jobs:
|
|||||||
zip -qr "../dist/release/memos-bber-chrome-${{ steps.versions.outputs.chrome_version }}.zip" .
|
zip -qr "../dist/release/memos-bber-chrome-${{ steps.versions.outputs.chrome_version }}.zip" .
|
||||||
popd >/dev/null
|
popd >/dev/null
|
||||||
|
|
||||||
|
pushd edge >/dev/null
|
||||||
|
zip -qr "../dist/release/memos-bber-edge-${{ steps.versions.outputs.edge_version }}.zip" .
|
||||||
|
popd >/dev/null
|
||||||
|
|
||||||
pushd firefox >/dev/null
|
pushd firefox >/dev/null
|
||||||
zip -qr "../dist/release/memos-bber-firefox-${{ steps.versions.outputs.firefox_version }}.xpi" .
|
zip -qr "../dist/release/memos-bber-firefox-${{ steps.versions.outputs.firefox_version }}.xpi" .
|
||||||
popd >/dev/null
|
popd >/dev/null
|
||||||
@@ -77,4 +88,5 @@ jobs:
|
|||||||
tag='${{ steps.release.outputs.tag }}'
|
tag='${{ steps.release.outputs.tag }}'
|
||||||
echo "## Release assets" >> "$GITHUB_STEP_SUMMARY"
|
echo "## Release assets" >> "$GITHUB_STEP_SUMMARY"
|
||||||
echo "- Chrome: https://github.com/${{ github.repository }}/releases/download/$tag/memos-bber-chrome-${{ steps.versions.outputs.chrome_version }}.zip" >> "$GITHUB_STEP_SUMMARY"
|
echo "- Chrome: https://github.com/${{ github.repository }}/releases/download/$tag/memos-bber-chrome-${{ steps.versions.outputs.chrome_version }}.zip" >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
echo "- Edge: https://github.com/${{ github.repository }}/releases/download/$tag/memos-bber-edge-${{ steps.versions.outputs.edge_version }}.zip" >> "$GITHUB_STEP_SUMMARY"
|
||||||
echo "- Firefox: https://github.com/${{ github.repository }}/releases/download/$tag/memos-bber-firefox-${{ steps.versions.outputs.firefox_version }}.xpi" >> "$GITHUB_STEP_SUMMARY"
|
echo "- Firefox: https://github.com/${{ github.repository }}/releases/download/$tag/memos-bber-firefox-${{ steps.versions.outputs.firefox_version }}.xpi" >> "$GITHUB_STEP_SUMMARY"
|
||||||
@@ -1,84 +1,19 @@
|
|||||||
## 说明
|
一款通过浏览器插件发布 [Memos](https://usememos.com/) 的插件。基于 iSpeak-bber 修改,原作者为 [DreamyTZK](https://www.antmoe.com/)。
|
||||||
|
|
||||||
|
## 在线商店安装
|
||||||
|
|
||||||
- Chrome 应用商店:https://chrome.google.com/webstore/detail/memos-bber/cbhjebjfccgchgbmfbobjmebjjckgofe/
|
- Chrome 应用商店:https://chrome.google.com/webstore/detail/memos-bber/cbhjebjfccgchgbmfbobjmebjjckgofe/
|
||||||
- (审核中)FireFox 应用商店: https://addons.mozilla.org/zh-CN/firefox/addon/memos-bber/
|
- (审核中)FireFox 应用商店: https://addons.mozilla.org/zh-CN/firefox/addon/memos-bber/
|
||||||
- edge 应用商店: 等待开发
|
- Edge: 等待上架
|
||||||
|
|
||||||
一个通过浏览器插件发布 [Memos](https://usememos.com/) 的插件。基于 iSpeak-bber 修改,原作者为 [DreamyTZK](https://www.antmoe.com/)。
|
## 移动端
|
||||||
|
|
||||||
|
Android
|
||||||
|
- 可直接用 chrome 扩展: https://github.com/uazo/cromite/releases
|
||||||
|
- firefox 手机版
|
||||||
|
- edge 手机版
|
||||||
|
|
||||||
## 更新日志
|
### 使用教程
|
||||||
- 20260422 调整发送设置,支持仅发送附件
|
|
||||||
#### 20260421 更新匹配 0.27.x
|
|
||||||
- 20260325 优化语言按钮样式
|
|
||||||
- 20260323 优化中文显示效果
|
|
||||||
- 20260322 适配移动端竖屏窗口
|
|
||||||
- 20260310 记忆拖拽窗口大小,移除拖拽窗口动画
|
|
||||||
- 20260309 右键发送选中文本保持原格式,增加全屏和窗口放大功能
|
|
||||||
#### 20260308 向前兼容到0.15.0,可能再往前也行,只测试到0.15.0
|
|
||||||
- 20260307 增加语言切换按钮以及韩语和日语支持,
|
|
||||||
- 2026年03月06日 右键菜单发送选中文本附带原文链接
|
|
||||||
- 2026年03月05日 向前兼容到0.24.0,可能再往前也行,因为只测试了0.24.0和0.25.0以及当前最新版本,如有更早版本需求,可issue反馈
|
|
||||||
- 2026年02月22日 由于原作者基本放弃更新,现接手维护,不兼容更新,匹配 v0.26.1 ,欢迎各位大佬PR
|
|
||||||
|
|
||||||
## 教程
|
|
||||||
|
|
||||||
- 在文本框输入想搜索的关键字,点击搜索按钮
|
- 在文本框输入想搜索的关键字,点击搜索按钮
|
||||||
- 随机和搜索功能在`0.24`以上版本支持私有权限的 memo,其它版本不支持
|
- 随机和搜索功能在`0.24`以上版本支持私有权限的 memo,其它版本不支持
|
||||||
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>点击展开/折叠内容</summary>
|
|
||||||
2024.07.21 不兼容更新,已匹配 v0.22.3
|
|
||||||
|
|
||||||
2024.06.15 感谢好心人 [PR#44](https://github.com/lmm214/memos-bber/pull/44)
|
|
||||||
|
|
||||||
2024.05.20 更新匹配至 v0.22
|
|
||||||
|
|
||||||
2023.09.19 不兼容更新匹配 Memos v0.15 的 `Access tokens` 模式。
|
|
||||||
|
|
||||||
<img width="483" alt="123" src="https://github.com/lmm214/memos-bber/assets/1472390/4ce2edc2-ce64-44d5-b4ef-d2e79b9d6a1a">
|
|
||||||
|
|
||||||
2023.07.16 支持 Memos v0.14.0 `api/v1`,同时兼容之前的 api。
|
|
||||||
|
|
||||||
2023.04.29 右键菜单的一系列改进,感谢 @EZForever 的 PR [#17](https://github.com/lmm214/memos-bber/pull/17)
|
|
||||||
|
|
||||||
2023.04.09 匹配 v0.12.0 附件链接由 filename 改为 publicId 。
|
|
||||||
|
|
||||||
2023.03.25 右键菜单发送文本改为“追加模式”(不刷新已打开页面、不刷新已打开页面、不刷新已打开页面时);新增多语言支持(en、zh-cn)。
|
|
||||||
|
|
||||||
2023.03.19 上传图片重命名精确的秒;打开插件时 focus 输入框(配合右键发送文本到扩展,设置快捷键打开扩展,按下 Ctrl+Enter 记下)。
|
|
||||||
|
|
||||||
2023.03.10 修复发布后调用最新一条 Memos。
|
|
||||||
|
|
||||||
2023.03.09 新增右键发送文本至 Memos 输入框。
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
2023.03.05 新增指定标签“仅自己可见”或“所有人可见”;图片上传文件名添加时间戳。
|
|
||||||
|
|
||||||
2023.02.26 更改 Memos 可见范围按钮样式。支持 Ctrl/Meta + Enter 记下。点击标题跳转主站。
|
|
||||||
|
|
||||||
2023.02.25 修复 v0.11.0 下随机按钮失效。(api amount 失效导致,换用 stats 获取总条数)
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
2023.02.07 新增发布后显示最新一条 Memo ,具体一条新增归档按钮。
|
|
||||||
|
|
||||||
2023.02.06 新增搜索按钮;新增图片灯箱。
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
2023.02.05 随机 Memos 支持指定标签 (算是彩蛋:标签列表点开,输入框内有且只有1个标签时,点击随机按钮)
|
|
||||||
|
|
||||||
2023.02.04 新增随机 Memos 按钮,随时唤醒记忆。
|
|
||||||
|
|
||||||
2022.11.15 新增插入文件图片按钮,尝试修复首次安装需要点一下小锁。
|
|
||||||
|
|
||||||
2022.11.13 新增插入 todo 按钮。
|
|
||||||
|
|
||||||
2022.11.8 支持拖拽上传附件(一个个传)。
|
|
||||||
|
|
||||||
2022.10.24 添加 visiblity 发送设置。
|
|
||||||
|
|
||||||
</details>
|
|
||||||
+71
@@ -0,0 +1,71 @@
|
|||||||
|
## 更新日志
|
||||||
|
- 20260423 优化 firefox 抖动问题和支持手机版,支持 edge 浏览器扩展
|
||||||
|
- 20260422 调整发送设置,支持仅发送附件
|
||||||
|
#### 20260421 更新匹配 0.27.x
|
||||||
|
- 20260325 优化语言按钮样式
|
||||||
|
- 20260323 优化中文显示效果
|
||||||
|
- 20260322 适配移动端竖屏窗口
|
||||||
|
- 20260310 记忆拖拽窗口大小,移除拖拽窗口动画
|
||||||
|
- 20260309 右键发送选中文本保持原格式,增加全屏和窗口放大功能
|
||||||
|
#### 20260308 向前兼容到0.15.0,可能再往前也行,只测试到0.15.0
|
||||||
|
- 20260307 增加语言切换按钮以及韩语和日语支持,
|
||||||
|
- 2026年03月06日 右键菜单发送选中文本附带原文链接
|
||||||
|
- 2026年03月05日 向前兼容到0.24.0,可能再往前也行,因为只测试了0.24.0和0.25.0以及当前最新版本,如有更早版本需求,可issue反馈
|
||||||
|
- 2026年02月22日 由于原作者基本放弃更新,现接手维护,不兼容更新,匹配 v0.26.1 ,欢迎各位大佬PR
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>点击展开/折叠内容</summary>
|
||||||
|
2024.07.21 不兼容更新,已匹配 v0.22.3
|
||||||
|
|
||||||
|
2024.06.15 感谢好心人 [PR#44](https://github.com/lmm214/memos-bber/pull/44)
|
||||||
|
|
||||||
|
2024.05.20 更新匹配至 v0.22
|
||||||
|
|
||||||
|
2023.09.19 不兼容更新匹配 Memos v0.15 的 `Access tokens` 模式。
|
||||||
|
|
||||||
|
<img width="483" alt="123" src="https://github.com/lmm214/memos-bber/assets/1472390/4ce2edc2-ce64-44d5-b4ef-d2e79b9d6a1a">
|
||||||
|
|
||||||
|
2023.07.16 支持 Memos v0.14.0 `api/v1`,同时兼容之前的 api。
|
||||||
|
|
||||||
|
2023.04.29 右键菜单的一系列改进,感谢 @EZForever 的 PR [#17](https://github.com/lmm214/memos-bber/pull/17)
|
||||||
|
|
||||||
|
2023.04.09 匹配 v0.12.0 附件链接由 filename 改为 publicId 。
|
||||||
|
|
||||||
|
2023.03.25 右键菜单发送文本改为“追加模式”(不刷新已打开页面、不刷新已打开页面、不刷新已打开页面时);新增多语言支持(en、zh-cn)。
|
||||||
|
|
||||||
|
2023.03.19 上传图片重命名精确的秒;打开插件时 focus 输入框(配合右键发送文本到扩展,设置快捷键打开扩展,按下 Ctrl+Enter 记下)。
|
||||||
|
|
||||||
|
2023.03.10 修复发布后调用最新一条 Memos。
|
||||||
|
|
||||||
|
2023.03.09 新增右键发送文本至 Memos 输入框。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
2023.03.05 新增指定标签“仅自己可见”或“所有人可见”;图片上传文件名添加时间戳。
|
||||||
|
|
||||||
|
2023.02.26 更改 Memos 可见范围按钮样式。支持 Ctrl/Meta + Enter 记下。点击标题跳转主站。
|
||||||
|
|
||||||
|
2023.02.25 修复 v0.11.0 下随机按钮失效。(api amount 失效导致,换用 stats 获取总条数)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
2023.02.07 新增发布后显示最新一条 Memo ,具体一条新增归档按钮。
|
||||||
|
|
||||||
|
2023.02.06 新增搜索按钮;新增图片灯箱。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
2023.02.05 随机 Memos 支持指定标签 (算是彩蛋:标签列表点开,输入框内有且只有1个标签时,点击随机按钮)
|
||||||
|
|
||||||
|
2023.02.04 新增随机 Memos 按钮,随时唤醒记忆。
|
||||||
|
|
||||||
|
2022.11.15 新增插入文件图片按钮,尝试修复首次安装需要点一下小锁。
|
||||||
|
|
||||||
|
2022.11.13 新增插入 todo 按钮。
|
||||||
|
|
||||||
|
2022.11.8 支持拖拽上传附件(一个个传)。
|
||||||
|
|
||||||
|
2022.10.24 添加 visiblity 发送设置。
|
||||||
|
|
||||||
|
</details>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2021 Charles Chin
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
{
|
||||||
|
"extName": {
|
||||||
|
"message": "Memos"
|
||||||
|
},
|
||||||
|
"actionTitle": {
|
||||||
|
"message": "Send Memos"
|
||||||
|
},
|
||||||
|
"extDescription": {
|
||||||
|
"message": "memos: A lightweight, self-hosted memo hub."
|
||||||
|
},
|
||||||
|
"sendTo": {
|
||||||
|
"message": "SendTo Memos \"%s\""
|
||||||
|
},
|
||||||
|
"sendLinkTo": {
|
||||||
|
"message": "Send link to Memos"
|
||||||
|
},
|
||||||
|
"sendImageTo": {
|
||||||
|
"message": "Send image to Memos"
|
||||||
|
},
|
||||||
|
"saveBtn":{
|
||||||
|
"message": "Save"
|
||||||
|
},
|
||||||
|
"supportedMemosVersion": {
|
||||||
|
"message": "Compatible with Memos v0.15.0 - 0.27.x"
|
||||||
|
},
|
||||||
|
"settingsConnectionTitle": {
|
||||||
|
"message": "Connection"
|
||||||
|
},
|
||||||
|
"settingsConnectionDesc": {
|
||||||
|
"message": "Configure the Memos site URL and access token."
|
||||||
|
},
|
||||||
|
"settingsPostingTitle": {
|
||||||
|
"message": "Posting"
|
||||||
|
},
|
||||||
|
"settingsPostingDesc": {
|
||||||
|
"message": "Default text for attachment-only sends"
|
||||||
|
},
|
||||||
|
"placeApiUrl":{
|
||||||
|
"message": "Memos site URL"
|
||||||
|
},
|
||||||
|
"placeApiTokens":{
|
||||||
|
"message": "Memos Access Tokens"
|
||||||
|
},
|
||||||
|
"placeContent":{
|
||||||
|
"message": "What's on your mind..."
|
||||||
|
},
|
||||||
|
"lockPrivate":{
|
||||||
|
"message": "Private"
|
||||||
|
},
|
||||||
|
"lockProtected":{
|
||||||
|
"message": "Protected"
|
||||||
|
},
|
||||||
|
"lockPublic":{
|
||||||
|
"message": "Public"
|
||||||
|
},
|
||||||
|
"submitBtn":{
|
||||||
|
"message": "Save"
|
||||||
|
},
|
||||||
|
"placeHideInput":{
|
||||||
|
"message": "Default 'Private' tag name"
|
||||||
|
},
|
||||||
|
"placeShowInput":{
|
||||||
|
"message": "Default 'Everyone can see' Tag name"
|
||||||
|
},
|
||||||
|
"placeAttachmentOnlyDefaultText": {
|
||||||
|
"message": "Default text for attachment-only sends (leave blank to use built-in text)"
|
||||||
|
},
|
||||||
|
"uploadedListTitle": {
|
||||||
|
"message": "Uploaded files, Drag to reorder"
|
||||||
|
},
|
||||||
|
"uploadedListEmpty": {
|
||||||
|
"message": "No uploaded files"
|
||||||
|
},
|
||||||
|
"tipReorder": {
|
||||||
|
"message": "Drag to reorder"
|
||||||
|
},
|
||||||
|
"tipDeleteAttachment": {
|
||||||
|
"message": "Delete"
|
||||||
|
},
|
||||||
|
"attachmentDeleteSuccess": {
|
||||||
|
"message": "Deleted"
|
||||||
|
},
|
||||||
|
"attachmentDeleteFailed": {
|
||||||
|
"message": "Delete failed 😭"
|
||||||
|
},
|
||||||
|
"picDrag":{
|
||||||
|
"message": "Drag upload the image"
|
||||||
|
},
|
||||||
|
"picCancelDrag":{
|
||||||
|
"message": "Cancel upload"
|
||||||
|
},
|
||||||
|
"picUploading":{
|
||||||
|
"message": "Upload the picture..."
|
||||||
|
},
|
||||||
|
"picSuccess":{
|
||||||
|
"message": "Upload completed"
|
||||||
|
},
|
||||||
|
"picFailed":{
|
||||||
|
"message": "Uploading failed"
|
||||||
|
},
|
||||||
|
"picPending":{
|
||||||
|
"message": "Image uploading is in progress"
|
||||||
|
},
|
||||||
|
"saveSuccess":{
|
||||||
|
"message": "Save Info Success!"
|
||||||
|
},
|
||||||
|
"searchNow":{
|
||||||
|
"message": "What are you looking for?"
|
||||||
|
},
|
||||||
|
"searchNone":{
|
||||||
|
"message": "Try another word!"
|
||||||
|
},
|
||||||
|
"archiveSuccess":{
|
||||||
|
"message": "Archive Success 😊"
|
||||||
|
},
|
||||||
|
"archiveFailed":{
|
||||||
|
"message": "Archive Failed 😭"
|
||||||
|
},
|
||||||
|
"getTabFailed":{
|
||||||
|
"message": "Get Tab Failed 😭"
|
||||||
|
},
|
||||||
|
"memoUploading":{
|
||||||
|
"message": "Sending"
|
||||||
|
},
|
||||||
|
"memoSuccess":{
|
||||||
|
"message": "Success! 😊"
|
||||||
|
},
|
||||||
|
"memoFailed":{
|
||||||
|
"message": "Failed! 😭"
|
||||||
|
},
|
||||||
|
"invalidToken":{
|
||||||
|
"message": "Invalid token or url 😭"
|
||||||
|
},
|
||||||
|
"tipOpenSite": {
|
||||||
|
"message": "Open Memos"
|
||||||
|
},
|
||||||
|
"tipSettings": {
|
||||||
|
"message": "Settings"
|
||||||
|
},
|
||||||
|
"tipTags": {
|
||||||
|
"message": "Insert tag"
|
||||||
|
},
|
||||||
|
"tipTodo": {
|
||||||
|
"message": "Insert todo"
|
||||||
|
},
|
||||||
|
"tipUpload": {
|
||||||
|
"message": "Upload file"
|
||||||
|
},
|
||||||
|
"tipLink": {
|
||||||
|
"message": "Insert current tab link"
|
||||||
|
},
|
||||||
|
"tipRandom": {
|
||||||
|
"message": "Random memo"
|
||||||
|
},
|
||||||
|
"tipSearch": {
|
||||||
|
"message": "Search"
|
||||||
|
},
|
||||||
|
"tipVisibility": {
|
||||||
|
"message": "Visibility"
|
||||||
|
},
|
||||||
|
"tipSend": {
|
||||||
|
"message": "Send (Ctrl/⌘+Enter)"
|
||||||
|
},
|
||||||
|
"tipLanguage": {
|
||||||
|
"message": "Language"
|
||||||
|
},
|
||||||
|
"langAuto": {
|
||||||
|
"message": "Auto"
|
||||||
|
},
|
||||||
|
"langEnglish": {
|
||||||
|
"message": "English"
|
||||||
|
},
|
||||||
|
"langChineseSimplified": {
|
||||||
|
"message": "简体中文"
|
||||||
|
},
|
||||||
|
"langJapanese": {
|
||||||
|
"message": "日本語"
|
||||||
|
},
|
||||||
|
"langKorean": {
|
||||||
|
"message": "한국어"
|
||||||
|
},
|
||||||
|
"tipFullscreen": {
|
||||||
|
"message": "Open fullscreen editor"
|
||||||
|
},
|
||||||
|
"tipResize": {
|
||||||
|
"message": "Drag to resize (min: default size)"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
{
|
||||||
|
"extName": {
|
||||||
|
"message": "Memos"
|
||||||
|
},
|
||||||
|
"actionTitle": {
|
||||||
|
"message": "Memos に送信"
|
||||||
|
},
|
||||||
|
"extDescription": {
|
||||||
|
"message": "memos: 軽量なセルフホスト型メモハブ。"
|
||||||
|
},
|
||||||
|
"sendTo": {
|
||||||
|
"message": "Memos に \"%s\" を送信"
|
||||||
|
},
|
||||||
|
"sendLinkTo": {
|
||||||
|
"message": "リンクを Memos に送信"
|
||||||
|
},
|
||||||
|
"sendImageTo": {
|
||||||
|
"message": "画像を Memos に送信"
|
||||||
|
},
|
||||||
|
"saveBtn": {
|
||||||
|
"message": "保存"
|
||||||
|
},
|
||||||
|
"supportedMemosVersion": {
|
||||||
|
"message": "Memos v0.15.0 - 0.27.x に対応"
|
||||||
|
},
|
||||||
|
"settingsConnectionTitle": {
|
||||||
|
"message": "接続設定"
|
||||||
|
},
|
||||||
|
"settingsConnectionDesc": {
|
||||||
|
"message": "Memos のURLとアクセストークンを設定します。"
|
||||||
|
},
|
||||||
|
"settingsPostingTitle": {
|
||||||
|
"message": "投稿設定"
|
||||||
|
},
|
||||||
|
"settingsPostingDesc": {
|
||||||
|
"message": "添付ファイルのみ送信時の既定テキスト"
|
||||||
|
},
|
||||||
|
"placeApiUrl": {
|
||||||
|
"message": "Memos サイトURL"
|
||||||
|
},
|
||||||
|
"placeApiTokens": {
|
||||||
|
"message": "Memos アクセストークン"
|
||||||
|
},
|
||||||
|
"placeContent": {
|
||||||
|
"message": "今のメモは…"
|
||||||
|
},
|
||||||
|
"lockPrivate": {
|
||||||
|
"message": "非公開"
|
||||||
|
},
|
||||||
|
"lockProtected": {
|
||||||
|
"message": "保護"
|
||||||
|
},
|
||||||
|
"lockPublic": {
|
||||||
|
"message": "公開"
|
||||||
|
},
|
||||||
|
"submitBtn": {
|
||||||
|
"message": "送信"
|
||||||
|
},
|
||||||
|
"placeHideInput": {
|
||||||
|
"message": "既定の「非公開」タグ名"
|
||||||
|
},
|
||||||
|
"placeShowInput": {
|
||||||
|
"message": "既定の「全員に公開」タグ名"
|
||||||
|
},
|
||||||
|
"placeAttachmentOnlyDefaultText": {
|
||||||
|
"message": "添付ファイルのみ送信時の既定テキスト(空欄で内蔵文言を使用)"
|
||||||
|
},
|
||||||
|
"uploadedListTitle": {
|
||||||
|
"message": "アップロード済みファイル(ドラッグで並べ替え)"
|
||||||
|
},
|
||||||
|
"uploadedListEmpty": {
|
||||||
|
"message": "アップロード済みファイルはありません"
|
||||||
|
},
|
||||||
|
"tipReorder": {
|
||||||
|
"message": "ドラッグして並べ替え"
|
||||||
|
},
|
||||||
|
"tipDeleteAttachment": {
|
||||||
|
"message": "削除"
|
||||||
|
},
|
||||||
|
"attachmentDeleteSuccess": {
|
||||||
|
"message": "削除しました"
|
||||||
|
},
|
||||||
|
"attachmentDeleteFailed": {
|
||||||
|
"message": "削除に失敗しました 😭"
|
||||||
|
},
|
||||||
|
"picDrag": {
|
||||||
|
"message": "画像をここにドラッグしてアップロード"
|
||||||
|
},
|
||||||
|
"picCancelDrag": {
|
||||||
|
"message": "アップロードをキャンセル"
|
||||||
|
},
|
||||||
|
"picUploading": {
|
||||||
|
"message": "画像をアップロード中..."
|
||||||
|
},
|
||||||
|
"picSuccess": {
|
||||||
|
"message": "アップロード完了"
|
||||||
|
},
|
||||||
|
"picFailed": {
|
||||||
|
"message": "アップロード失敗"
|
||||||
|
},
|
||||||
|
"picPending": {
|
||||||
|
"message": "画像のアップロードが進行中です"
|
||||||
|
},
|
||||||
|
"saveSuccess": {
|
||||||
|
"message": "保存しました!"
|
||||||
|
},
|
||||||
|
"searchNow": {
|
||||||
|
"message": "何を探していますか?"
|
||||||
|
},
|
||||||
|
"searchNone": {
|
||||||
|
"message": "別のキーワードを試してください!"
|
||||||
|
},
|
||||||
|
"archiveSuccess": {
|
||||||
|
"message": "アーカイブ成功 😊"
|
||||||
|
},
|
||||||
|
"archiveFailed": {
|
||||||
|
"message": "アーカイブ失敗 😭"
|
||||||
|
},
|
||||||
|
"getTabFailed": {
|
||||||
|
"message": "タブの取得に失敗 😭"
|
||||||
|
},
|
||||||
|
"memoUploading": {
|
||||||
|
"message": "送信中"
|
||||||
|
},
|
||||||
|
"memoSuccess": {
|
||||||
|
"message": "成功!😊"
|
||||||
|
},
|
||||||
|
"memoFailed": {
|
||||||
|
"message": "失敗!😭"
|
||||||
|
},
|
||||||
|
"invalidToken": {
|
||||||
|
"message": "無効なトークンまたはURL 😭"
|
||||||
|
},
|
||||||
|
"tipOpenSite": {
|
||||||
|
"message": "Memos を開く"
|
||||||
|
},
|
||||||
|
"tipSettings": {
|
||||||
|
"message": "設定"
|
||||||
|
},
|
||||||
|
"tipTags": {
|
||||||
|
"message": "タグを挿入"
|
||||||
|
},
|
||||||
|
"tipTodo": {
|
||||||
|
"message": "ToDo を挿入"
|
||||||
|
},
|
||||||
|
"tipUpload": {
|
||||||
|
"message": "ファイルをアップロード"
|
||||||
|
},
|
||||||
|
"tipLink": {
|
||||||
|
"message": "現在のタブのリンクを挿入"
|
||||||
|
},
|
||||||
|
"tipRandom": {
|
||||||
|
"message": "ランダムメモ"
|
||||||
|
},
|
||||||
|
"tipSearch": {
|
||||||
|
"message": "検索"
|
||||||
|
},
|
||||||
|
"tipVisibility": {
|
||||||
|
"message": "公開範囲"
|
||||||
|
},
|
||||||
|
"tipSend": {
|
||||||
|
"message": "送信(Ctrl/⌘+Enter)"
|
||||||
|
},
|
||||||
|
"tipLanguage": {
|
||||||
|
"message": "言語"
|
||||||
|
},
|
||||||
|
"langAuto": {
|
||||||
|
"message": "自動"
|
||||||
|
},
|
||||||
|
"langEnglish": {
|
||||||
|
"message": "English"
|
||||||
|
},
|
||||||
|
"langChineseSimplified": {
|
||||||
|
"message": "简体中文"
|
||||||
|
},
|
||||||
|
"langJapanese": {
|
||||||
|
"message": "日本語"
|
||||||
|
},
|
||||||
|
"langKorean": {
|
||||||
|
"message": "한국어"
|
||||||
|
},
|
||||||
|
"tipFullscreen": {
|
||||||
|
"message": "全画面で編集"
|
||||||
|
},
|
||||||
|
"tipResize": {
|
||||||
|
"message": "ドラッグで拡大/縮小(最小:初期サイズ)"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
{
|
||||||
|
"extName": {
|
||||||
|
"message": "Memos"
|
||||||
|
},
|
||||||
|
"actionTitle": {
|
||||||
|
"message": "Memos 보내기"
|
||||||
|
},
|
||||||
|
"extDescription": {
|
||||||
|
"message": "memos: 가볍고 셀프호스팅 가능한 메모 허브."
|
||||||
|
},
|
||||||
|
"sendTo": {
|
||||||
|
"message": "Memos로 \"%s\" 보내기"
|
||||||
|
},
|
||||||
|
"sendLinkTo": {
|
||||||
|
"message": "링크를 Memos로 보내기"
|
||||||
|
},
|
||||||
|
"sendImageTo": {
|
||||||
|
"message": "이미지를 Memos로 보내기"
|
||||||
|
},
|
||||||
|
"saveBtn": {
|
||||||
|
"message": "저장"
|
||||||
|
},
|
||||||
|
"supportedMemosVersion": {
|
||||||
|
"message": "Memos v0.15.0 - 0.27.x 호환"
|
||||||
|
},
|
||||||
|
"settingsConnectionTitle": {
|
||||||
|
"message": "연결 설정"
|
||||||
|
},
|
||||||
|
"settingsConnectionDesc": {
|
||||||
|
"message": "Memos 사이트 URL과 액세스 토큰을 설정합니다."
|
||||||
|
},
|
||||||
|
"settingsPostingTitle": {
|
||||||
|
"message": "전송 설정"
|
||||||
|
},
|
||||||
|
"settingsPostingDesc": {
|
||||||
|
"message": "첨부만 전송할 때의 기본 텍스트"
|
||||||
|
},
|
||||||
|
"placeApiUrl": {
|
||||||
|
"message": "Memos 사이트 URL"
|
||||||
|
},
|
||||||
|
"placeApiTokens": {
|
||||||
|
"message": "Memos 액세스 토큰"
|
||||||
|
},
|
||||||
|
"placeContent": {
|
||||||
|
"message": "지금 떠오른 생각은..."
|
||||||
|
},
|
||||||
|
"lockPrivate": {
|
||||||
|
"message": "비공개"
|
||||||
|
},
|
||||||
|
"lockProtected": {
|
||||||
|
"message": "보호됨"
|
||||||
|
},
|
||||||
|
"lockPublic": {
|
||||||
|
"message": "공개"
|
||||||
|
},
|
||||||
|
"submitBtn": {
|
||||||
|
"message": "전송"
|
||||||
|
},
|
||||||
|
"placeHideInput": {
|
||||||
|
"message": "기본 '비공개' 태그 이름"
|
||||||
|
},
|
||||||
|
"placeShowInput": {
|
||||||
|
"message": "기본 '모두 공개' 태그 이름"
|
||||||
|
},
|
||||||
|
"placeAttachmentOnlyDefaultText": {
|
||||||
|
"message": "첨부만 전송할 때의 기본 텍스트(비워두면 내장 문구 사용)"
|
||||||
|
},
|
||||||
|
"uploadedListTitle": {
|
||||||
|
"message": "업로드된 파일(드래그로 순서 변경)"
|
||||||
|
},
|
||||||
|
"uploadedListEmpty": {
|
||||||
|
"message": "업로드된 파일이 없습니다"
|
||||||
|
},
|
||||||
|
"tipReorder": {
|
||||||
|
"message": "드래그하여 순서 변경"
|
||||||
|
},
|
||||||
|
"tipDeleteAttachment": {
|
||||||
|
"message": "삭제"
|
||||||
|
},
|
||||||
|
"attachmentDeleteSuccess": {
|
||||||
|
"message": "삭제됨"
|
||||||
|
},
|
||||||
|
"attachmentDeleteFailed": {
|
||||||
|
"message": "삭제 실패 😭"
|
||||||
|
},
|
||||||
|
"picDrag": {
|
||||||
|
"message": "이미지를 드래그하여 업로드"
|
||||||
|
},
|
||||||
|
"picCancelDrag": {
|
||||||
|
"message": "업로드 취소"
|
||||||
|
},
|
||||||
|
"picUploading": {
|
||||||
|
"message": "이미지 업로드 중..."
|
||||||
|
},
|
||||||
|
"picSuccess": {
|
||||||
|
"message": "업로드 완료"
|
||||||
|
},
|
||||||
|
"picFailed": {
|
||||||
|
"message": "업로드 실패"
|
||||||
|
},
|
||||||
|
"picPending": {
|
||||||
|
"message": "이미지 업로드가 진행 중입니다"
|
||||||
|
},
|
||||||
|
"saveSuccess": {
|
||||||
|
"message": "저장 성공!"
|
||||||
|
},
|
||||||
|
"searchNow": {
|
||||||
|
"message": "무엇을 찾고 있나요?"
|
||||||
|
},
|
||||||
|
"searchNone": {
|
||||||
|
"message": "다른 단어를 시도해 보세요!"
|
||||||
|
},
|
||||||
|
"archiveSuccess": {
|
||||||
|
"message": "보관 성공 😊"
|
||||||
|
},
|
||||||
|
"archiveFailed": {
|
||||||
|
"message": "보관 실패 😭"
|
||||||
|
},
|
||||||
|
"getTabFailed": {
|
||||||
|
"message": "탭 가져오기 실패 😭"
|
||||||
|
},
|
||||||
|
"memoUploading": {
|
||||||
|
"message": "전송 중"
|
||||||
|
},
|
||||||
|
"memoSuccess": {
|
||||||
|
"message": "성공! 😊"
|
||||||
|
},
|
||||||
|
"memoFailed": {
|
||||||
|
"message": "실패! 😭"
|
||||||
|
},
|
||||||
|
"invalidToken": {
|
||||||
|
"message": "유효하지 않은 토큰 또는 URL 😭"
|
||||||
|
},
|
||||||
|
"tipOpenSite": {
|
||||||
|
"message": "Memos 열기"
|
||||||
|
},
|
||||||
|
"tipSettings": {
|
||||||
|
"message": "설정"
|
||||||
|
},
|
||||||
|
"tipTags": {
|
||||||
|
"message": "태그 삽입"
|
||||||
|
},
|
||||||
|
"tipTodo": {
|
||||||
|
"message": "할 일 삽입"
|
||||||
|
},
|
||||||
|
"tipUpload": {
|
||||||
|
"message": "파일 업로드"
|
||||||
|
},
|
||||||
|
"tipLink": {
|
||||||
|
"message": "현재 탭 링크 삽입"
|
||||||
|
},
|
||||||
|
"tipRandom": {
|
||||||
|
"message": "랜덤 메모"
|
||||||
|
},
|
||||||
|
"tipSearch": {
|
||||||
|
"message": "검색"
|
||||||
|
},
|
||||||
|
"tipVisibility": {
|
||||||
|
"message": "공개 범위"
|
||||||
|
},
|
||||||
|
"tipSend": {
|
||||||
|
"message": "전송(Ctrl/⌘+Enter)"
|
||||||
|
},
|
||||||
|
"tipLanguage": {
|
||||||
|
"message": "언어"
|
||||||
|
},
|
||||||
|
"langAuto": {
|
||||||
|
"message": "자동"
|
||||||
|
},
|
||||||
|
"langEnglish": {
|
||||||
|
"message": "English"
|
||||||
|
},
|
||||||
|
"langChineseSimplified": {
|
||||||
|
"message": "简体中文"
|
||||||
|
},
|
||||||
|
"langJapanese": {
|
||||||
|
"message": "日本語"
|
||||||
|
},
|
||||||
|
"langKorean": {
|
||||||
|
"message": "한국어"
|
||||||
|
},
|
||||||
|
"tipFullscreen": {
|
||||||
|
"message": "전체화면 편집"
|
||||||
|
},
|
||||||
|
"tipResize": {
|
||||||
|
"message": "드래그로 확대/축소(최소: 기본 크기)"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
{
|
||||||
|
"extName": {
|
||||||
|
"message": "Memos"
|
||||||
|
},
|
||||||
|
"actionTitle": {
|
||||||
|
"message": "发送 Memos"
|
||||||
|
},
|
||||||
|
"extDescription": {
|
||||||
|
"message": "一键发送灵感时刻,珍藏你的记忆"
|
||||||
|
},
|
||||||
|
"sendTo": {
|
||||||
|
"message": "发送至 Memos “%s”"
|
||||||
|
},
|
||||||
|
"sendLinkTo": {
|
||||||
|
"message": "发送链接至 Memos"
|
||||||
|
},
|
||||||
|
"sendImageTo": {
|
||||||
|
"message": "发送图片至 Memos"
|
||||||
|
},
|
||||||
|
"saveBtn":{
|
||||||
|
"message": "保存"
|
||||||
|
},
|
||||||
|
"supportedMemosVersion": {
|
||||||
|
"message": "兼容 Memos v0.15.0 - 0.27.x"
|
||||||
|
},
|
||||||
|
"settingsConnectionTitle": {
|
||||||
|
"message": "连接设置"
|
||||||
|
},
|
||||||
|
"settingsConnectionDesc": {
|
||||||
|
"message": "配置 Memos 服务地址和访问令牌。"
|
||||||
|
},
|
||||||
|
"settingsPostingTitle": {
|
||||||
|
"message": "发送设置"
|
||||||
|
},
|
||||||
|
"settingsPostingDesc": {
|
||||||
|
"message": "仅发送附件时的默认文本"
|
||||||
|
},
|
||||||
|
"placeApiUrl":{
|
||||||
|
"message": "请填入 Memos 主页网址"
|
||||||
|
},
|
||||||
|
"placeApiTokens":{
|
||||||
|
"message": "请填入 Memos Access Tokens"
|
||||||
|
},
|
||||||
|
"placeContent":{
|
||||||
|
"message": "现在的想法是..."
|
||||||
|
},
|
||||||
|
"lockPrivate":{
|
||||||
|
"message": "私有"
|
||||||
|
},
|
||||||
|
"lockProtected":{
|
||||||
|
"message": "登录可见"
|
||||||
|
},
|
||||||
|
"lockPublic":{
|
||||||
|
"message": "公开"
|
||||||
|
},
|
||||||
|
"submitBtn":{
|
||||||
|
"message": "记下"
|
||||||
|
},
|
||||||
|
"placeHideInput":{
|
||||||
|
"message": "默认“私有”标签名"
|
||||||
|
},
|
||||||
|
"placeShowInput":{
|
||||||
|
"message": "默认“公开”标签名"
|
||||||
|
},
|
||||||
|
"placeAttachmentOnlyDefaultText":{
|
||||||
|
"message": "仅发送附件时的默认文本(留空则使用内置文案)"
|
||||||
|
},
|
||||||
|
"picDrag":{
|
||||||
|
"message": "拖拽到窗口上传该图片"
|
||||||
|
},
|
||||||
|
"picCancelDrag":{
|
||||||
|
"message": "取消上传"
|
||||||
|
},
|
||||||
|
"picUploading":{
|
||||||
|
"message": "图片上传中……"
|
||||||
|
},
|
||||||
|
"picSuccess":{
|
||||||
|
"message": "上传完成"
|
||||||
|
},
|
||||||
|
"picFailed":{
|
||||||
|
"message": "上传图片失败"
|
||||||
|
},
|
||||||
|
"picPending":{
|
||||||
|
"message": "有图片等待上传"
|
||||||
|
},
|
||||||
|
"saveSuccess":{
|
||||||
|
"message": "保存信息成功"
|
||||||
|
},
|
||||||
|
"searchNow":{
|
||||||
|
"message": "想搜点啥?"
|
||||||
|
},
|
||||||
|
"searchNone":{
|
||||||
|
"message": "搜不到,换个词试试"
|
||||||
|
},
|
||||||
|
"archiveSuccess":{
|
||||||
|
"message": "归档成功!😊"
|
||||||
|
},
|
||||||
|
"archiveFailed":{
|
||||||
|
"message": "归档失败 😭"
|
||||||
|
},
|
||||||
|
"getTabFailed":{
|
||||||
|
"message": "获取标签失败 😭"
|
||||||
|
},
|
||||||
|
"memoUploading":{
|
||||||
|
"message": "发送中……"
|
||||||
|
},
|
||||||
|
"memoSuccess":{
|
||||||
|
"message": "发送成功!😊"
|
||||||
|
},
|
||||||
|
"memoFailed":{
|
||||||
|
"message": "发送失败 😭"
|
||||||
|
},
|
||||||
|
"invalidToken":{
|
||||||
|
"message": "无效的 token 或 url 😭"
|
||||||
|
},
|
||||||
|
"uploadedListTitle": {
|
||||||
|
"message": "已上传文件,可拖动排序"
|
||||||
|
},
|
||||||
|
"uploadedListEmpty": {
|
||||||
|
"message": "暂无已上传文件"
|
||||||
|
},
|
||||||
|
"tipReorder": {
|
||||||
|
"message": "拖动排序"
|
||||||
|
},
|
||||||
|
"tipDeleteAttachment": {
|
||||||
|
"message": "删除"
|
||||||
|
},
|
||||||
|
"attachmentDeleteSuccess": {
|
||||||
|
"message": "删除成功"
|
||||||
|
},
|
||||||
|
"attachmentDeleteFailed": {
|
||||||
|
"message": "删除失败 😭"
|
||||||
|
},
|
||||||
|
"tipOpenSite": {
|
||||||
|
"message": "打开 Memos"
|
||||||
|
},
|
||||||
|
"tipSettings": {
|
||||||
|
"message": "设置"
|
||||||
|
},
|
||||||
|
"tipTags": {
|
||||||
|
"message": "插入标签"
|
||||||
|
},
|
||||||
|
"tipTodo": {
|
||||||
|
"message": "插入待办"
|
||||||
|
},
|
||||||
|
"tipUpload": {
|
||||||
|
"message": "上传文件"
|
||||||
|
},
|
||||||
|
"tipLink": {
|
||||||
|
"message": "插入当前页面链接"
|
||||||
|
},
|
||||||
|
"tipRandom": {
|
||||||
|
"message": "随机一条"
|
||||||
|
},
|
||||||
|
"tipSearch": {
|
||||||
|
"message": "搜索"
|
||||||
|
},
|
||||||
|
"tipVisibility": {
|
||||||
|
"message": "可见性"
|
||||||
|
},
|
||||||
|
"tipSend": {
|
||||||
|
"message": "发送(Ctrl/⌘+Enter)"
|
||||||
|
},
|
||||||
|
"tipLanguage": {
|
||||||
|
"message": "语言"
|
||||||
|
},
|
||||||
|
"langAuto": {
|
||||||
|
"message": "跟随浏览器"
|
||||||
|
},
|
||||||
|
"langEnglish": {
|
||||||
|
"message": "English"
|
||||||
|
},
|
||||||
|
"langChineseSimplified": {
|
||||||
|
"message": "简体中文"
|
||||||
|
},
|
||||||
|
"langJapanese": {
|
||||||
|
"message": "日本語"
|
||||||
|
},
|
||||||
|
"langKorean": {
|
||||||
|
"message": "한국어"
|
||||||
|
},
|
||||||
|
"tipFullscreen": {
|
||||||
|
"message": "全屏编辑"
|
||||||
|
},
|
||||||
|
"tipResize": {
|
||||||
|
"message": "拖拽缩放编辑框(最小为默认大小)"
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 6.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,658 @@
|
|||||||
|
button, input, textarea {
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 100%;
|
||||||
|
font-weight: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
color: inherit;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
input:focus::-moz-placeholder ,.common-editor-inputer:focus::-moz-placeholder {
|
||||||
|
color: #d3d3d3
|
||||||
|
}
|
||||||
|
input::placeholder ,.common-editor-inputer::placeholder {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
input:focus::placeholder ,.common-editor-inputer:focus::placeholder {
|
||||||
|
color: #d3d3d3
|
||||||
|
}
|
||||||
|
|
||||||
|
.body{
|
||||||
|
min-width:360px;
|
||||||
|
background-color: #f6f5f4;
|
||||||
|
padding:0 1rem 1rem;
|
||||||
|
font-family: eafont,PingFang SC,Hiragino Sans GB,Microsoft YaHei,STHeiti,WenQuanYi Micro Hei,Helvetica,Arial,sans-serif;
|
||||||
|
font-size: 16px;
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
a{color: #555;}
|
||||||
|
.title{
|
||||||
|
width: 100px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1.125rem;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 2.5rem;
|
||||||
|
color: rgb(55,65,81);
|
||||||
|
}
|
||||||
|
.memo-editor,.random-item{
|
||||||
|
border: 2px solid rgb(229,231,235);
|
||||||
|
border-radius: .5rem;
|
||||||
|
background-color: rgb(255,255,255);
|
||||||
|
margin-top:0.8rem;
|
||||||
|
padding: 0.6rem;
|
||||||
|
}
|
||||||
|
.memo-editor{
|
||||||
|
position: relative;
|
||||||
|
resize: none;
|
||||||
|
overflow: visible;
|
||||||
|
box-sizing: border-box;
|
||||||
|
contain: layout;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.memo-editor-header{
|
||||||
|
position: sticky;
|
||||||
|
top: .5rem;
|
||||||
|
z-index: 3;
|
||||||
|
height: 0;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#editor-resize-handle{
|
||||||
|
position: absolute;
|
||||||
|
right: .35rem;
|
||||||
|
bottom: .35rem;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
border-right: 2px solid #bbb;
|
||||||
|
border-bottom: 2px solid #bbb;
|
||||||
|
cursor: nwse-resize;
|
||||||
|
opacity: .8;
|
||||||
|
user-select: none;
|
||||||
|
touch-action: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#editor-resize-handle:hover{
|
||||||
|
opacity: 1;
|
||||||
|
border-right-color: #888;
|
||||||
|
border-bottom-color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body.fullscreen #editor-resize-handle{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body.fullscreen #fullscreen{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.random-item{
|
||||||
|
border: 1px solid rgb(229,231,235);
|
||||||
|
color: #666;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
.random-time{font-size:13px;margin-bottom:6px;color: #999;font-style:italic;}
|
||||||
|
.random-time span{float: right;margin-top: -3px;cursor: pointer;}
|
||||||
|
.random-time span svg.icon{width:15px;height:15px;padding:4px 6px 0;opacity: 0.6;margin-left:4px;}
|
||||||
|
.random-time span svg.icon:hover{opacity: 1;}
|
||||||
|
|
||||||
|
.random-image{width:180px;height:180px;max-width:100%;object-fit: cover;border-radius: .25rem;margin:5px 5px 5px 0;}
|
||||||
|
.random-content{width:100%;
|
||||||
|
max-width:100%;
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
word-break: normal;}
|
||||||
|
.btns-container{text-align:right;}
|
||||||
|
.memo-editor #fullscreen{
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
z-index: 2;
|
||||||
|
border: 1px solid rgb(229,231,235);
|
||||||
|
border-radius: .25rem;
|
||||||
|
background-color: rgb(255,255,255);
|
||||||
|
color: #666;
|
||||||
|
font-size: .75rem;
|
||||||
|
line-height: 1;
|
||||||
|
padding: .25rem;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: .9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.memo-editor #fullscreen svg{
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.memo-editor #fullscreen:hover{
|
||||||
|
opacity: 1;
|
||||||
|
background-color: rgb(243,244,246);
|
||||||
|
}
|
||||||
|
.common-editor-inputer,input.inputer{
|
||||||
|
font-family: ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
resize: none;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
background-color: transparent;
|
||||||
|
font-size: 1rem;
|
||||||
|
min-height: 40px;
|
||||||
|
scrollbar-width: none;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.common-editor-inputer{
|
||||||
|
padding-right: 1.5rem;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-height: 0;
|
||||||
|
display: block;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body.fullscreen{
|
||||||
|
min-width: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body.fullscreen .common-editor-inputer{
|
||||||
|
min-height: 60vh;
|
||||||
|
}
|
||||||
|
input.inputer{border-bottom: 1px solid #ccc;width:75%;}
|
||||||
|
|
||||||
|
.settings-panel{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: .75rem;
|
||||||
|
margin-top: .75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-section{
|
||||||
|
border: 1px solid rgb(229,231,235);
|
||||||
|
border-radius: .5rem;
|
||||||
|
background-color: rgb(255,255,255);
|
||||||
|
padding: .75rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: .6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-section-title{
|
||||||
|
font-size: .9rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: rgb(55,65,81);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-section-desc{
|
||||||
|
font-size: .75rem;
|
||||||
|
line-height: 1.35;
|
||||||
|
color: #7a7a7a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-input{
|
||||||
|
width: 100% !important;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid rgb(229,231,235);
|
||||||
|
border-radius: .35rem;
|
||||||
|
background-color: #fafafa;
|
||||||
|
padding: .55rem .7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-input:focus{
|
||||||
|
border-color: rgb(22,163,74);
|
||||||
|
background-color: rgb(255,255,255);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-textarea{
|
||||||
|
resize: vertical;
|
||||||
|
min-height: 4.5rem;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-actions{
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
#saveKey{margin:0;flex:1;}
|
||||||
|
|
||||||
|
.common-tools-wrapper {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 1rem;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-list-wrapper{
|
||||||
|
margin-top: .5rem;
|
||||||
|
}
|
||||||
|
.upload-list-title{
|
||||||
|
font-size: .875rem;
|
||||||
|
color: #999;
|
||||||
|
margin-top: .5rem;
|
||||||
|
margin-bottom: .25rem;
|
||||||
|
}
|
||||||
|
.upload-list{
|
||||||
|
border: 1px solid rgb(229,231,235);
|
||||||
|
border-radius: .5rem;
|
||||||
|
background-color: rgb(255,255,255);
|
||||||
|
padding: .25rem;
|
||||||
|
}
|
||||||
|
.upload-empty{
|
||||||
|
padding: .5rem .75rem;
|
||||||
|
font-size: .875rem;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
.upload-item{
|
||||||
|
display:flex;
|
||||||
|
align-items:center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: .4rem .5rem;
|
||||||
|
border-radius: .25rem;
|
||||||
|
color:#666;
|
||||||
|
}
|
||||||
|
.upload-item + .upload-item{
|
||||||
|
border-top: 1px solid rgb(243,244,246);
|
||||||
|
}
|
||||||
|
.upload-item.drag-over{
|
||||||
|
background-color: rgb(243,244,246);
|
||||||
|
}
|
||||||
|
.upload-left{
|
||||||
|
display:flex;
|
||||||
|
align-items:center;
|
||||||
|
min-width: 0;
|
||||||
|
gap: .5rem;
|
||||||
|
}
|
||||||
|
.upload-drag{
|
||||||
|
cursor: grab;
|
||||||
|
opacity: .6;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.upload-filename{
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
font-size: .875rem;
|
||||||
|
}
|
||||||
|
.upload-del{
|
||||||
|
cursor:pointer;
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1;
|
||||||
|
padding: .15rem .35rem;
|
||||||
|
border-radius: .25rem;
|
||||||
|
opacity: .6;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
.upload-del:hover{
|
||||||
|
opacity: 1;
|
||||||
|
background-color: rgb(243,244,246);
|
||||||
|
}
|
||||||
|
.common-tools-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
.confirm-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
cursor: pointer;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: rgb(22,163,74);
|
||||||
|
padding:0 1rem;
|
||||||
|
font-size: .875rem;
|
||||||
|
line-height: 2rem;
|
||||||
|
color: rgb(255,255,255);
|
||||||
|
box-shadow: 0 1px 3px 0
|
||||||
|
rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);
|
||||||
|
user-select: none;
|
||||||
|
border-radius: .25rem;
|
||||||
|
border-style: none;
|
||||||
|
opacity: .6;
|
||||||
|
}
|
||||||
|
.confirm-btn:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.confirm-btn:disabled{
|
||||||
|
opacity: .6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.common-tools-container .mr-5{margin-right: .5rem;}
|
||||||
|
.common-tools-container svg.icon,#blog_info_edit svg.icon{width:24px;height:24px;opacity: 0.6;cursor: pointer;}
|
||||||
|
#newtodo svg.icon{padding-top: 2px;}
|
||||||
|
#tags svg.icon{padding: 2px;width:23px;height:23px;}
|
||||||
|
#random svg.icon{padding:2px;width:19px;height:19px;}
|
||||||
|
.common-tools-container svg.icon:hover{opacity: 1;}
|
||||||
|
|
||||||
|
#locked,#taglist,#visibilitylis,#randomlist{display: none;}
|
||||||
|
.tag-list,.visibility-list {
|
||||||
|
margin-top: .5rem;
|
||||||
|
max-height: 13rem;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
font-family: ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;
|
||||||
|
}
|
||||||
|
.tag-list>.item-container,.visibility-list >.item-lock,.tag-list .hidetag{
|
||||||
|
display: inline-block;
|
||||||
|
background-color: #666;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: .2rem .5rem;
|
||||||
|
border-radius: .25rem;
|
||||||
|
font-size: .875rem;
|
||||||
|
line-height: 1.25rem;
|
||||||
|
color: #fff;
|
||||||
|
margin:0 6px 6px 0;
|
||||||
|
}
|
||||||
|
.tag-list .hidetag{padding:0;float:right;background-color:#ddd;}
|
||||||
|
.tag-list .hidetag:hover{background-color:#666;}
|
||||||
|
.tag-hide{display: none;}
|
||||||
|
.tag-hide input.inputer{width:40%;font-size:11px;}
|
||||||
|
|
||||||
|
.visibility-list .item-lock.lock-now{
|
||||||
|
background-color:rgb(22,163,74);
|
||||||
|
}
|
||||||
|
#blog_info_edit{
|
||||||
|
position: absolute;
|
||||||
|
right: 1rem;
|
||||||
|
top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lang-switcher{
|
||||||
|
position: absolute;
|
||||||
|
right: 3.5rem;
|
||||||
|
top: .55rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lang-toggle{
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
background-color: transparent;
|
||||||
|
color: #666;
|
||||||
|
min-width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0;
|
||||||
|
opacity: .6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lang-toggle:hover,
|
||||||
|
.lang-toggle[aria-expanded="true"]{
|
||||||
|
background-color: transparent;
|
||||||
|
color: #666;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lang-toggle-text{
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 24px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 24px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: .02em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lang-menu{
|
||||||
|
position: absolute;
|
||||||
|
top: calc(100% + .35rem);
|
||||||
|
right: 0;
|
||||||
|
min-width: 8rem;
|
||||||
|
padding: .25rem;
|
||||||
|
border: 1px solid rgb(229,231,235);
|
||||||
|
border-radius: .5rem;
|
||||||
|
background-color: rgb(255,255,255);
|
||||||
|
box-shadow: 0 8px 24px rgba(15,23,42,.12);
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lang-menu.hidden{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lang-menu-item{
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
text-align: left;
|
||||||
|
padding: .4rem .5rem;
|
||||||
|
border-radius: .35rem;
|
||||||
|
background: transparent;
|
||||||
|
color: #555;
|
||||||
|
font-size: .75rem;
|
||||||
|
line-height: 1.25rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lang-menu-item:hover{
|
||||||
|
background-color: rgb(243,244,246);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lang-menu-item.active{
|
||||||
|
background-color: rgb(220,252,231);
|
||||||
|
color: rgb(22,101,52);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.tip{
|
||||||
|
margin-left: 36%;
|
||||||
|
max-width: 640px;
|
||||||
|
position: fixed;
|
||||||
|
text-align: center;
|
||||||
|
top: 15px;
|
||||||
|
width: 58%;
|
||||||
|
z-index: 10001;
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-info{
|
||||||
|
background: -webkit-gradient(linear,left top,right top,from(#9c51ff),to(#816bff));
|
||||||
|
background: -webkit-linear-gradient(90deg,#9c51ff,#816bff);
|
||||||
|
background: linear-gradient(90deg,#9c51ff,#816bff);
|
||||||
|
-moz-box-shadow: 3px 3px 20px #d7ceff38;
|
||||||
|
-webkit-box-shadow: 3px 3px 20px #d7ceff38;
|
||||||
|
box-shadow: 3px 3px 20px #d7ceff38;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 8px 40px;
|
||||||
|
display: inline-block;
|
||||||
|
border-radius: 3px;
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes bounceIn {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
-webkit-transform: scale(.3);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 1;
|
||||||
|
-webkit-transform: scale(1);
|
||||||
|
}
|
||||||
|
70% {
|
||||||
|
-webkit-transform: scale(.95);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
-webkit-transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bounceIn {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(.3);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
70% {
|
||||||
|
transform: scale(.95);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bounceIn {
|
||||||
|
-webkit-animation-name: bounceIn;
|
||||||
|
animation-name: bounceIn;
|
||||||
|
}
|
||||||
|
.animate {
|
||||||
|
-webkit-animation-duration: .3s;
|
||||||
|
animation-duration: .3s;
|
||||||
|
-webkit-animation-fill-mode: both;
|
||||||
|
animation-fill-mode: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.\!hidden{
|
||||||
|
display: none!important;
|
||||||
|
}
|
||||||
|
.selector-wrapper {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
height: 2rem;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: flex-start
|
||||||
|
}
|
||||||
|
.selector-wrapper>.current-value-container {
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
border-radius: .25rem;
|
||||||
|
border-width: 1px;
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
||||||
|
padding-left: .5rem;
|
||||||
|
padding-right: .25rem;
|
||||||
|
}
|
||||||
|
.selector-wrapper>.current-value-container>.value-text {
|
||||||
|
margin-right: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
font-size: .875rem
|
||||||
|
}
|
||||||
|
.selector-wrapper>.current-value-container>.value-text {
|
||||||
|
width: calc(100% - 20px)
|
||||||
|
}
|
||||||
|
.selector-wrapper>.current-value-container>.lock-text {
|
||||||
|
margin-right: .25rem;
|
||||||
|
display: flex;
|
||||||
|
width: 1rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center
|
||||||
|
}
|
||||||
|
.selector-wrapper>.current-value-container>.arrow-text {
|
||||||
|
display: flex;
|
||||||
|
width: 1rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center
|
||||||
|
}
|
||||||
|
.selector-wrapper>.current-value-container>.arrow-text>.icon-img {
|
||||||
|
height: auto;
|
||||||
|
width: 1rem;
|
||||||
|
opacity: .4
|
||||||
|
}
|
||||||
|
.selector-wrapper>.items-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 100%;
|
||||||
|
left: 0px;
|
||||||
|
z-index: 1;
|
||||||
|
margin-top: .25rem;
|
||||||
|
margin-left: -.5rem;
|
||||||
|
display: flex;
|
||||||
|
width: auto;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
overflow-y: auto;
|
||||||
|
border-radius: .375rem;
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
||||||
|
padding: .25rem;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
scrollbar-width: none
|
||||||
|
}
|
||||||
|
.selector-wrapper>.items-wrapper::-webkit-scrollbar {
|
||||||
|
display: none
|
||||||
|
}
|
||||||
|
.selector-wrapper>.items-wrapper {
|
||||||
|
min-width: calc(100% + 16px);
|
||||||
|
max-height: 256px;
|
||||||
|
box-shadow: 0 0 8px #0003
|
||||||
|
}
|
||||||
|
.selector-wrapper>.items-wrapper>.item-lock {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
white-space: nowrap;
|
||||||
|
border-radius: .25rem;
|
||||||
|
padding-left: .75rem;
|
||||||
|
padding-right: .75rem;
|
||||||
|
font-size: .875rem;
|
||||||
|
line-height: 2rem
|
||||||
|
}
|
||||||
|
.selector-wrapper>.items-wrapper>.item-lock:hover {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(243 244 246 / var(--tw-bg-opacity))
|
||||||
|
}
|
||||||
|
.selector-wrapper>.items-wrapper>.item-lock.selected {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(22 163 74 / var(--tw-text-opacity))
|
||||||
|
}
|
||||||
|
|
||||||
|
.selector-wrapper>.items-wrapper>.tip-text {
|
||||||
|
padding: .25rem .75rem;
|
||||||
|
font-size: .875rem;
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(75 85 99 / var(--tw-text-opacity))
|
||||||
|
}
|
||||||
|
.selector-wrapper>.selector-disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
pointer-events: none;
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(229 231 235 / var(--tw-bg-opacity));
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(156 163 175 / var(--tw-text-opacity))
|
||||||
|
}
|
||||||
@@ -0,0 +1,218 @@
|
|||||||
|
const UI_LANGUAGE_STORAGE_KEY = 'uiLanguage'
|
||||||
|
|
||||||
|
const SUPPORTED_UI_LANGUAGES = new Set(['auto', 'en', 'zh_CN', 'ja', 'ko'])
|
||||||
|
|
||||||
|
function normalizeUiLanguage(value) {
|
||||||
|
const lang = String(value || 'auto')
|
||||||
|
return SUPPORTED_UI_LANGUAGES.has(lang) ? lang : 'auto'
|
||||||
|
}
|
||||||
|
|
||||||
|
function storageSyncGet(defaults) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
chrome.storage.sync.get(defaults, (items) => resolve(items || {}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateContextMenu(id, update) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
try {
|
||||||
|
chrome.contextMenus.update(id, update, () => resolve())
|
||||||
|
} catch (_) {
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function pageReadSelectionText() {
|
||||||
|
try {
|
||||||
|
const active = document.activeElement
|
||||||
|
const isTextInput =
|
||||||
|
active &&
|
||||||
|
(active.tagName === 'TEXTAREA' ||
|
||||||
|
(active.tagName === 'INPUT' &&
|
||||||
|
/^(text|search|url|tel|email|password)$/i.test(active.type || 'text')))
|
||||||
|
|
||||||
|
if (isTextInput && typeof active.selectionStart === 'number' && typeof active.selectionEnd === 'number') {
|
||||||
|
return String(active.value || '').slice(active.selectionStart, active.selectionEnd).replace(/\r\n?/g, '\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
const sel = window.getSelection && window.getSelection()
|
||||||
|
if (!sel) return ''
|
||||||
|
return String(sel.toString() || '').replace(/\r\n?/g, '\n')
|
||||||
|
} catch (_) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSelectionTextFromTab(tabId, fallbackText) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const fallback = typeof fallbackText === 'string' ? fallbackText : ''
|
||||||
|
if (!tabId || !chrome.scripting || typeof chrome.scripting.executeScript !== 'function') {
|
||||||
|
resolve(fallback)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
chrome.scripting.executeScript(
|
||||||
|
{
|
||||||
|
target: { tabId },
|
||||||
|
func: pageReadSelectionText
|
||||||
|
},
|
||||||
|
(results) => {
|
||||||
|
if (chrome.runtime.lastError) {
|
||||||
|
resolve(fallback)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const first = Array.isArray(results) ? results[0] : null
|
||||||
|
const text = first && typeof first.result === 'string' ? first.result : ''
|
||||||
|
resolve(text || fallback)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} catch (_) {
|
||||||
|
resolve(fallback)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function tryOpenActionPopup(tab) {
|
||||||
|
try {
|
||||||
|
if (!chrome.action || typeof chrome.action.openPopup !== 'function') return
|
||||||
|
const windowId = tab && typeof tab.windowId === 'number' ? tab.windowId : undefined
|
||||||
|
|
||||||
|
const open = () => {
|
||||||
|
try {
|
||||||
|
if (typeof windowId === 'number') {
|
||||||
|
chrome.action.openPopup({ windowId }, () => void chrome.runtime.lastError)
|
||||||
|
} else {
|
||||||
|
chrome.action.openPopup({}, () => void chrome.runtime.lastError)
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
// best-effort only
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid: "Cannot show popup for an inactive window".
|
||||||
|
if (typeof windowId === 'number' && chrome.windows && typeof chrome.windows.update === 'function') {
|
||||||
|
chrome.windows.update(windowId, { focused: true }, () => {
|
||||||
|
void chrome.runtime.lastError
|
||||||
|
open()
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
open()
|
||||||
|
} catch (_) {
|
||||||
|
// best-effort only
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let cachedUiLanguage = null
|
||||||
|
let cachedOverrideMessages = null
|
||||||
|
|
||||||
|
async function loadLocaleMessages(locale) {
|
||||||
|
if (!locale || locale === 'auto') return null
|
||||||
|
try {
|
||||||
|
const url = chrome.runtime.getURL(`_locales/${locale}/messages.json`)
|
||||||
|
const resp = await fetch(url)
|
||||||
|
if (!resp.ok) return null
|
||||||
|
return await resp.json()
|
||||||
|
} catch (_) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getUiLanguage() {
|
||||||
|
const items = await storageSyncGet({ [UI_LANGUAGE_STORAGE_KEY]: 'auto' })
|
||||||
|
return normalizeUiLanguage(items[UI_LANGUAGE_STORAGE_KEY])
|
||||||
|
}
|
||||||
|
|
||||||
|
async function t(key) {
|
||||||
|
const lang = await getUiLanguage()
|
||||||
|
if (lang !== cachedUiLanguage) {
|
||||||
|
cachedUiLanguage = lang
|
||||||
|
cachedOverrideMessages = await loadLocaleMessages(lang)
|
||||||
|
}
|
||||||
|
|
||||||
|
const msg = cachedOverrideMessages && cachedOverrideMessages[key] && cachedOverrideMessages[key].message
|
||||||
|
if (typeof msg === 'string' && msg.length > 0) return msg
|
||||||
|
return chrome.i18n.getMessage(key) || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshContextMenus() {
|
||||||
|
await updateContextMenu('Memos-send-selection', { title: await t('sendTo') })
|
||||||
|
await updateContextMenu('Memos-send-link', { title: await t('sendLinkTo') })
|
||||||
|
await updateContextMenu('Memos-send-image', { title: await t('sendImageTo') })
|
||||||
|
}
|
||||||
|
|
||||||
|
chrome.runtime.onInstalled.addListener(() => {
|
||||||
|
chrome.contextMenus.create({
|
||||||
|
type: 'normal',
|
||||||
|
title: chrome.i18n.getMessage('sendTo'),
|
||||||
|
id: 'Memos-send-selection',
|
||||||
|
contexts: ['selection']
|
||||||
|
})
|
||||||
|
chrome.contextMenus.create({
|
||||||
|
type: 'normal',
|
||||||
|
title: chrome.i18n.getMessage('sendLinkTo'),
|
||||||
|
id: 'Memos-send-link',
|
||||||
|
contexts: ['link', 'page']
|
||||||
|
})
|
||||||
|
chrome.contextMenus.create({
|
||||||
|
type: 'normal',
|
||||||
|
title: chrome.i18n.getMessage('sendImageTo'),
|
||||||
|
id: 'Memos-send-image',
|
||||||
|
contexts: ['image']
|
||||||
|
})
|
||||||
|
|
||||||
|
// Apply override titles if user selected a fixed language.
|
||||||
|
refreshContextMenus()
|
||||||
|
})
|
||||||
|
|
||||||
|
chrome.storage.onChanged.addListener((changes, areaName) => {
|
||||||
|
if (areaName !== 'sync') return
|
||||||
|
if (!changes[UI_LANGUAGE_STORAGE_KEY]) return
|
||||||
|
cachedUiLanguage = null
|
||||||
|
cachedOverrideMessages = null
|
||||||
|
refreshContextMenus()
|
||||||
|
})
|
||||||
|
|
||||||
|
chrome.contextMenus.onClicked.addListener((info, tab) => {
|
||||||
|
const appendContent = (tempCont, { openPopup } = { openPopup: false }) => {
|
||||||
|
chrome.storage.sync.get({ open_action: 'save_text', open_content: '' }, function (items) {
|
||||||
|
if (items.open_action === 'upload_image') {
|
||||||
|
t('picPending').then((m) => alert(m))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
chrome.storage.sync.set(
|
||||||
|
{
|
||||||
|
open_action: 'save_text',
|
||||||
|
open_content: items.open_content + tempCont
|
||||||
|
},
|
||||||
|
function () {
|
||||||
|
if (openPopup) tryOpenActionPopup(tab)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.menuItemId === 'Memos-send-selection') {
|
||||||
|
const ref = info.linkUrl || info.pageUrl
|
||||||
|
const tabId = tab && tab.id
|
||||||
|
|
||||||
|
getSelectionTextFromTab(tabId, info.selectionText).then((selectionText) => {
|
||||||
|
const tempCont = selectionText + '\n' + `[Reference Link](${ref})` + '\n'
|
||||||
|
appendContent(tempCont, { openPopup: true })
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.menuItemId === 'Memos-send-link') {
|
||||||
|
appendContent((info.linkUrl || info.pageUrl) + '\n')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.menuItemId === 'Memos-send-image') {
|
||||||
|
appendContent(`` + '\n')
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,521 @@
|
|||||||
|
(function (global) {
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
const FLAVOR_V020_V021 = 'v020-v021'
|
||||||
|
const KNOWN_FLAVORS = [FLAVOR_V020_V021, 'v023', 'modern']
|
||||||
|
|
||||||
|
function requestJson(options, success, fail) {
|
||||||
|
global.$
|
||||||
|
.ajax(options)
|
||||||
|
.done(function (data) {
|
||||||
|
if (success) success(data)
|
||||||
|
})
|
||||||
|
.fail(function (xhr) {
|
||||||
|
if (fail) fail(xhr)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractMemos(data) {
|
||||||
|
if (global.MemosApiModern && typeof global.MemosApiModern.extractMemosListFromResponse === 'function') {
|
||||||
|
return global.MemosApiModern.extractMemosListFromResponse(data)
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFlavor(info) {
|
||||||
|
if (!info) return 'legacy'
|
||||||
|
if (info.apiFlavor === 'modern' && global.MemosApiV023) return 'modern'
|
||||||
|
if (info.apiFlavor === 'v023' && global.MemosApiV023) return 'v023'
|
||||||
|
if ((info.apiFlavor === FLAVOR_V020_V021 || info.apiFlavor === 'v1') && global.MemosApiV020V021) {
|
||||||
|
return FLAVOR_V020_V021
|
||||||
|
}
|
||||||
|
return 'legacy'
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeDetectedFlavor(flavor) {
|
||||||
|
const value = typeof flavor === 'string' ? flavor : ''
|
||||||
|
if (value === 'v020' || value === 'v021' || value === 'v1') return FLAVOR_V020_V021
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
function looksLikeMemosListPayload(data) {
|
||||||
|
if (!data) return false
|
||||||
|
if (Array.isArray(data)) return true
|
||||||
|
if (Array.isArray(data.memos)) return true
|
||||||
|
if (data.data && Array.isArray(data.data.memos)) return true
|
||||||
|
if (Array.isArray(data.list)) return true
|
||||||
|
if (typeof data.error === 'string' || typeof data.message === 'string') return false
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
function isNotFoundLikeProbeXhr(xhr) {
|
||||||
|
const status = xhr && xhr.status
|
||||||
|
return status === 404 || status === 405
|
||||||
|
}
|
||||||
|
|
||||||
|
function probeFlavor(apiUrl, apiTokens, callback) {
|
||||||
|
const headers = { Authorization: 'Bearer ' + apiTokens }
|
||||||
|
const modernQ =
|
||||||
|
'api/v1/memos?pageSize=1&filter=' +
|
||||||
|
encodeURIComponent('visibility in ["PUBLIC","PROTECTED"]')
|
||||||
|
const v023Q =
|
||||||
|
'api/v1/memos?pageSize=1&filter=' +
|
||||||
|
encodeURIComponent('visibilities == ["PUBLIC","PROTECTED"]')
|
||||||
|
const v020V021Q = 'api/v1/memo?limit=1&rowStatus=NORMAL'
|
||||||
|
|
||||||
|
function finish(flavor) {
|
||||||
|
const normalized = normalizeDetectedFlavor(flavor)
|
||||||
|
if (KNOWN_FLAVORS.indexOf(normalized) !== -1) {
|
||||||
|
if (callback) callback({ flavor: normalized })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (callback) callback({ flavor: 'unknown' })
|
||||||
|
}
|
||||||
|
|
||||||
|
function probeV023() {
|
||||||
|
global.$
|
||||||
|
.ajax({
|
||||||
|
url: apiUrl + v023Q,
|
||||||
|
method: 'GET',
|
||||||
|
headers: headers,
|
||||||
|
dataType: 'json'
|
||||||
|
})
|
||||||
|
.done(function (data) {
|
||||||
|
if (looksLikeMemosListPayload(data)) finish('v023')
|
||||||
|
else finish('unknown')
|
||||||
|
})
|
||||||
|
.fail(function () {
|
||||||
|
finish('unknown')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
global.$
|
||||||
|
.ajax({
|
||||||
|
url: apiUrl + modernQ,
|
||||||
|
method: 'GET',
|
||||||
|
headers: headers,
|
||||||
|
dataType: 'json'
|
||||||
|
})
|
||||||
|
.done(function (data) {
|
||||||
|
if (looksLikeMemosListPayload(data)) {
|
||||||
|
finish('modern')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
probeV023()
|
||||||
|
})
|
||||||
|
.fail(function (xhr) {
|
||||||
|
if (xhr && xhr.status === 400) {
|
||||||
|
probeV023()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNotFoundLikeProbeXhr(xhr)) {
|
||||||
|
global.$
|
||||||
|
.ajax({
|
||||||
|
url: apiUrl + v020V021Q,
|
||||||
|
method: 'GET',
|
||||||
|
headers: headers,
|
||||||
|
dataType: 'json'
|
||||||
|
})
|
||||||
|
.done(function (data) {
|
||||||
|
if (looksLikeMemosListPayload(data)) finish(FLAVOR_V020_V021)
|
||||||
|
else finish('unknown')
|
||||||
|
})
|
||||||
|
.fail(function () {
|
||||||
|
finish('unknown')
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
finish('unknown')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function keepLegacyVisibleMemos(list) {
|
||||||
|
const items = Array.isArray(list) ? list : []
|
||||||
|
return items.filter(function (memo) {
|
||||||
|
if (!memo) return false
|
||||||
|
const visibility = typeof memo.visibility === 'string' ? memo.visibility.toUpperCase() : ''
|
||||||
|
if (!visibility) return true
|
||||||
|
return visibility === 'PUBLIC' || visibility === 'PROTECTED'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractTagsFromGenericMemo(memo) {
|
||||||
|
if (!memo) return []
|
||||||
|
if (Array.isArray(memo.tags) && memo.tags.length > 0) return memo.tags
|
||||||
|
if (Array.isArray(memo.tagList) && memo.tagList.length > 0) return memo.tagList
|
||||||
|
if (memo.property && Array.isArray(memo.property.tags) && memo.property.tags.length > 0) {
|
||||||
|
return memo.property.tags
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectTags(info, memos) {
|
||||||
|
const items = Array.isArray(memos) ? memos : []
|
||||||
|
const out = items.flatMap(function (memo) {
|
||||||
|
if (!memo) return []
|
||||||
|
if (getFlavor(info) === 'v023' && global.MemosApiV023 && typeof global.MemosApiV023.extractTagsFromMemo === 'function') {
|
||||||
|
return global.MemosApiV023.extractTagsFromMemo(memo)
|
||||||
|
}
|
||||||
|
return extractTagsFromGenericMemo(memo)
|
||||||
|
})
|
||||||
|
return [...new Set(out.filter(Boolean))]
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildUploadVisibility(editorContent, hideTag, showTag, memoLock) {
|
||||||
|
const content = typeof editorContent === 'string' ? editorContent : ''
|
||||||
|
const nowTag = content.match(/(#[^\s#]+)/)
|
||||||
|
let visibility = memoLock || ''
|
||||||
|
if (nowTag) {
|
||||||
|
if (nowTag[1] === showTag) visibility = 'PUBLIC'
|
||||||
|
else if (nowTag[1] === hideTag) visibility = 'PRIVATE'
|
||||||
|
}
|
||||||
|
return visibility
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildModernFilter(parts) {
|
||||||
|
const p = parts || {}
|
||||||
|
const exprs = []
|
||||||
|
|
||||||
|
if (typeof p.contentSearch === 'string' && p.contentSearch.length > 0) {
|
||||||
|
exprs.push('content.contains(' + JSON.stringify(String(p.contentSearch)) + ')')
|
||||||
|
}
|
||||||
|
|
||||||
|
return exprs.join(' && ')
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeUploadedItem(entity, fallbackFilename) {
|
||||||
|
if (!entity) return null
|
||||||
|
const inferredId = (function () {
|
||||||
|
const value = entity.id != null ? entity.id : entity.ID != null ? entity.ID : entity.Id
|
||||||
|
if (typeof value === 'number' && Number.isFinite(value)) return Math.floor(value)
|
||||||
|
if (typeof value === 'string' && value.trim() !== '' && !Number.isNaN(Number(value))) {
|
||||||
|
return Math.floor(Number(value))
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
})()
|
||||||
|
|
||||||
|
const name = entity.name || (inferredId != null ? 'resources/' + String(inferredId) : '')
|
||||||
|
if (!name && inferredId == null) return null
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: inferredId != null ? inferredId : entity.id,
|
||||||
|
name: name,
|
||||||
|
filename: entity.filename || fallbackFilename || name,
|
||||||
|
createTime: entity.createTime || entity.createdTs || entity.createdAt,
|
||||||
|
type: entity.type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function unwrapLegacyMemoEntity(data) {
|
||||||
|
if (!data) return data
|
||||||
|
if (data.memo) return data.memo
|
||||||
|
if (data.data && data.data.memo) return data.data.memo
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeLegacyResourceIdList(list) {
|
||||||
|
const items = Array.isArray(list) ? list : []
|
||||||
|
return items
|
||||||
|
.map(function (item) {
|
||||||
|
if (!item) return null
|
||||||
|
if (typeof item.id === 'number' && Number.isFinite(item.id)) return Math.floor(item.id)
|
||||||
|
if (typeof item.id === 'string' && item.id.trim() !== '' && !Number.isNaN(Number(item.id))) {
|
||||||
|
return Math.floor(Number(item.id))
|
||||||
|
}
|
||||||
|
const name = typeof item.name === 'string' ? item.name : ''
|
||||||
|
const tail = name ? name.split('/').pop() : ''
|
||||||
|
if (tail && !Number.isNaN(Number(tail))) return Math.floor(Number(tail))
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
.filter(function (value) {
|
||||||
|
return value != null && Number.isFinite(value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolve(info) {
|
||||||
|
const flavor = getFlavor(info)
|
||||||
|
|
||||||
|
function listTags(success, fail) {
|
||||||
|
if (flavor === FLAVOR_V020_V021 && global.MemosApiV020V021) {
|
||||||
|
global.MemosApiV020V021.getTagSuggestion(info, success, fail)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flavor === 'v023' && global.MemosApiV023) {
|
||||||
|
const filterExpr = global.MemosApiV023.buildFilter({
|
||||||
|
rowStatus: 'NORMAL',
|
||||||
|
creator: 'users/' + info.userid
|
||||||
|
})
|
||||||
|
global.MemosApiV023.listMemos(
|
||||||
|
info,
|
||||||
|
{ pageSize: 1000, filterExpr: filterExpr },
|
||||||
|
function (data) {
|
||||||
|
if (success) success(collectTags(info, extractMemos(data)))
|
||||||
|
},
|
||||||
|
fail
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (global.MemosApiModern) {
|
||||||
|
global.MemosApiModern.fetchMemosWithFallback(
|
||||||
|
info,
|
||||||
|
'?pageSize=1000',
|
||||||
|
function (data) {
|
||||||
|
if (success) success(collectTags(info, extractMemos(data)))
|
||||||
|
},
|
||||||
|
fail
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchMemos(pattern, success, fail) {
|
||||||
|
const text = String(pattern || '')
|
||||||
|
const patternLiteral = JSON.stringify(text)
|
||||||
|
const legacyFilter = '?filter=' + encodeURIComponent('visibility in ["PUBLIC","PROTECTED"] && content.contains(' + patternLiteral + ')')
|
||||||
|
|
||||||
|
if (flavor === 'modern' && global.MemosApiV023) {
|
||||||
|
const filterExpr = buildModernFilter({ contentSearch: text })
|
||||||
|
global.MemosApiV023.listMemos(info, { pageSize: 1000, filterExpr: filterExpr }, function (data) {
|
||||||
|
if (success) success(extractMemos(data))
|
||||||
|
}, fail)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flavor === 'v023' && global.MemosApiV023) {
|
||||||
|
const filterExpr = global.MemosApiV023.buildFilter({
|
||||||
|
visibilities: ['PUBLIC', 'PROTECTED'],
|
||||||
|
contentSearch: text
|
||||||
|
})
|
||||||
|
global.MemosApiV023.listMemos(info, { pageSize: 1000, filterExpr: filterExpr }, function (data) {
|
||||||
|
if (success) success(keepLegacyVisibleMemos(extractMemos(data)))
|
||||||
|
}, fail)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flavor === FLAVOR_V020_V021 && global.MemosApiV020V021) {
|
||||||
|
global.MemosApiV020V021.listMemos(info, { limit: 1000, rowStatus: 'NORMAL', contentSearch: text }, function (data) {
|
||||||
|
if (success) success(keepLegacyVisibleMemos(extractMemos(data)))
|
||||||
|
}, fail)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (global.MemosApiModern) {
|
||||||
|
global.MemosApiModern.fetchMemosWithFallback(info, legacyFilter, function (data) {
|
||||||
|
if (success) success(keepLegacyVisibleMemos(extractMemos(data)))
|
||||||
|
}, fail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function listRandomMemos(success, fail) {
|
||||||
|
if (flavor === 'modern' && global.MemosApiV023) {
|
||||||
|
const filterExpr = global.MemosApiV023.buildFilter({})
|
||||||
|
global.MemosApiV023.listMemos(info, { pageSize: 1000, filterExpr: filterExpr }, function (data) {
|
||||||
|
if (success) success(extractMemos(data))
|
||||||
|
}, fail)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flavor === 'v023' && global.MemosApiV023) {
|
||||||
|
const filterExpr = global.MemosApiV023.buildFilter({ visibilities: ['PUBLIC', 'PROTECTED'] })
|
||||||
|
global.MemosApiV023.listMemos(info, { pageSize: 1000, filterExpr: filterExpr }, function (data) {
|
||||||
|
if (success) success(keepLegacyVisibleMemos(extractMemos(data)))
|
||||||
|
}, fail)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flavor === FLAVOR_V020_V021 && global.MemosApiV020V021) {
|
||||||
|
global.MemosApiV020V021.listMemos(info, { limit: 1000, rowStatus: 'NORMAL' }, function (data) {
|
||||||
|
if (success) success(keepLegacyVisibleMemos(extractMemos(data)))
|
||||||
|
}, fail)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (global.MemosApiModern) {
|
||||||
|
const legacyFilter = '?filter=' + encodeURIComponent('visibility in ["PUBLIC","PROTECTED"]')
|
||||||
|
global.MemosApiModern.fetchMemosWithFallback(info, legacyFilter, function (data) {
|
||||||
|
if (success) success(keepLegacyVisibleMemos(extractMemos(data)))
|
||||||
|
}, fail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteResource(item, success, fail) {
|
||||||
|
const name = item && item.name ? item.name : ''
|
||||||
|
const rid = item && item.id != null ? item.id : ''
|
||||||
|
const inferredId = (function () {
|
||||||
|
if (rid != null && String(rid).trim() !== '' && !Number.isNaN(Number(rid))) return Math.floor(Number(rid))
|
||||||
|
const tail = String(name || '').split('/').pop()
|
||||||
|
if (tail && !Number.isNaN(Number(tail))) return Math.floor(Number(tail))
|
||||||
|
return null
|
||||||
|
})()
|
||||||
|
|
||||||
|
if (flavor === FLAVOR_V020_V021 && global.MemosApiV020V021 && typeof global.MemosApiV020V021.deleteResource === 'function' && inferredId != null) {
|
||||||
|
global.MemosApiV020V021.deleteResource(info, inferredId, success, fail)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
requestJson({
|
||||||
|
url: info.apiUrl + 'api/v1/' + name,
|
||||||
|
type: 'DELETE',
|
||||||
|
headers: { Authorization: 'Bearer ' + info.apiTokens }
|
||||||
|
}, success, fail)
|
||||||
|
}
|
||||||
|
|
||||||
|
function uploadFile(file, options, success, fail) {
|
||||||
|
const oldName = String(file && file.name ? file.name : 'upload').split('.')
|
||||||
|
const fileExt = String(file && file.name ? file.name : '').split('.').pop()
|
||||||
|
const now = global.dayjs().format('YYYYMMDDHHmmss')
|
||||||
|
const nextName = oldName[0] + '_' + now + (fileExt ? '.' + fileExt : '')
|
||||||
|
|
||||||
|
if (flavor === FLAVOR_V020_V021 && global.MemosApiV020V021) {
|
||||||
|
global.MemosApiV020V021.uploadResourceBlob(
|
||||||
|
info,
|
||||||
|
file,
|
||||||
|
{ filename: nextName, type: file.type },
|
||||||
|
function (entity) {
|
||||||
|
if (success) success(normalizeUploadedItem(entity, nextName))
|
||||||
|
},
|
||||||
|
fail
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const reader = new FileReader()
|
||||||
|
reader.onload = function (e) {
|
||||||
|
const base64String = e && e.target && e.target.result ? String(e.target.result).split(',')[1] : ''
|
||||||
|
const payload = {
|
||||||
|
content: base64String,
|
||||||
|
visibility: buildUploadVisibility(options && options.editorContent, options && options.hideTag, options && options.showTag, options && options.memoLock),
|
||||||
|
filename: nextName,
|
||||||
|
type: file.type
|
||||||
|
}
|
||||||
|
|
||||||
|
global.MemosApiModern.uploadAttachmentOrResource(
|
||||||
|
info,
|
||||||
|
payload,
|
||||||
|
function (resp) {
|
||||||
|
const entity = (resp && resp.resource) || resp
|
||||||
|
if (success) success(normalizeUploadedItem(entity, nextName))
|
||||||
|
},
|
||||||
|
fail
|
||||||
|
)
|
||||||
|
}
|
||||||
|
reader.onerror = fail
|
||||||
|
reader.readAsDataURL(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
function archiveMemo(memo, success, fail) {
|
||||||
|
const memoId = memo && memo.id != null ? memo.id : ''
|
||||||
|
const memoName = memo && memo.name ? memo.name : ''
|
||||||
|
|
||||||
|
if (flavor === FLAVOR_V020_V021 && global.MemosApiV020V021 && memoId !== '') {
|
||||||
|
global.MemosApiV020V021.patchMemo(info, memoId, { rowStatus: 'ARCHIVED' }, success, fail)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
requestJson({
|
||||||
|
url: info.apiUrl + 'api/v1/' + memoName,
|
||||||
|
type: 'PATCH',
|
||||||
|
data: JSON.stringify({ state: 'ARCHIVED' }),
|
||||||
|
contentType: 'application/json',
|
||||||
|
dataType: 'json',
|
||||||
|
headers: { Authorization: 'Bearer ' + info.apiTokens }
|
||||||
|
}, success, fail)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMemo(memoRef, success, fail) {
|
||||||
|
const url = flavor === FLAVOR_V020_V021
|
||||||
|
? info.apiUrl + 'api/v1/memo/' + memoRef
|
||||||
|
: info.apiUrl + 'api/v1/' + memoRef
|
||||||
|
|
||||||
|
requestJson({
|
||||||
|
url: url,
|
||||||
|
type: 'GET',
|
||||||
|
contentType: 'application/json',
|
||||||
|
dataType: 'json',
|
||||||
|
headers: { Authorization: 'Bearer ' + info.apiTokens }
|
||||||
|
}, function (data) {
|
||||||
|
if (success) success(flavor === FLAVOR_V020_V021 ? unwrapLegacyMemoEntity(data) : data)
|
||||||
|
}, fail)
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMemo(params, success, fail) {
|
||||||
|
const payload = params || {}
|
||||||
|
|
||||||
|
if (flavor === FLAVOR_V020_V021 && global.MemosApiV020V021) {
|
||||||
|
global.MemosApiV020V021.createMemo(
|
||||||
|
info,
|
||||||
|
{
|
||||||
|
content: payload.content,
|
||||||
|
visibility: payload.visibility,
|
||||||
|
resourceIdList: normalizeLegacyResourceIdList(payload.resourceIdList)
|
||||||
|
},
|
||||||
|
success,
|
||||||
|
fail
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
requestJson({
|
||||||
|
url: info.apiUrl + 'api/v1/memos',
|
||||||
|
type: 'POST',
|
||||||
|
data: JSON.stringify({
|
||||||
|
content: payload.content,
|
||||||
|
visibility: payload.visibility
|
||||||
|
}),
|
||||||
|
contentType: 'application/json',
|
||||||
|
dataType: 'json',
|
||||||
|
headers: { Authorization: 'Bearer ' + info.apiTokens }
|
||||||
|
}, function (data) {
|
||||||
|
const createdName = data && data.name ? data.name : data && data.memo && data.memo.name ? data.memo.name : ''
|
||||||
|
const resources = Array.isArray(payload.resourceIdList) ? payload.resourceIdList : []
|
||||||
|
if (!createdName) {
|
||||||
|
if (success) success(data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (resources.length === 0) {
|
||||||
|
getMemo(createdName, success, fail)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
global.MemosApiModern.patchMemoWithAttachmentsOrResources(
|
||||||
|
info,
|
||||||
|
createdName,
|
||||||
|
resources,
|
||||||
|
function () {
|
||||||
|
getMemo(createdName, success, fail)
|
||||||
|
},
|
||||||
|
function () {
|
||||||
|
getMemo(createdName, success, fail)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}, fail)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
flavor: flavor,
|
||||||
|
needsAuthenticatedImagePreview: function () {
|
||||||
|
return flavor === FLAVOR_V020_V021
|
||||||
|
},
|
||||||
|
listTags: listTags,
|
||||||
|
searchMemos: searchMemos,
|
||||||
|
listRandomMemos: listRandomMemos,
|
||||||
|
deleteResource: deleteResource,
|
||||||
|
uploadFile: uploadFile,
|
||||||
|
archiveMemo: archiveMemo,
|
||||||
|
getMemo: getMemo,
|
||||||
|
createMemo: createMemo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
global.MemosApiAdapter = {
|
||||||
|
FLAVOR_V020_V021: FLAVOR_V020_V021,
|
||||||
|
KNOWN_FLAVORS: KNOWN_FLAVORS.slice(),
|
||||||
|
getFlavor: getFlavor,
|
||||||
|
normalizeDetectedFlavor: normalizeDetectedFlavor,
|
||||||
|
probeFlavor: probeFlavor,
|
||||||
|
resolve: resolve
|
||||||
|
}
|
||||||
|
})(window)
|
||||||
@@ -0,0 +1,512 @@
|
|||||||
|
(function (global) {
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
function extractUserIdFromAuthResponse(response) {
|
||||||
|
if (!response) return null
|
||||||
|
|
||||||
|
const user = response.user || response
|
||||||
|
|
||||||
|
if (typeof user.id === 'number' && Number.isFinite(user.id)) return user.id
|
||||||
|
if (typeof user.id === 'string' && user.id.trim() !== '' && !Number.isNaN(Number(user.id))) {
|
||||||
|
return Number(user.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof user.username === 'string' && user.username.trim() !== '') {
|
||||||
|
return user.username.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = user.name || (user.user && user.user.name)
|
||||||
|
if (typeof name === 'string') {
|
||||||
|
const m = name.match(/\busers\/(\d+)\b/)
|
||||||
|
if (m) return Number(m[1])
|
||||||
|
const last = name.split('/').pop()
|
||||||
|
if (last) {
|
||||||
|
if (!Number.isNaN(Number(last))) return Number(last)
|
||||||
|
if (last.trim() !== '') return last.trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractMemosListFromResponse(data) {
|
||||||
|
if (!data) return []
|
||||||
|
if (Array.isArray(data)) return data
|
||||||
|
if (Array.isArray(data.memos)) return data.memos
|
||||||
|
if (data.data && Array.isArray(data.data.memos)) return data.data.memos
|
||||||
|
if (Array.isArray(data.list)) return data.list
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
function isNotFoundLikeXhr(jqXhr) {
|
||||||
|
const status = jqXhr && jqXhr.status
|
||||||
|
return status === 404 || status === 405
|
||||||
|
}
|
||||||
|
|
||||||
|
function authWithFallback(apiUrl, apiTokens, callback) {
|
||||||
|
const headers = { Authorization: 'Bearer ' + apiTokens }
|
||||||
|
|
||||||
|
// v0.26+: GET auth/me
|
||||||
|
// older: POST/GET auth/status
|
||||||
|
const tries = [
|
||||||
|
{ method: 'GET', path: 'api/v1/auth/me', uiPath: 'memos' },
|
||||||
|
// v0.25: session-based auth service still accepts bearer tokens and returns { user: ... }.
|
||||||
|
{ method: 'GET', path: 'api/v1/auth/sessions/current', uiPath: 'memos' },
|
||||||
|
// v0.20: current user endpoint.
|
||||||
|
{ method: 'GET', path: 'api/v1/user/me', uiPath: 'm' },
|
||||||
|
{ method: 'POST', path: 'api/v1/auth/status', uiPath: 'm' },
|
||||||
|
{ method: 'GET', path: 'api/v1/auth/status', uiPath: 'm' }
|
||||||
|
]
|
||||||
|
|
||||||
|
function runAt(index) {
|
||||||
|
if (index >= tries.length) {
|
||||||
|
callback(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const t = tries[index]
|
||||||
|
global.$
|
||||||
|
.ajax({
|
||||||
|
async: true,
|
||||||
|
crossDomain: true,
|
||||||
|
url: apiUrl + t.path,
|
||||||
|
method: t.method,
|
||||||
|
headers: headers
|
||||||
|
})
|
||||||
|
.done(function (response) {
|
||||||
|
const userId = extractUserIdFromAuthResponse(response)
|
||||||
|
if (userId != null) callback({ userId: userId, uiPath: t.uiPath, raw: response })
|
||||||
|
else runAt(index + 1)
|
||||||
|
})
|
||||||
|
.fail(function () {
|
||||||
|
runAt(index + 1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
runAt(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchMemosWithFallback(info, query, success, fail) {
|
||||||
|
const qs = query || ''
|
||||||
|
const headers = { Authorization: 'Bearer ' + info.apiTokens }
|
||||||
|
|
||||||
|
// v0.24: `GET /api/v1/memos` tends to behave like a public feed (private memos excluded).
|
||||||
|
// For an authenticated user, `GET /api/v1/users/{id}/memos` is the safe way to retrieve
|
||||||
|
// the full set (including private), which affects tag extraction.
|
||||||
|
// Newer versions may not expose the user-scoped endpoint, so we fallback by 404/405.
|
||||||
|
const urlUserScoped = info.userid
|
||||||
|
? info.apiUrl + 'api/v1/users/' + encodeURIComponent(String(info.userid)) + '/memos' + qs
|
||||||
|
: null
|
||||||
|
const urlGlobal = info.apiUrl + 'api/v1/memos' + qs
|
||||||
|
|
||||||
|
const urlPrimary = urlUserScoped || urlGlobal
|
||||||
|
const urlFallback = urlUserScoped ? urlGlobal : null
|
||||||
|
|
||||||
|
global.$
|
||||||
|
.ajax({
|
||||||
|
url: urlPrimary,
|
||||||
|
type: 'GET',
|
||||||
|
contentType: 'application/json',
|
||||||
|
dataType: 'json',
|
||||||
|
headers: headers
|
||||||
|
})
|
||||||
|
.done(function (data) {
|
||||||
|
success(data)
|
||||||
|
})
|
||||||
|
.fail(function (xhr) {
|
||||||
|
const status = xhr && xhr.status
|
||||||
|
const canFallback = Boolean(urlFallback) && (isNotFoundLikeXhr(xhr) || status === 400)
|
||||||
|
if (!canFallback) {
|
||||||
|
if (fail) fail(xhr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
global.$
|
||||||
|
.ajax({
|
||||||
|
url: urlFallback,
|
||||||
|
type: 'GET',
|
||||||
|
contentType: 'application/json',
|
||||||
|
dataType: 'json',
|
||||||
|
headers: headers
|
||||||
|
})
|
||||||
|
.done(function (data) {
|
||||||
|
success(data)
|
||||||
|
})
|
||||||
|
.fail(function (xhr2) {
|
||||||
|
if (fail) fail(xhr2)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function uploadAttachmentOrResource(info, payload, onSuccess, onFail) {
|
||||||
|
const headers = { Authorization: 'Bearer ' + info.apiTokens }
|
||||||
|
const urlAttachments = info.apiUrl + 'api/v1/attachments'
|
||||||
|
const urlResources = info.apiUrl + 'api/v1/resources'
|
||||||
|
|
||||||
|
function stripVisibility(p) {
|
||||||
|
if (!p || typeof p !== 'object') return p
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(p, 'visibility')) return p
|
||||||
|
const copy = Object.assign({}, p)
|
||||||
|
delete copy.visibility
|
||||||
|
return copy
|
||||||
|
}
|
||||||
|
|
||||||
|
global.$
|
||||||
|
.ajax({
|
||||||
|
url: urlAttachments,
|
||||||
|
data: JSON.stringify(payload),
|
||||||
|
type: 'POST',
|
||||||
|
cache: false,
|
||||||
|
processData: false,
|
||||||
|
contentType: 'application/json',
|
||||||
|
dataType: 'json',
|
||||||
|
headers: headers
|
||||||
|
})
|
||||||
|
.done(function (data) {
|
||||||
|
onSuccess(data, 'attachments')
|
||||||
|
})
|
||||||
|
.fail(function (xhr) {
|
||||||
|
if (xhr && xhr.status === 400) {
|
||||||
|
global.$
|
||||||
|
.ajax({
|
||||||
|
url: urlAttachments,
|
||||||
|
data: JSON.stringify(stripVisibility(payload)),
|
||||||
|
type: 'POST',
|
||||||
|
cache: false,
|
||||||
|
processData: false,
|
||||||
|
contentType: 'application/json',
|
||||||
|
dataType: 'json',
|
||||||
|
headers: headers
|
||||||
|
})
|
||||||
|
.done(function (data) {
|
||||||
|
onSuccess(data, 'attachments')
|
||||||
|
})
|
||||||
|
.fail(function (xhrRetry) {
|
||||||
|
if (!isNotFoundLikeXhr(xhrRetry)) {
|
||||||
|
if (onFail) onFail(xhrRetry)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// fall through to resources below
|
||||||
|
xhr = xhrRetry
|
||||||
|
if (!isNotFoundLikeXhr(xhr)) {
|
||||||
|
if (onFail) onFail(xhr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
global.$
|
||||||
|
.ajax({
|
||||||
|
url: urlResources,
|
||||||
|
data: JSON.stringify(payload),
|
||||||
|
type: 'POST',
|
||||||
|
cache: false,
|
||||||
|
processData: false,
|
||||||
|
contentType: 'application/json',
|
||||||
|
dataType: 'json',
|
||||||
|
headers: headers
|
||||||
|
})
|
||||||
|
.done(function (data) {
|
||||||
|
onSuccess(data, 'resources')
|
||||||
|
})
|
||||||
|
.fail(function (xhr2) {
|
||||||
|
if (xhr2 && xhr2.status === 400) {
|
||||||
|
global.$
|
||||||
|
.ajax({
|
||||||
|
url: urlResources,
|
||||||
|
data: JSON.stringify(stripVisibility(payload)),
|
||||||
|
type: 'POST',
|
||||||
|
cache: false,
|
||||||
|
processData: false,
|
||||||
|
contentType: 'application/json',
|
||||||
|
dataType: 'json',
|
||||||
|
headers: headers
|
||||||
|
})
|
||||||
|
.done(function (data) {
|
||||||
|
onSuccess(data, 'resources')
|
||||||
|
})
|
||||||
|
.fail(function (xhr3) {
|
||||||
|
if (onFail) onFail(xhr3)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (onFail) onFail(xhr2)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isNotFoundLikeXhr(xhr)) {
|
||||||
|
if (onFail) onFail(xhr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
global.$
|
||||||
|
.ajax({
|
||||||
|
url: urlResources,
|
||||||
|
data: JSON.stringify(payload),
|
||||||
|
type: 'POST',
|
||||||
|
cache: false,
|
||||||
|
processData: false,
|
||||||
|
contentType: 'application/json',
|
||||||
|
dataType: 'json',
|
||||||
|
headers: headers
|
||||||
|
})
|
||||||
|
.done(function (data) {
|
||||||
|
onSuccess(data, 'resources')
|
||||||
|
})
|
||||||
|
.fail(function (xhr2) {
|
||||||
|
if (xhr2 && xhr2.status === 400) {
|
||||||
|
global.$
|
||||||
|
.ajax({
|
||||||
|
url: urlResources,
|
||||||
|
data: JSON.stringify(stripVisibility(payload)),
|
||||||
|
type: 'POST',
|
||||||
|
cache: false,
|
||||||
|
processData: false,
|
||||||
|
contentType: 'application/json',
|
||||||
|
dataType: 'json',
|
||||||
|
headers: headers
|
||||||
|
})
|
||||||
|
.done(function (data) {
|
||||||
|
onSuccess(data, 'resources')
|
||||||
|
})
|
||||||
|
.fail(function (xhr3) {
|
||||||
|
if (onFail) onFail(xhr3)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (onFail) onFail(xhr2)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function patchMemoWithAttachmentsOrResources(info, memoName, list, onSuccess, onFail) {
|
||||||
|
const headers = { Authorization: 'Bearer ' + info.apiTokens }
|
||||||
|
const url = info.apiUrl + 'api/v1/' + memoName
|
||||||
|
const items = Array.isArray(list) ? list : []
|
||||||
|
|
||||||
|
const hasResourceNames = items.some(function (x) {
|
||||||
|
return x && typeof x.name === 'string' && x.name.indexOf('resources/') === 0
|
||||||
|
})
|
||||||
|
const hasAttachmentNames = items.some(function (x) {
|
||||||
|
return x && typeof x.name === 'string' && x.name.indexOf('attachments/') === 0
|
||||||
|
})
|
||||||
|
|
||||||
|
function doPatchAttachments() {
|
||||||
|
const attachments = items
|
||||||
|
.map(function (x) {
|
||||||
|
if (!x) return null
|
||||||
|
const n = x.name
|
||||||
|
if (!n) return null
|
||||||
|
if (hasAttachmentNames && typeof n === 'string' && n.indexOf('attachments/') !== 0) return null
|
||||||
|
return { name: n }
|
||||||
|
})
|
||||||
|
.filter(Boolean)
|
||||||
|
|
||||||
|
// Prefer the dedicated subresource endpoint when available.
|
||||||
|
global.$
|
||||||
|
.ajax({
|
||||||
|
url: url + '/attachments',
|
||||||
|
type: 'PATCH',
|
||||||
|
data: JSON.stringify({ name: memoName, attachments: attachments }),
|
||||||
|
contentType: 'application/json',
|
||||||
|
dataType: 'json',
|
||||||
|
headers: headers
|
||||||
|
})
|
||||||
|
.done(function (data) {
|
||||||
|
onSuccess(data, 'attachments')
|
||||||
|
})
|
||||||
|
.fail(function (xhr0) {
|
||||||
|
// If the endpoint doesn't exist, try UpdateMemo-style patching.
|
||||||
|
if (isNotFoundLikeXhr(xhr0)) {
|
||||||
|
// continue
|
||||||
|
} else if (xhr0 && xhr0.status && xhr0.status !== 400) {
|
||||||
|
// continue; some gateways may reject body shape here.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some versions accept a loose patch, others require updateMask.
|
||||||
|
const attachmentsPayloadLoose = {
|
||||||
|
name: memoName,
|
||||||
|
attachments: attachments
|
||||||
|
}
|
||||||
|
|
||||||
|
global.$
|
||||||
|
.ajax({
|
||||||
|
url: url,
|
||||||
|
type: 'PATCH',
|
||||||
|
data: JSON.stringify(attachmentsPayloadLoose),
|
||||||
|
contentType: 'application/json',
|
||||||
|
dataType: 'json',
|
||||||
|
headers: headers
|
||||||
|
})
|
||||||
|
.done(function (data) {
|
||||||
|
onSuccess(data, 'attachments')
|
||||||
|
})
|
||||||
|
.fail(function (xhr) {
|
||||||
|
// v0.25 requires update mask when updating attachments.
|
||||||
|
if (!isNotFoundLikeXhr(xhr) && xhr && xhr.status !== 400) {
|
||||||
|
if (onFail) onFail(xhr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the server doesn't support attachments at all, fallback to resources flow.
|
||||||
|
if (isNotFoundLikeXhr(xhr)) {
|
||||||
|
doPatchResources()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const attachmentsPayloadV025 = {
|
||||||
|
name: memoName,
|
||||||
|
attachments: attachments
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateUrl1 = url + (url.indexOf('?') >= 0 ? '&' : '?') + 'updateMask=attachments'
|
||||||
|
global.$
|
||||||
|
.ajax({
|
||||||
|
url: updateUrl1,
|
||||||
|
type: 'PATCH',
|
||||||
|
data: JSON.stringify(attachmentsPayloadV025),
|
||||||
|
contentType: 'application/json',
|
||||||
|
dataType: 'json',
|
||||||
|
headers: headers
|
||||||
|
})
|
||||||
|
.done(function (data) {
|
||||||
|
onSuccess(data, 'attachments')
|
||||||
|
})
|
||||||
|
.fail(function (xhr2) {
|
||||||
|
if (isNotFoundLikeXhr(xhr2)) {
|
||||||
|
doPatchResources()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Some grpc-gateway setups prefer updateMask.paths.
|
||||||
|
if (xhr2 && xhr2.status === 400) {
|
||||||
|
const updateUrl2 =
|
||||||
|
url + (url.indexOf('?') >= 0 ? '&' : '?') + 'updateMask.paths=attachments'
|
||||||
|
global.$
|
||||||
|
.ajax({
|
||||||
|
url: updateUrl2,
|
||||||
|
type: 'PATCH',
|
||||||
|
data: JSON.stringify(attachmentsPayloadV025),
|
||||||
|
contentType: 'application/json',
|
||||||
|
dataType: 'json',
|
||||||
|
headers: headers
|
||||||
|
})
|
||||||
|
.done(function (data) {
|
||||||
|
onSuccess(data, 'attachments')
|
||||||
|
})
|
||||||
|
.fail(function (xhr3) {
|
||||||
|
if (isNotFoundLikeXhr(xhr3)) {
|
||||||
|
doPatchResources()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (onFail) onFail(xhr3)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (onFail) onFail(xhr2)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function doPatchResources() {
|
||||||
|
const resources = items
|
||||||
|
.map(function (x) {
|
||||||
|
if (!x) return null
|
||||||
|
const n = x.name
|
||||||
|
if (!n) return null
|
||||||
|
if (hasResourceNames && typeof n === 'string' && n.indexOf('resources/') !== 0) return null
|
||||||
|
return { name: n }
|
||||||
|
})
|
||||||
|
.filter(Boolean)
|
||||||
|
|
||||||
|
// Prefer the dedicated subresource endpoint when available.
|
||||||
|
global.$
|
||||||
|
.ajax({
|
||||||
|
url: url + '/resources',
|
||||||
|
type: 'PATCH',
|
||||||
|
data: JSON.stringify({ name: memoName, resources: resources }),
|
||||||
|
contentType: 'application/json',
|
||||||
|
dataType: 'json',
|
||||||
|
headers: headers
|
||||||
|
})
|
||||||
|
.done(function (data) {
|
||||||
|
onSuccess(data, 'resources')
|
||||||
|
})
|
||||||
|
.fail(function (xhr0) {
|
||||||
|
if (!isNotFoundLikeXhr(xhr0) && xhr0 && xhr0.status && xhr0.status !== 400) {
|
||||||
|
// continue; try UpdateMemo flow below.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try a loose PATCH first (some versions accept this).
|
||||||
|
const resourcesPayloadLoose = { resources: resources }
|
||||||
|
|
||||||
|
global.$
|
||||||
|
.ajax({
|
||||||
|
url: url,
|
||||||
|
type: 'PATCH',
|
||||||
|
data: JSON.stringify(resourcesPayloadLoose),
|
||||||
|
contentType: 'application/json',
|
||||||
|
dataType: 'json',
|
||||||
|
headers: headers
|
||||||
|
})
|
||||||
|
.done(function (data) {
|
||||||
|
onSuccess(data, 'resources')
|
||||||
|
})
|
||||||
|
.fail(function (xhr2) {
|
||||||
|
// v0.24 expects UpdateMemo with an update mask when modifying resources.
|
||||||
|
// The gateway commonly accepts `updateMask=resources` as a query param and a
|
||||||
|
// Memo body containing `name` + `resources`.
|
||||||
|
if (!isNotFoundLikeXhr(xhr2) && xhr2 && xhr2.status !== 400) {
|
||||||
|
if (onFail) onFail(xhr2)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateUrl = url + (url.indexOf('?') >= 0 ? '&' : '?') + 'updateMask=resources'
|
||||||
|
const resourcesPayloadV024 = {
|
||||||
|
name: memoName,
|
||||||
|
resources: resources
|
||||||
|
}
|
||||||
|
|
||||||
|
global.$
|
||||||
|
.ajax({
|
||||||
|
url: updateUrl,
|
||||||
|
type: 'PATCH',
|
||||||
|
data: JSON.stringify(resourcesPayloadV024),
|
||||||
|
contentType: 'application/json',
|
||||||
|
dataType: 'json',
|
||||||
|
headers: headers
|
||||||
|
})
|
||||||
|
.done(function (data) {
|
||||||
|
onSuccess(data, 'resources')
|
||||||
|
})
|
||||||
|
.fail(function (xhr3) {
|
||||||
|
if (onFail) onFail(xhr3)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the list clearly contains v0.24-style resource names, go directly to the
|
||||||
|
// resource linking flow. If it contains attachment names, go attachment flow.
|
||||||
|
if (hasResourceNames && !hasAttachmentNames) {
|
||||||
|
doPatchResources()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (hasAttachmentNames && !hasResourceNames) {
|
||||||
|
doPatchAttachments()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default to attachments first, then fallback to resources.
|
||||||
|
doPatchAttachments()
|
||||||
|
}
|
||||||
|
|
||||||
|
global.MemosApiModern = {
|
||||||
|
extractUserIdFromAuthResponse: extractUserIdFromAuthResponse,
|
||||||
|
extractMemosListFromResponse: extractMemosListFromResponse,
|
||||||
|
isNotFoundLikeXhr: isNotFoundLikeXhr,
|
||||||
|
authWithFallback: authWithFallback,
|
||||||
|
fetchMemosWithFallback: fetchMemosWithFallback,
|
||||||
|
uploadAttachmentOrResource: uploadAttachmentOrResource,
|
||||||
|
patchMemoWithAttachmentsOrResources: patchMemoWithAttachmentsOrResources
|
||||||
|
}
|
||||||
|
})(window)
|
||||||
@@ -0,0 +1,286 @@
|
|||||||
|
(function (global) {
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
function isNotFoundLikeXhr(jqXhr) {
|
||||||
|
const status = jqXhr && jqXhr.status
|
||||||
|
return status === 404 || status === 405
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractMemoListFromResponse(data) {
|
||||||
|
if (!data) return []
|
||||||
|
if (Array.isArray(data)) return data
|
||||||
|
if (Array.isArray(data.memos)) return data.memos
|
||||||
|
if (data.data && Array.isArray(data.data.memos)) return data.data.memos
|
||||||
|
if (Array.isArray(data.list)) return data.list
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractMemoEntityFromResponse(data) {
|
||||||
|
if (!data) return data
|
||||||
|
if (data.memo) return data.memo
|
||||||
|
if (data.data && data.data.memo) return data.data.memo
|
||||||
|
if (data.data && (data.data.id != null || data.data.name || data.data.content)) return data.data
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractResourceEntityFromResponse(data) {
|
||||||
|
if (!data) return data
|
||||||
|
if (data.resource) return data.resource
|
||||||
|
if (data.data && data.data.resource) return data.data.resource
|
||||||
|
if (data.data && (data.data.id != null || data.data.name || data.data.filename)) return data.data
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestGet(url, headers, success, fail) {
|
||||||
|
global.$
|
||||||
|
.ajax({
|
||||||
|
url: url,
|
||||||
|
type: 'GET',
|
||||||
|
contentType: 'application/json',
|
||||||
|
dataType: 'json',
|
||||||
|
headers: headers
|
||||||
|
})
|
||||||
|
.done(function (data) {
|
||||||
|
if (success) success(data)
|
||||||
|
})
|
||||||
|
.fail(function (xhr) {
|
||||||
|
if (fail) fail(xhr)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestPostJson(url, headers, body, success, fail) {
|
||||||
|
global.$
|
||||||
|
.ajax({
|
||||||
|
url: url,
|
||||||
|
type: 'POST',
|
||||||
|
contentType: 'application/json',
|
||||||
|
dataType: 'json',
|
||||||
|
data: body != null ? JSON.stringify(body) : null,
|
||||||
|
headers: headers
|
||||||
|
})
|
||||||
|
.done(function (data) {
|
||||||
|
if (success) success(data)
|
||||||
|
})
|
||||||
|
.fail(function (xhr) {
|
||||||
|
if (fail) fail(xhr)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestPatchJson(url, headers, body, success, fail) {
|
||||||
|
global.$
|
||||||
|
.ajax({
|
||||||
|
url: url,
|
||||||
|
type: 'PATCH',
|
||||||
|
contentType: 'application/json',
|
||||||
|
dataType: 'json',
|
||||||
|
data: body != null ? JSON.stringify(body) : null,
|
||||||
|
headers: headers
|
||||||
|
})
|
||||||
|
.done(function (data) {
|
||||||
|
if (success) success(data)
|
||||||
|
})
|
||||||
|
.fail(function (xhr) {
|
||||||
|
if (fail) fail(xhr)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// v1 memo list: GET /api/v1/memo
|
||||||
|
// Query params (v0.20/v0.21): limit/offset/rowStatus/content/tag (best-effort)
|
||||||
|
function listMemos(info, options, success, fail) {
|
||||||
|
const opt = options || {}
|
||||||
|
const headers = { Authorization: 'Bearer ' + info.apiTokens }
|
||||||
|
|
||||||
|
const limit = opt.limit && Number.isFinite(opt.limit) ? Math.max(1, Math.floor(opt.limit)) : 1000
|
||||||
|
const offset = opt.offset && Number.isFinite(opt.offset) ? Math.max(0, Math.floor(opt.offset)) : null
|
||||||
|
const rowStatus = typeof opt.rowStatus === 'string' && opt.rowStatus ? opt.rowStatus : 'NORMAL'
|
||||||
|
|
||||||
|
const content = typeof opt.contentSearch === 'string' ? opt.contentSearch : ''
|
||||||
|
const tag = typeof opt.tagSearch === 'string' ? opt.tagSearch : ''
|
||||||
|
|
||||||
|
let qs = '?limit=' + encodeURIComponent(String(limit))
|
||||||
|
if (offset != null) qs += '&offset=' + encodeURIComponent(String(offset))
|
||||||
|
if (rowStatus) qs += '&rowStatus=' + encodeURIComponent(String(rowStatus))
|
||||||
|
if (content) qs += '&content=' + encodeURIComponent(String(content))
|
||||||
|
if (tag) qs += '&tag=' + encodeURIComponent(String(tag).replace(/^#/, ''))
|
||||||
|
|
||||||
|
requestGet(
|
||||||
|
info.apiUrl + 'api/v1/memo' + qs,
|
||||||
|
headers,
|
||||||
|
function (data) {
|
||||||
|
if (success) success({ memos: extractMemoListFromResponse(data) })
|
||||||
|
},
|
||||||
|
function (xhr) {
|
||||||
|
// Some builds might expose plural `/api/v1/memos`; try as a last resort (still v1).
|
||||||
|
if (isNotFoundLikeXhr(xhr)) {
|
||||||
|
requestGet(
|
||||||
|
info.apiUrl + 'api/v1/memos' + qs,
|
||||||
|
headers,
|
||||||
|
function (data2) {
|
||||||
|
if (success) success({ memos: extractMemoListFromResponse(data2) })
|
||||||
|
},
|
||||||
|
fail
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (fail) fail(xhr)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMemo(info, body, success, fail) {
|
||||||
|
const headers = { Authorization: 'Bearer ' + info.apiTokens }
|
||||||
|
requestPostJson(
|
||||||
|
info.apiUrl + 'api/v1/memo',
|
||||||
|
headers,
|
||||||
|
body,
|
||||||
|
function (data) {
|
||||||
|
if (success) success(extractMemoEntityFromResponse(data))
|
||||||
|
},
|
||||||
|
function (xhr) {
|
||||||
|
// Last resort: plural route.
|
||||||
|
if (isNotFoundLikeXhr(xhr)) {
|
||||||
|
requestPostJson(
|
||||||
|
info.apiUrl + 'api/v1/memos',
|
||||||
|
headers,
|
||||||
|
body,
|
||||||
|
function (data2) {
|
||||||
|
if (success) success(extractMemoEntityFromResponse(data2))
|
||||||
|
},
|
||||||
|
fail
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (fail) fail(xhr)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function patchMemo(info, memoId, patch, success, fail) {
|
||||||
|
const headers = { Authorization: 'Bearer ' + info.apiTokens }
|
||||||
|
const id = memoId != null ? String(memoId) : ''
|
||||||
|
if (!id) {
|
||||||
|
if (fail) fail({ status: 400 })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
requestPatchJson(
|
||||||
|
info.apiUrl + 'api/v1/memo/' + encodeURIComponent(id),
|
||||||
|
headers,
|
||||||
|
patch,
|
||||||
|
function (data) {
|
||||||
|
if (success) success(extractMemoEntityFromResponse(data))
|
||||||
|
},
|
||||||
|
fail
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTagList(info, success, fail) {
|
||||||
|
const headers = { Authorization: 'Bearer ' + info.apiTokens }
|
||||||
|
requestGet(
|
||||||
|
info.apiUrl + 'api/v1/tag',
|
||||||
|
headers,
|
||||||
|
function (data) {
|
||||||
|
const list = Array.isArray(data) ? data : Array.isArray(data.tags) ? data.tags : []
|
||||||
|
const out = list
|
||||||
|
.map(function (t) {
|
||||||
|
if (!t) return ''
|
||||||
|
if (typeof t === 'string') return t
|
||||||
|
if (typeof t.name === 'string') return t.name
|
||||||
|
if (typeof t.tag === 'string') return t.tag
|
||||||
|
return ''
|
||||||
|
})
|
||||||
|
.map(function (s) {
|
||||||
|
return String(s).replace(/^#/, '').trim()
|
||||||
|
})
|
||||||
|
.filter(Boolean)
|
||||||
|
if (success) success(out)
|
||||||
|
},
|
||||||
|
fail
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTagSuggestion(info, success, fail) {
|
||||||
|
const headers = { Authorization: 'Bearer ' + info.apiTokens }
|
||||||
|
requestGet(
|
||||||
|
info.apiUrl + 'api/v1/tag/suggestion',
|
||||||
|
headers,
|
||||||
|
function (data) {
|
||||||
|
const list = Array.isArray(data) ? data : []
|
||||||
|
const out = list
|
||||||
|
.map(function (s) {
|
||||||
|
return String(s).replace(/^#/, '').trim()
|
||||||
|
})
|
||||||
|
.filter(Boolean)
|
||||||
|
if (success) success(out)
|
||||||
|
},
|
||||||
|
function (xhr) {
|
||||||
|
// Some forks might only expose list.
|
||||||
|
if (isNotFoundLikeXhr(xhr)) {
|
||||||
|
getTagList(info, success, fail)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (fail) fail(xhr)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function uploadResourceBlob(info, file, meta, success, fail) {
|
||||||
|
const headers = { Authorization: 'Bearer ' + info.apiTokens }
|
||||||
|
const url = info.apiUrl + 'api/v1/resource/blob'
|
||||||
|
|
||||||
|
const m = meta || {}
|
||||||
|
const filename = String(m.filename || (file && file.name) || 'upload')
|
||||||
|
|
||||||
|
const form = new FormData()
|
||||||
|
if (file) form.append('file', file, filename)
|
||||||
|
|
||||||
|
global.$
|
||||||
|
.ajax({
|
||||||
|
url: url,
|
||||||
|
type: 'POST',
|
||||||
|
data: form,
|
||||||
|
processData: false,
|
||||||
|
contentType: false,
|
||||||
|
dataType: 'json',
|
||||||
|
headers: headers
|
||||||
|
})
|
||||||
|
.done(function (data) {
|
||||||
|
if (success) success(extractResourceEntityFromResponse(data))
|
||||||
|
})
|
||||||
|
.fail(function (xhr) {
|
||||||
|
if (fail) fail(xhr)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteResource(info, resourceId, success, fail) {
|
||||||
|
const headers = { Authorization: 'Bearer ' + info.apiTokens }
|
||||||
|
const id = resourceId != null ? String(resourceId) : ''
|
||||||
|
if (!id) {
|
||||||
|
if (fail) fail({ status: 400 })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
global.$
|
||||||
|
.ajax({
|
||||||
|
url: info.apiUrl + 'api/v1/resource/' + encodeURIComponent(id),
|
||||||
|
type: 'DELETE',
|
||||||
|
headers: headers
|
||||||
|
})
|
||||||
|
.done(function (data) {
|
||||||
|
if (success) success(data)
|
||||||
|
})
|
||||||
|
.fail(function (xhr) {
|
||||||
|
if (fail) fail(xhr)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
global.MemosApiV020V021 = {
|
||||||
|
listMemos: listMemos,
|
||||||
|
createMemo: createMemo,
|
||||||
|
patchMemo: patchMemo,
|
||||||
|
getTagList: getTagList,
|
||||||
|
getTagSuggestion: getTagSuggestion,
|
||||||
|
uploadResourceBlob: uploadResourceBlob,
|
||||||
|
deleteResource: deleteResource
|
||||||
|
}
|
||||||
|
})(window)
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
(function (global) {
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
function buildFilter(parts) {
|
||||||
|
const p = parts || {}
|
||||||
|
const exprs = []
|
||||||
|
|
||||||
|
if (p.creator) {
|
||||||
|
// v0.23 expects a CEL string variable `creator`.
|
||||||
|
exprs.push('creator == ' + JSON.stringify(String(p.creator)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(p.visibilities) && p.visibilities.length > 0) {
|
||||||
|
const list = p.visibilities.map(function (v) {
|
||||||
|
return JSON.stringify(String(v))
|
||||||
|
})
|
||||||
|
exprs.push('visibilities == [' + list.join(',') + ']')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof p.contentSearch === 'string' && p.contentSearch.length > 0) {
|
||||||
|
exprs.push('content_search == [' + JSON.stringify(String(p.contentSearch)) + ']')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof p.rowStatus === 'string' && p.rowStatus.length > 0) {
|
||||||
|
exprs.push('row_status == ' + JSON.stringify(String(p.rowStatus)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(p.tagSearch) && p.tagSearch.length > 0) {
|
||||||
|
const list = p.tagSearch.map(function (t) {
|
||||||
|
return JSON.stringify(String(t).replace(/^#/, ''))
|
||||||
|
})
|
||||||
|
exprs.push('tag_search == [' + list.join(',') + ']')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof p.random === 'boolean') {
|
||||||
|
exprs.push('random == ' + (p.random ? 'true' : 'false'))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof p.limit === 'number' && Number.isFinite(p.limit) && p.limit > 0) {
|
||||||
|
exprs.push('limit == ' + String(Math.floor(p.limit)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return exprs.join(' && ')
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractTagsFromMemo(memo) {
|
||||||
|
if (!memo) return []
|
||||||
|
|
||||||
|
// v0.23: tags live in memo.property.tags
|
||||||
|
if (memo.property && Array.isArray(memo.property.tags)) return memo.property.tags
|
||||||
|
|
||||||
|
// Defensive: some versions/serializers may use `properties` instead of `property`.
|
||||||
|
if (memo.properties && Array.isArray(memo.properties.tags)) return memo.properties.tags
|
||||||
|
|
||||||
|
// Defensive: some JSON serializers may wrap repeated fields.
|
||||||
|
if (memo.property && memo.property.tags && Array.isArray(memo.property.tags.values)) {
|
||||||
|
return memo.property.tags.values
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memo.properties && memo.properties.tags && Array.isArray(memo.properties.tags.values)) {
|
||||||
|
return memo.properties.tags.values
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: parse tags from content, e.g. "#tag".
|
||||||
|
const content = typeof memo.content === 'string' ? memo.content : ''
|
||||||
|
if (!content) return []
|
||||||
|
|
||||||
|
const found = []
|
||||||
|
// Match any hashtag token; server-side parser is stricter, but we want a lenient UI fallback.
|
||||||
|
const re = /#([^\s#]+)/g
|
||||||
|
let m
|
||||||
|
while ((m = re.exec(content))) {
|
||||||
|
let tag = m[1] || ''
|
||||||
|
// Trim trailing punctuation/brackets commonly attached in markdown.
|
||||||
|
tag = tag.replace(/[\]\[\)\(\}\{"'.,;:!?]+$/g, '')
|
||||||
|
tag = tag.replace(/^#+/, '')
|
||||||
|
tag = tag.trim()
|
||||||
|
if (!tag) continue
|
||||||
|
if (tag.length > 64) tag = tag.slice(0, 64)
|
||||||
|
found.push(tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(new Set(found))
|
||||||
|
}
|
||||||
|
|
||||||
|
function listMemos(info, options, success, fail) {
|
||||||
|
const opt = options || {}
|
||||||
|
const pageSize = opt.pageSize && Number.isFinite(opt.pageSize) ? Math.max(1, Math.floor(opt.pageSize)) : 1000
|
||||||
|
const filterExpr = typeof opt.filterExpr === 'string' ? opt.filterExpr : ''
|
||||||
|
|
||||||
|
const qs =
|
||||||
|
'?pageSize=' +
|
||||||
|
encodeURIComponent(String(pageSize)) +
|
||||||
|
(filterExpr ? '&filter=' + encodeURIComponent(filterExpr) : '')
|
||||||
|
|
||||||
|
// v0.23 removed the user-scoped memos endpoint: `/api/v1/users/{id}/memos`.
|
||||||
|
// Don't reuse fetchMemosWithFallback() because it will always emit an extra 404 first.
|
||||||
|
global.$
|
||||||
|
.ajax({
|
||||||
|
url: info.apiUrl + 'api/v1/memos' + qs,
|
||||||
|
type: 'GET',
|
||||||
|
contentType: 'application/json',
|
||||||
|
dataType: 'json',
|
||||||
|
headers: { Authorization: 'Bearer ' + info.apiTokens }
|
||||||
|
})
|
||||||
|
.done(function (data) {
|
||||||
|
success(data)
|
||||||
|
})
|
||||||
|
.fail(function (xhr) {
|
||||||
|
if (fail) fail(xhr)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
global.MemosApiV023 = {
|
||||||
|
buildFilter: buildFilter,
|
||||||
|
listMemos: listMemos,
|
||||||
|
extractTagsFromMemo: extractTagsFromMemo
|
||||||
|
}
|
||||||
|
})(window)
|
||||||
Vendored
+1
File diff suppressed because one or more lines are too long
+210
@@ -0,0 +1,210 @@
|
|||||||
|
const UI_LANGUAGE_STORAGE_KEY = 'uiLanguage'
|
||||||
|
|
||||||
|
const SUPPORTED_UI_LANGUAGES = new Set(['auto', 'en', 'zh_CN', 'ja', 'ko'])
|
||||||
|
|
||||||
|
function normalizeUiLanguage(value) {
|
||||||
|
const lang = String(value || 'auto')
|
||||||
|
return SUPPORTED_UI_LANGUAGES.has(lang) ? lang : 'auto'
|
||||||
|
}
|
||||||
|
|
||||||
|
function storageSyncGet(defaults) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
chrome.storage.sync.get(defaults, (items) => resolve(items || {}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function storageSyncSet(items) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
chrome.storage.sync.set(items, () => resolve())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadLocaleMessages(locale) {
|
||||||
|
if (!locale || locale === 'auto') return null
|
||||||
|
try {
|
||||||
|
const url = chrome.runtime.getURL(`_locales/${locale}/messages.json`)
|
||||||
|
const resp = await fetch(url)
|
||||||
|
if (!resp.ok) return null
|
||||||
|
return await resp.json()
|
||||||
|
} catch (_) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatSubstitutions(message, substitutions) {
|
||||||
|
if (!message) return ''
|
||||||
|
if (substitutions == null) return message
|
||||||
|
const subs = Array.isArray(substitutions) ? substitutions : [substitutions]
|
||||||
|
let out = message
|
||||||
|
for (let i = 0; i < subs.length; i++) {
|
||||||
|
const v = String(subs[i])
|
||||||
|
out = out.replaceAll(`$${i + 1}`, v)
|
||||||
|
out = out.replace('%s', v)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentUiLanguage = 'auto'
|
||||||
|
let overrideMessages = null
|
||||||
|
|
||||||
|
function getLanguageToggleLabel(lang) {
|
||||||
|
if (lang === 'en') return 'EN'
|
||||||
|
if (lang === 'zh_CN') return '中'
|
||||||
|
if (lang === 'ja') return '日'
|
||||||
|
if (lang === 'ko') return '한'
|
||||||
|
return 'A'
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncLanguageToggleText(lang) {
|
||||||
|
const text = document.getElementById('langToggleText')
|
||||||
|
if (text) text.textContent = getLanguageToggleLabel(lang)
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncLanguageMenuState(lang) {
|
||||||
|
const items = document.querySelectorAll('.lang-menu-item')
|
||||||
|
items.forEach((item) => {
|
||||||
|
const isActive = item.getAttribute('data-lang') === lang
|
||||||
|
item.classList.toggle('active', isActive)
|
||||||
|
item.setAttribute('aria-checked', isActive ? 'true' : 'false')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function setLanguageMenuOpen(isOpen) {
|
||||||
|
const toggle = document.getElementById('langToggle')
|
||||||
|
const menu = document.getElementById('langMenu')
|
||||||
|
if (!toggle || !menu) return
|
||||||
|
toggle.setAttribute('aria-expanded', isOpen ? 'true' : 'false')
|
||||||
|
menu.classList.toggle('hidden', !isOpen)
|
||||||
|
}
|
||||||
|
|
||||||
|
function t(key, substitutions) {
|
||||||
|
const msg = overrideMessages && overrideMessages[key] && overrideMessages[key].message
|
||||||
|
if (typeof msg === 'string' && msg.length > 0) {
|
||||||
|
return formatSubstitutions(msg, substitutions)
|
||||||
|
}
|
||||||
|
const chromeMsg = chrome.i18n.getMessage(key, substitutions) || ''
|
||||||
|
return formatSubstitutions(chromeMsg, substitutions)
|
||||||
|
}
|
||||||
|
|
||||||
|
function setText(id, messageKey) {
|
||||||
|
const el = document.getElementById(id)
|
||||||
|
if (el) el.textContent = t(messageKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
function setPlaceholder(id, messageKey) {
|
||||||
|
const el = document.getElementById(id)
|
||||||
|
if (el) el.placeholder = t(messageKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
function setTitle(id, messageKey) {
|
||||||
|
const el = document.getElementById(id)
|
||||||
|
if (el) el.title = t(messageKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyStaticI18n() {
|
||||||
|
setText('saveSettings', 'saveBtn')
|
||||||
|
setText('saveTag', 'saveBtn')
|
||||||
|
|
||||||
|
setText('supportedMemosVersion', 'supportedMemosVersion')
|
||||||
|
setText('settingsConnectionTitle', 'settingsConnectionTitle')
|
||||||
|
setText('settingsConnectionDesc', 'settingsConnectionDesc')
|
||||||
|
setText('settingsPostingTitle', 'settingsPostingTitle')
|
||||||
|
setText('settingsPostingDesc', 'settingsPostingDesc')
|
||||||
|
|
||||||
|
setPlaceholder('apiUrl', 'placeApiUrl')
|
||||||
|
setPlaceholder('apiTokens', 'placeApiTokens')
|
||||||
|
setPlaceholder('content', 'placeContent')
|
||||||
|
|
||||||
|
setText('lockPrivate', 'lockPrivate')
|
||||||
|
setText('lockProtected', 'lockProtected')
|
||||||
|
setText('lockPublic', 'lockPublic')
|
||||||
|
|
||||||
|
setText('content_submit_text', 'submitBtn')
|
||||||
|
const fullscreen = document.getElementById('fullscreen')
|
||||||
|
if (fullscreen) fullscreen.setAttribute('aria-label', t('tipFullscreen'))
|
||||||
|
|
||||||
|
setPlaceholder('hideInput', 'placeHideInput')
|
||||||
|
setPlaceholder('showInput', 'placeShowInput')
|
||||||
|
setPlaceholder('attachmentOnlyDefaultText', 'placeAttachmentOnlyDefaultText')
|
||||||
|
|
||||||
|
setText('uploadlist-title', 'uploadedListTitle')
|
||||||
|
|
||||||
|
// Language switcher
|
||||||
|
setText('langOptionAuto', 'langAuto')
|
||||||
|
setText('langOptionEn', 'langEnglish')
|
||||||
|
setText('langOptionZhCN', 'langChineseSimplified')
|
||||||
|
setText('langOptionJa', 'langJapanese')
|
||||||
|
setText('langOptionKo', 'langKorean')
|
||||||
|
setTitle('langToggle', 'tipLanguage')
|
||||||
|
const langToggle = document.getElementById('langToggle')
|
||||||
|
if (langToggle) langToggle.setAttribute('aria-label', t('tipLanguage'))
|
||||||
|
|
||||||
|
// Native hover tooltips (title)
|
||||||
|
setTitle('opensite', 'tipOpenSite')
|
||||||
|
setTitle('blog_info_edit', 'tipSettings')
|
||||||
|
setTitle('tags', 'tipTags')
|
||||||
|
setTitle('newtodo', 'tipTodo')
|
||||||
|
setTitle('upres', 'tipUpload')
|
||||||
|
setTitle('getlink', 'tipLink')
|
||||||
|
setTitle('random', 'tipRandom')
|
||||||
|
setTitle('search', 'tipSearch')
|
||||||
|
setTitle('lock', 'tipVisibility')
|
||||||
|
setTitle('content_submit_text', 'tipSend')
|
||||||
|
setTitle('fullscreen', 'tipFullscreen')
|
||||||
|
setTitle('editor-resize-handle', 'tipResize')
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setUiLanguage(nextLang, { persist = true } = {}) {
|
||||||
|
const lang = normalizeUiLanguage(nextLang)
|
||||||
|
currentUiLanguage = lang
|
||||||
|
overrideMessages = await loadLocaleMessages(lang)
|
||||||
|
applyStaticI18n()
|
||||||
|
syncLanguageToggleText(lang)
|
||||||
|
syncLanguageMenuState(lang)
|
||||||
|
|
||||||
|
if (persist) await storageSyncSet({ [UI_LANGUAGE_STORAGE_KEY]: lang })
|
||||||
|
window.dispatchEvent(new CustomEvent('i18n:changed', { detail: { lang } }))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function initLanguageSwitcher() {
|
||||||
|
const switcher = document.getElementById('lang_switcher')
|
||||||
|
const toggle = document.getElementById('langToggle')
|
||||||
|
const langItems = document.querySelectorAll('.lang-menu-item')
|
||||||
|
|
||||||
|
if (toggle) {
|
||||||
|
toggle.addEventListener('click', (event) => {
|
||||||
|
event.stopPropagation()
|
||||||
|
const isOpen = toggle.getAttribute('aria-expanded') === 'true'
|
||||||
|
setLanguageMenuOpen(!isOpen)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
langItems.forEach((item) => {
|
||||||
|
item.addEventListener('click', async (event) => {
|
||||||
|
event.stopPropagation()
|
||||||
|
setLanguageMenuOpen(false)
|
||||||
|
await setUiLanguage(item.getAttribute('data-lang'))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
document.addEventListener('click', (event) => {
|
||||||
|
if (!switcher || switcher.contains(event.target)) return
|
||||||
|
setLanguageMenuOpen(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
document.addEventListener('keydown', (event) => {
|
||||||
|
if (event.key === 'Escape') setLanguageMenuOpen(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
const storedItems = await storageSyncGet({ [UI_LANGUAGE_STORAGE_KEY]: 'auto' })
|
||||||
|
const stored = normalizeUiLanguage(storedItems[UI_LANGUAGE_STORAGE_KEY])
|
||||||
|
await setUiLanguage(stored, { persist: false })
|
||||||
|
setLanguageMenuOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
window.t = t
|
||||||
|
window.setUiLanguage = setUiLanguage
|
||||||
|
window.getUiLanguage = () => currentUiLanguage
|
||||||
|
|
||||||
|
applyStaticI18n()
|
||||||
|
window.i18nReady = initLanguageSwitcher()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
!function(e,_){"object"==typeof exports&&"undefined"!=typeof module?module.exports=_(require("dayjs")):"function"==typeof define&&define.amd?define(["dayjs"],_):(e="undefined"!=typeof globalThis?globalThis:e||self).dayjs_locale_ja=_(e.dayjs)}(this,(function(e){"use strict";function _(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var t=_(e),a={name:"ja",relativeTime:{future:"%s後",past:"%s前",s:"数秒",m:"1分",mm:"%d分",h:"1時間",hh:"%d時間",d:"1日",dd:"%d日",M:"1か月",MM:"%dか月",y:"1年",yy:"%d年"}};return t.default.locale(a,null,!0),a}));
|
||||||
Vendored
+2
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
|||||||
|
!function(e,_){"object"==typeof exports&&"undefined"!=typeof module?module.exports=_(require("dayjs")):"function"==typeof define&&define.amd?define(["dayjs"],_):(e="undefined"!=typeof globalThis?globalThis:e||self).dayjs_locale_ko=_(e.dayjs)}(this,(function(e){"use strict";function _(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var t=_(e),a={name:"ko",relativeTime:{future:"%s 후",past:"%s 전",s:"몇 초",m:"1분",mm:"%d분",h:"1시간",hh:"%d시간",d:"하루",dd:"%d일",M:"1개월",MM:"%d개월",y:"1년",yy:"%d년"}};return t.default.locale(a,null,!0),a}));
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
/**
|
||||||
|
* 消息提示组件
|
||||||
|
*
|
||||||
|
* 1.调用
|
||||||
|
* 字符串类型参数: $.message('成功');
|
||||||
|
* 对象型参数:$.message({});
|
||||||
|
*
|
||||||
|
* 2.参数详解
|
||||||
|
* message:' 操作成功', //提示信息
|
||||||
|
time:'2000', //显示时间(默认:2s)
|
||||||
|
type:'success', //显示类型,包括4种:success.error,info,warning
|
||||||
|
showClose:false, //显示关闭按钮(默认:否)
|
||||||
|
autoClose:true, //是否自动关闭(默认:是)
|
||||||
|
*
|
||||||
|
* type:success,error,info,warning
|
||||||
|
*/
|
||||||
|
|
||||||
|
$.extend({
|
||||||
|
message: function(options) {
|
||||||
|
var defaults={
|
||||||
|
message:' 操作成功',
|
||||||
|
time:'2000',
|
||||||
|
autoClose: true,
|
||||||
|
onClose:function(){}
|
||||||
|
};
|
||||||
|
|
||||||
|
if(typeof options === 'string'){
|
||||||
|
defaults.message=options;
|
||||||
|
}
|
||||||
|
if(typeof options === 'object'){
|
||||||
|
defaults=$.extend({},defaults,options);
|
||||||
|
}
|
||||||
|
//message模版
|
||||||
|
var template='<div class="tip animate bounceIn">\n' +
|
||||||
|
' <p class="tip-info">'+defaults.message+'</p>\n' +
|
||||||
|
'</div>';
|
||||||
|
var _this=this;
|
||||||
|
var $body=$('body');
|
||||||
|
var $message=$(template);
|
||||||
|
var timer;
|
||||||
|
|
||||||
|
//移除所有并插入该消息
|
||||||
|
$('.tip').remove();
|
||||||
|
$body.append($message);
|
||||||
|
//居中
|
||||||
|
$message.css({
|
||||||
|
'margin-left':'-'+$message.width()/2+'px'
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
//自动关闭
|
||||||
|
if (defaults.autoClose){
|
||||||
|
timer=setTimeout(function(){
|
||||||
|
closeFn();
|
||||||
|
},defaults.time);
|
||||||
|
}
|
||||||
|
//关闭
|
||||||
|
var closeFn = function(){
|
||||||
|
$message.addClass('hide');
|
||||||
|
$message.remove();
|
||||||
|
defaults.onClose(defaults);
|
||||||
|
clearTimeout(timer);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
+1259
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
|||||||
|
!function(r,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(r="undefined"!=typeof globalThis?globalThis:r||self).dayjs_plugin_relativeTime=e()}(this,(function(){"use strict";return function(r,e,t){r=r||{};var n=e.prototype,o={future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"};function i(r,e,t,o){return n.fromToBase(r,e,t,o)}t.en.relativeTime=o,n.fromToBase=function(e,n,i,d,u){for(var f,a,s,l=i.$locale().relativeTime||o,h=r.thresholds||[{l:"s",r:44,d:"second"},{l:"m",r:89},{l:"mm",r:44,d:"minute"},{l:"h",r:89},{l:"hh",r:21,d:"hour"},{l:"d",r:35},{l:"dd",r:25,d:"day"},{l:"M",r:45},{l:"MM",r:10,d:"month"},{l:"y",r:17},{l:"yy",d:"year"}],m=h.length,c=0;c<m;c+=1){var y=h[c];y.d&&(f=d?t(e).diff(i,y.d,!0):i.diff(e,y.d,!0));var p=(r.rounding||Math.round)(Math.abs(f));if(s=f>0,p<=y.r||!y.r){p<=1&&c>0&&(y=h[c-1]);var v=l[y.l];u&&(p=u(""+p)),a="string"==typeof v?v.replace("%d",p):v(p,n,y.l,s);break}}if(n)return a;var M=s?l.future:l.past;return"function"==typeof M?M(a):M.replace("%s",a)},n.to=function(r,e){return i(r,e,this,!0)},n.from=function(r,e){return i(r,e,this)};var d=function(r){return r.$u?t.utc():t()};n.toNow=function(r){return this.to(d(this),r)},n.fromNow=function(r){return this.from(d(this),r)}}}));
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* ViewImage.min.js 2.0.2
|
||||||
|
* MIT License - http://www.opensource.org/licenses/mit-license.php
|
||||||
|
* https://tokinx.github.io/ViewImage/
|
||||||
|
*/
|
||||||
|
var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.createTemplateTagFirstArg=function(b){return b.raw=b};$jscomp.createTemplateTagFirstArgWithRaw=function(b,a){b.raw=a;return b};$jscomp.arrayIteratorImpl=function(b){var a=0;return function(){return a<b.length?{done:!1,value:b[a++]}:{done:!0}}};$jscomp.arrayIterator=function(b){return{next:$jscomp.arrayIteratorImpl(b)}};$jscomp.makeIterator=function(b){var a="undefined"!=typeof Symbol&&Symbol.iterator&&b[Symbol.iterator];return a?a.call(b):$jscomp.arrayIterator(b)};
|
||||||
|
$jscomp.arrayFromIterator=function(b){for(var a,d=[];!(a=b.next()).done;)d.push(a.value);return d};$jscomp.arrayFromIterable=function(b){return b instanceof Array?b:$jscomp.arrayFromIterator($jscomp.makeIterator(b))};
|
||||||
|
(function(){window.ViewImage=new function(){var b=this;this.target="[view-image] img";this.listener=function(a){if(!(a.ctrlKey||a.metaKey||a.shiftKey||a.altKey)){var d=String(b.target.split(",").map(function(g){return g.trim()+":not([no-view])"})),c=a.target.closest(d);if(c){var e=c.closest("[view-image]")||document.body;d=[].concat($jscomp.arrayFromIterable(e.querySelectorAll(d))).map(function(g){return g.href||g.src});b.display(d,c.href||c.src);a.stopPropagation();a.preventDefault()}}};this.init=
|
||||||
|
function(a){a&&(b.target=a);["removeEventListener","addEventListener"].forEach(function(d){document[d]("click",b.listener,!1)})};this.display=function(a,d){var c=a.indexOf(d),e=(new DOMParser).parseFromString('\n <div class="view-image">\n <style>.view-image{position:fixed;inset:0;z-index:500;padding:1rem;display:flex;flex-direction:column;animation:view-image-in 300ms;backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px)}.view-image__out{animation:view-image-out 300ms}@keyframes view-image-in{0%{opacity:0}}@keyframes view-image-out{100%{opacity:0}}.view-image-btn{width:32px;height:32px;display:flex;justify-content:center;align-items:center;cursor:pointer;border-radius:3px;background-color:rgba(255,255,255,0.2)}.view-image-btn:hover{background-color:rgba(255,255,255,0.5)}.view-image-close__full{position:absolute;inset:0;background-color:rgba(48,55,66,0.3);z-index:unset;cursor:zoom-out;margin:0}.view-image-container{height:0;flex:1;display:flex;align-items:center;justify-content:center;}.view-image-lead{display:contents}.view-image-lead img{position:relative;z-index:1;max-width:100%;max-height:100%;object-fit:contain;border-radius:3px}.view-image-lead__in img{animation:view-image-lead-in 300ms}.view-image-lead__out img{animation:view-image-lead-out 300ms forwards}@keyframes view-image-lead-in{0%{opacity:0;transform:translateY(-20px)}}@keyframes view-image-lead-out{100%{opacity:0;transform:translateY(20px)}}[class*=__out] ~ .view-image-loading{display:block}.view-image-loading{position:absolute;inset:50%;width:8rem;height:2rem;color:#aab2bd;overflow:hidden;text-align:center;margin:-1rem -4rem;z-index:1;display:none}.view-image-loading::after{content:"";position:absolute;inset:50% 0;width:100%;height:3px;background:rgba(255,255,255,0.5);transform:translateX(-100%) translateY(-50%);animation:view-image-loading 800ms -100ms ease-in-out infinite}@keyframes view-image-loading{0%{transform:translateX(-100%)}100%{transform:translateX(100%)}}.view-image-tools{position:relative;display:flex;justify-content:space-between;align-content:center;color:#fff;max-width:600px;position: absolute; bottom: 5%; left: 1rem; right: 1rem; backdrop-filter: blur(10px);margin:0 auto;padding:10px;border-radius:5px;background:rgba(0,0,0,0.1);margin-bottom:constant(safe-area-inset-bottom);margin-bottom:env(safe-area-inset-bottom);z-index:1}.view-image-tools__count{width:60px;display:flex;align-items:center;justify-content:center}.view-image-tools__flip{display:flex;gap:10px}.view-image-tools [class*=-close]{margin:0 10px}</style>\n <div class="view-image-container">\n <div class="view-image-lead"></div>\n <div class="view-image-loading"></div>\n <div class="view-image-close view-image-close__full"></div>\n </div>\n <div class="view-image-tools">\n <div class="view-image-tools__count">\n <span><b class="view-image-index">'+
|
||||||
|
(c+1)+"</b>/"+a.length+'</span>\n </div>\n <div class="view-image-tools__flip">\n <div class="view-image-btn view-image-tools__flip-prev">\n <svg width="20" height="20" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="48" height="48" fill="white" fill-opacity="0.01"/><path d="M31 36L19 24L31 12" stroke="white" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/></svg>\n </div>\n <div class="view-image-btn view-image-tools__flip-next">\n <svg width="20" height="20" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="48" height="48" fill="white" fill-opacity="0.01"/><path d="M19 12L31 24L19 36" stroke="white" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/></svg>\n </div>\n </div>\n <div class="view-image-btn view-image-close">\n <svg width="16" height="16" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="48" height="48" fill="white" fill-opacity="0.01"/><path d="M8 8L40 40" stroke="white" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path d="M8 40L40 8" stroke="white" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/></svg>\n </div>\n </div>\n </div>\n ',
|
||||||
|
"text/html").body.firstChild,g=function(f){var h={Escape:"close",ArrowLeft:"tools__flip-prev",ArrowRight:"tools__flip-next"};h[f.key]&&e.querySelector(".view-image-"+h[f.key]).click()},l=function(f){var h=new Image,k=e.querySelector(".view-image-lead");k.className="view-image-lead view-image-lead__out";setTimeout(function(){k.innerHTML="";h.onload=function(){setTimeout(function(){k.innerHTML='<img src="'+h.src+'" alt="ViewImage" no-view/>';k.className="view-image-lead view-image-lead__in"},100)};
|
||||||
|
h.src=f},300)};document.body.appendChild(e);l(d);window.addEventListener("keydown",g);e.onclick=function(f){f.target.closest(".view-image-close")?(window.removeEventListener("keydown",g),e.onclick=null,e.classList.add("view-image__out"),setTimeout(function(){return e.remove()},290)):f.target.closest(".view-image-tools__flip")&&(c=f.target.closest(".view-image-tools__flip-prev")?0===c?a.length-1:c-1:c===a.length-1?0:c+1,l(a[c]),e.querySelector(".view-image-index").innerHTML=c+1)}}}})();
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
!function(e,_){"object"==typeof exports&&"undefined"!=typeof module?module.exports=_(require("dayjs")):"function"==typeof define&&define.amd?define(["dayjs"],_):(e="undefined"!=typeof globalThis?globalThis:e||self).dayjs_locale_zh_cn=_(e.dayjs)}(this,(function(e){"use strict";function _(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var t=_(e),d={name:"zh-cn",weekdays:"星期日_星期一_星期二_星期三_星期四_星期五_星期六".split("_"),weekdaysShort:"周日_周一_周二_周三_周四_周五_周六".split("_"),weekdaysMin:"日_一_二_三_四_五_六".split("_"),months:"一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月".split("_"),monthsShort:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),ordinal:function(e,_){return"W"===_?e+"周":e+"日"},weekStart:1,yearStart:4,formats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY/MM/DD",LL:"YYYY年M月D日",LLL:"YYYY年M月D日Ah点mm分",LLLL:"YYYY年M月D日ddddAh点mm分",l:"YYYY/M/D",ll:"YYYY年M月D日",lll:"YYYY年M月D日 HH:mm",llll:"YYYY年M月D日dddd HH:mm"},relativeTime:{future:"%s内",past:"%s前",s:"几秒",m:"1 分钟",mm:"%d 分钟",h:"1 小时",hh:"%d 小时",d:"1 天",dd:"%d 天",M:"1 个月",MM:"%d 个月",y:"1 年",yy:"%d 年"},meridiem:function(e,_){var t=100*e+_;return t<600?"凌晨":t<900?"早上":t<1100?"上午":t<1300?"中午":t<1800?"下午":"晚上"}};return t.default.locale(d,null,!0),d}));
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"manifest_version": 3,
|
||||||
|
"name": "__MSG_extName__",
|
||||||
|
"default_locale": "en",
|
||||||
|
"version": "2026.04.23",
|
||||||
|
"action": {
|
||||||
|
"default_popup": "popup.html",
|
||||||
|
"default_icon": "assets/logo_24x24.png",
|
||||||
|
"default_title": "__MSG_actionTitle__"
|
||||||
|
},
|
||||||
|
"description": "__MSG_extDescription__",
|
||||||
|
"homepage_url": "https://github.com/Jonnyan404/memos-bber",
|
||||||
|
"icons": {
|
||||||
|
"128": "assets/logo.png",
|
||||||
|
"16": "assets/logo.png",
|
||||||
|
"48": "assets/logo.png"
|
||||||
|
},
|
||||||
|
"background": {
|
||||||
|
"service_worker": "js/background.js"
|
||||||
|
},
|
||||||
|
"permissions": [
|
||||||
|
"tabs",
|
||||||
|
"scripting",
|
||||||
|
"windows",
|
||||||
|
"storage",
|
||||||
|
"activeTab",
|
||||||
|
"contextMenus"
|
||||||
|
],
|
||||||
|
"host_permissions": ["http://*/*", "https://*/*"],
|
||||||
|
"commands": {
|
||||||
|
"open-extension": {
|
||||||
|
"description": "Open my extension",
|
||||||
|
"suggested_key": {
|
||||||
|
"default": "Ctrl+Shift+F",
|
||||||
|
"mac": "MacCtrl+Shift+F"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+194
@@ -0,0 +1,194 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta
|
||||||
|
name="viewport"
|
||||||
|
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
|
||||||
|
/>
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
||||||
|
<title>MEMOS</title>
|
||||||
|
<link rel="stylesheet" href="../css/main.css" />
|
||||||
|
</head>
|
||||||
|
<body class="body">
|
||||||
|
<div class="title" id="opensite">MEMOS</div>
|
||||||
|
<div id="lang_switcher" class="lang-switcher">
|
||||||
|
<button
|
||||||
|
id="langToggle"
|
||||||
|
class="lang-toggle"
|
||||||
|
type="button"
|
||||||
|
aria-haspopup="true"
|
||||||
|
aria-expanded="false"
|
||||||
|
>
|
||||||
|
<span id="langToggleText" class="lang-toggle-text" aria-hidden="true">A</span>
|
||||||
|
</button>
|
||||||
|
<div id="langMenu" class="lang-menu hidden" role="menu" aria-labelledby="langToggle">
|
||||||
|
<button id="langOptionAuto" class="lang-menu-item" type="button" data-lang="auto" role="menuitemradio" aria-checked="false"></button>
|
||||||
|
<button id="langOptionEn" class="lang-menu-item" type="button" data-lang="en" role="menuitemradio" aria-checked="false"></button>
|
||||||
|
<button id="langOptionZhCN" class="lang-menu-item" type="button" data-lang="zh_CN" role="menuitemradio" aria-checked="false"></button>
|
||||||
|
<button id="langOptionJa" class="lang-menu-item" type="button" data-lang="ja" role="menuitemradio" aria-checked="false"></button>
|
||||||
|
<button id="langOptionKo" class="lang-menu-item" type="button" data-lang="ko" role="menuitemradio" aria-checked="false"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="blog_info_edit"><svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" class="icon" viewBox="0 0 1024 1024">
|
||||||
|
<path d="M914 432c-5-26-21-43-41-43h-4c-54 0-99-44-99-99 0-17 9-37 9-38 10-22 2-50-18-65l-103-57h-1c-21-9-49-4-64 12-12 12-50 44-79 44s-68-33-79-45a60 60 0 0 0-64-13l-106 58-2 1a54 54 0 0 0-18 65c0 1 9 21 9 38 0 55-45 99-99 99h-5c-19 0-35 17-40 43 0 2-9 45-9 80s9 79 9 81c5 25 21 42 41 42h4c54 0 99 45 99 99 0 18-9 37-9 38-10 23-2 51 18 65l101 56 1 1c21 9 49 3 65-13 14-15 52-47 80-47 30 0 69 35 81 48a58 58 0 0 0 64 14l104-58 2-1c20-14 28-42 18-65 0-1-9-20-9-38 0-54 45-99 99-99h5c19 0 35-17 40-42 0-2 9-46 9-81s-9-78-9-80m-51 80c0 23-5 52-7 64a158 158 0 0 0-134 215l-89 49c-4-5-17-18-35-31-31-23-61-35-88-35s-57 12-88 34c-17 13-30 26-34 31l-86-48a159 159 0 0 0-134-215c-2-12-7-41-7-64 0-22 5-51 7-64a157 157 0 0 0 134-214l91-50c4 4 17 17 35 29 30 22 59 33 86 33s55-11 85-32c18-13 31-25 35-29l88 49a159 159 0 0 0 134 214c2 13 7 42 7 64"/>
|
||||||
|
<path d="M510 366a146 146 0 1 0 1 292 146 146 0 0 0-1-292m87 146a87 87 0 1 1-173-1 87 87 0 0 1 173 1"/>
|
||||||
|
</svg></div>
|
||||||
|
<div id="blog_info" class="settings-panel">
|
||||||
|
<div class="settings-section">
|
||||||
|
<div id="settingsConnectionTitle" class="settings-section-title"></div>
|
||||||
|
<div class="settings-section-desc" id="settingsConnectionDesc"></div>
|
||||||
|
<input
|
||||||
|
id="apiUrl"
|
||||||
|
class="inputer settings-input"
|
||||||
|
name="apiUrl"
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
maxlength="245"
|
||||||
|
placeholder=""
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
id="apiTokens"
|
||||||
|
class="inputer settings-input"
|
||||||
|
name="apiTokens"
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
maxlength="245"
|
||||||
|
placeholder=""
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<div id="supportedMemosVersion" class="upload-list-title"></div>
|
||||||
|
</div>
|
||||||
|
<div class="settings-section">
|
||||||
|
<div id="settingsPostingTitle" class="settings-section-title"></div>
|
||||||
|
<div class="settings-section-desc" id="settingsPostingDesc"></div>
|
||||||
|
<textarea
|
||||||
|
id="attachmentOnlyDefaultText"
|
||||||
|
class="inputer settings-input settings-textarea"
|
||||||
|
name="attachmentOnlyDefaultText"
|
||||||
|
rows="2"
|
||||||
|
maxlength="500"
|
||||||
|
placeholder=""
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="settings-actions">
|
||||||
|
<span id="saveSettings" class="action-btn confirm-btn"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="memo-editor">
|
||||||
|
<div class="memo-editor-header">
|
||||||
|
<button id="fullscreen" class="action-btn" type="button" aria-label="">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-fullscreen" viewBox="0 0 16 16">
|
||||||
|
<path d="M1.5 1a.5.5 0 0 0-.5.5v4a.5.5 0 0 1-1 0v-4A1.5 1.5 0 0 1 1.5 0h4a.5.5 0 0 1 0 1zM10 .5a.5.5 0 0 1 .5-.5h4A1.5 1.5 0 0 1 16 1.5v4a.5.5 0 0 1-1 0v-4a.5.5 0 0 0-.5-.5h-4a.5.5 0 0 1-.5-.5M.5 10a.5.5 0 0 1 .5.5v4a.5.5 0 0 0 .5.5h4a.5.5 0 0 1 0 1h-4A1.5 1.5 0 0 1 0 14.5v-4a.5.5 0 0 1 .5-.5m15 0a.5.5 0 0 1 .5.5v4a1.5 1.5 0 0 1-1.5 1.5h-4a.5.5 0 0 1 0-1h4a.5.5 0 0 0 .5-.5v-4a.5.5 0 0 1 .5-.5"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<textarea
|
||||||
|
class="common-editor-inputer"
|
||||||
|
rows="4"
|
||||||
|
name="text"
|
||||||
|
id="content"
|
||||||
|
placeholder=""
|
||||||
|
required=""
|
||||||
|
></textarea>
|
||||||
|
<div id="editor-resize-handle" aria-label="Resize"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="common-tools-wrapper">
|
||||||
|
<div class="common-tools-container">
|
||||||
|
<div id="tags" class="mr-5">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" class="icon" viewBox="0 0 1024 1024">
|
||||||
|
<path fill="#666" d="M171 341h682q43 0 43 43t-43 43H171q-43 0-43-43t43-43Z"/>
|
||||||
|
<path fill="#666" d="M423 85h4a39 39 0 0 1 38 43l-77 772a43 43 0 0 1-43 39h-4a39 39 0 0 1-38-43l77-772a43 43 0 0 1 43-39zm256 0h4a39 39 0 0 1 38 43l-77 772a43 43 0 0 1-43 39h-4a39 39 0 0 1-38-43l77-772a43 43 0 0 1 43-39z"/>
|
||||||
|
<path fill="#666" d="M171 597h682q43 0 43 43t-43 43H171q-43 0-43-43t43-43Z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div id="newtodo" class="mr-5">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" class="icon" viewBox="0 0 1024 1024">
|
||||||
|
<path fill="#666" d="M407 365a41 41 0 0 0-59 0 41 41 0 0 0 0 60l149 149c9 8 19 13 30 13s21-5 30-13l341-341c17-18 17-43 0-60s-43-17-60 0L527 484 407 365z"/>
|
||||||
|
<path fill="#666" d="M868 416c-23 0-45 19-45 45v277c0 2 0 7-2 9 0 2-2 4-4 6s-4 4-6 4l-9 2H247c-2 0-6 0-8-2-2 0-4-2-6-4-3-2-5-4-5-6l-2-9V183l2-8c0-2 2-4 5-6 2-3 4-5 6-5l8-2h278c23 0 45-19 45-45s-20-44-45-44H247c-14 0-27 2-42 8a144 144 0 0 0-55 60c-7 13-9 28-9 42v555c0 15 2 28 8 43a122 122 0 0 0 58 58c13 6 28 8 43 8h554a108 108 0 0 0 77-32c11-11 17-21 24-34 6-13 8-28 8-43V461c-2-26-21-45-45-45z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div id="upres" class="mr-5">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" class="icon" viewBox="0 0 1024 1024">
|
||||||
|
<path fill="#555" d="M752 80H272c-70 0-128 58-128 128v608c0 70 58 128 128 128h354c33 0 65-13 91-37l126-126c24-24 37-56 37-91V208c0-70-58-128-128-128zM208 816V208c0-35 29-64 64-64h480c35 0 64 29 64 64v464h-96c-70 0-128 58-128 128v80H272c-35 0-64-29-64-64zm462 45c-4 5-9 8-14 11v-72c0-35 29-64 64-64h75L670 861z"/>
|
||||||
|
<path fill="#555" d="M368 352h288c18 0 32-14 32-32s-14-32-32-32H368c-18 0-32 14-32 32s14 32 32 32zm128 256H368c-18 0-32 14-32 32s14 32 32 32h128c18 0 32-14 32-32s-14-32-32-32zm-128-96h288c18 0 32-14 32-32s-14-32-32-32H368c-18 0-32 14-32 32s14 32 32 32z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div id="getlink" class="mr-5">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" class="icon" viewBox="0 0 1024 1024">
|
||||||
|
<path fill="#666" d="m600 697-1 1-94 76a198 198 0 0 1-280-30c-69-85-56-211 30-280l99-81-46-57-99 81a273 273 0 0 0 143 483 279 279 0 0 0 29 1c63 0 122-21 171-61l95-76-46-56-1-1zm256-464a273 273 0 0 0-383-40l-91 73 47 58 90-74a199 199 0 1 1 250 310l-96 77-1 1 46 57 97-78c56-46 92-111 99-184 9-72-12-143-58-200z"/>
|
||||||
|
<path fill="#666" d="m388 668 306-255 1-1-48-56-305 255h-2z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div id="random" class="mr-5">
|
||||||
|
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="32" height="32"><path fill="#666" d="M988.492 718.906L864.168 595.6c-15.686-15.556-41.012-15.454-56.568.232-15.556 15.686-15.452 41.012.232 56.568L922.368 766h-48.812c-115.514 0-222.1-49.978-292.428-137.122-13.874-17.194-39.058-19.88-56.248-6.006-17.192 13.874-19.88 39.056-6.006 56.248C604.464 785.176 733.74 846 873.556 846h44.78L807.832 955.6c-15.684 15.556-15.79 40.882-.232 56.568A39.88 39.88 0 0 0 836.002 1024c10.18 0 20.368-3.864 28.166-11.6l124.324-123.306C1011.39 866.384 1024 836.162 1024 804s-12.61-62.382-35.508-85.094z"/><path fill="#666" d="M988.492 134.906L864.168 11.6c-15.686-15.556-41.012-15.454-56.568.232-15.556 15.686-15.452 41.012.232 56.568L918.336 178h-44.78c-163.332 0-314.542 86.102-394.626 224.702l-16.952 29.342-27.352-47.342C354.544 246.102 203.332 160 40 160c-22.092 0-40 17.908-40 40s17.908 40 40 40c134.852 0 259.522 70.782 325.356 184.724L415.78 512l-50.426 87.276C299.522 713.22 174.852 784 40 784c-22.092 0-40 17.908-40 40s17.908 40 40 40c163.332 0 314.542-86.102 394.626-224.702l61.64-106.684c.224-.374.442-.752.654-1.134l51.28-88.756C614.034 328.782 738.704 258 873.556 258h48.812L807.832 371.6c-15.684 15.556-15.79 40.882-.232 56.568A39.88 39.88 0 0 0 836.002 440c10.18 0 20.368-3.864 28.166-11.6l124.324-123.306C1011.39 282.384 1024 252.162 1024 220s-12.61-62.382-35.508-85.094z"/></svg>
|
||||||
|
</div>
|
||||||
|
<div id="search" class="mr-5">
|
||||||
|
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="32" height="32"><path d="M689.067 631.467L889.6 832c38.4 38.4-19.2 96-57.6 57.6L631.467 689.067C576 731.733 505.6 757.333 430.933 757.333 249.6 757.333 102.4 610.133 102.4 428.8s147.2-326.4 328.533-326.4 328.534 147.2 328.534 328.533c-2.134 74.667-27.734 145.067-70.4 200.534zm-258.134 44.8c136.534 0 245.334-110.934 245.334-245.334S565.333 183.467 430.933 183.467 183.467 294.4 183.467 430.933 294.4 676.267 430.933 676.267z" fill="#666"/></svg>
|
||||||
|
</div>
|
||||||
|
<div class="selector-wrapper visibility-selector ">
|
||||||
|
<div id="lock" class="current-value-container active false">
|
||||||
|
<span id="lock-now" class="value-text"></span><span class="arrow-text"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon-img"><polyline points="6 9 12 15 18 9"></polyline></svg></span>
|
||||||
|
</div>
|
||||||
|
<div id="lock-wrapper" class="items-wrapper !hidden">
|
||||||
|
<div id="lockPrivate" class="item-lock" data-type="PRIVATE"></div>
|
||||||
|
<div id="lockProtected" class="item-lock" data-type="PROTECTED"></div>
|
||||||
|
<div id="lockPublic" class="item-lock" data-type="PUBLIC"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="btns-container" type="submit" name="submit" id="submit">
|
||||||
|
<button id="content_submit_text" class="action-btn confirm-btn"><img class="icon-img" src="../assets/logo_24x24.png"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="upload-list-wrapper">
|
||||||
|
<div id="uploadlist-title" class="upload-list-title"></div>
|
||||||
|
<div id="uploadlist" class="upload-list"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tag-list" id="taglist"></div>
|
||||||
|
<div class="tag-hide" id="taghide">
|
||||||
|
<input
|
||||||
|
id="hideInput"
|
||||||
|
class="inputer"
|
||||||
|
name="hideInput"
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
maxlength="50"
|
||||||
|
placeholder=""
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
id="showInput"
|
||||||
|
class="inputer"
|
||||||
|
name="showInput"
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
maxlength="50"
|
||||||
|
placeholder=""
|
||||||
|
/>
|
||||||
|
<span id="saveTag" class="action-btn confirm-btn"></span>
|
||||||
|
</div>
|
||||||
|
<div class="" id="randomlist"></div>
|
||||||
|
|
||||||
|
<input type="file" id="inFile" style="display:none;">
|
||||||
|
|
||||||
|
<script src="../js/i18n.js"></script>
|
||||||
|
<script src="../js/jquery.min.js"></script>
|
||||||
|
<script src="../js/message.js"></script>
|
||||||
|
<script src="../js/dayjs.min.js"></script>
|
||||||
|
<script src="../js/zh-cn.js"></script>
|
||||||
|
<script src="../js/ja.js"></script>
|
||||||
|
<script src="../js/ko.js"></script>
|
||||||
|
<script src="../js/relativeTime.js"></script>
|
||||||
|
<script src="../js/view-image.js"></script>
|
||||||
|
<script src="../js/compat/memosApi.modern.js"></script>
|
||||||
|
<script src="../js/compat/memosApi.v020-v021.js"></script>
|
||||||
|
<script src="../js/compat/memosApi.v023.js"></script>
|
||||||
|
<script src="../js/compat/memosApi.adapter.js"></script>
|
||||||
|
<script src="../js/oper.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+28
-3
@@ -44,6 +44,7 @@ function initProportionalEditorResize() {
|
|||||||
const nonEditorHeight = Math.max(0, Math.ceil(document.body.scrollHeight - initialRect.height))
|
const nonEditorHeight = Math.max(0, Math.ceil(document.body.scrollHeight - initialRect.height))
|
||||||
let maxScale = 1
|
let maxScale = 1
|
||||||
let currentScale = 1
|
let currentScale = 1
|
||||||
|
let nextScale = 1
|
||||||
let dragging = false
|
let dragging = false
|
||||||
let dragStartX = 0
|
let dragStartX = 0
|
||||||
let dragStartY = 0
|
let dragStartY = 0
|
||||||
@@ -54,8 +55,21 @@ function initProportionalEditorResize() {
|
|||||||
return Math.min(Math.max(scale, 1), maxScale)
|
return Math.min(Math.max(scale, 1), maxScale)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const showPreviewScale = (scale) => {
|
||||||
|
const previewScale = clampScale(scale)
|
||||||
|
const relativeScale = previewScale / currentScale
|
||||||
|
editor.style.transformOrigin = 'top left'
|
||||||
|
editor.style.transform = `scale(${relativeScale})`
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearPreviewScale = () => {
|
||||||
|
editor.style.transform = ''
|
||||||
|
editor.style.transformOrigin = ''
|
||||||
|
}
|
||||||
|
|
||||||
const applyScale = (scale) => {
|
const applyScale = (scale) => {
|
||||||
currentScale = clampScale(scale)
|
currentScale = clampScale(scale)
|
||||||
|
nextScale = currentScale
|
||||||
editor.style.width = `${Math.round(baseW * currentScale)}px`
|
editor.style.width = `${Math.round(baseW * currentScale)}px`
|
||||||
editor.style.height = `${Math.round(baseH * currentScale)}px`
|
editor.style.height = `${Math.round(baseH * currentScale)}px`
|
||||||
}
|
}
|
||||||
@@ -75,6 +89,7 @@ function initProportionalEditorResize() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const computeMaxScale = () => {
|
const computeMaxScale = () => {
|
||||||
|
if (dragging) return
|
||||||
// In popup mode, allow scaling up to Chrome's max popup size.
|
// In popup mode, allow scaling up to Chrome's max popup size.
|
||||||
// Do not clamp by current window.innerWidth/innerHeight, otherwise the popup can't grow to the max.
|
// Do not clamp by current window.innerWidth/innerHeight, otherwise the popup can't grow to the max.
|
||||||
const viewportW = 800
|
const viewportW = 800
|
||||||
@@ -94,6 +109,9 @@ function initProportionalEditorResize() {
|
|||||||
if (!dragging) return
|
if (!dragging) return
|
||||||
dragging = false
|
dragging = false
|
||||||
handle.classList.remove('dragging')
|
handle.classList.remove('dragging')
|
||||||
|
clearPreviewScale()
|
||||||
|
applyScale(nextScale)
|
||||||
|
computeMaxScale()
|
||||||
persistScale()
|
persistScale()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,7 +121,8 @@ function initProportionalEditorResize() {
|
|||||||
const dy = ev.clientY - dragStartY
|
const dy = ev.clientY - dragStartY
|
||||||
const widthScale = (baseW * dragStartScale + dx) / baseW
|
const widthScale = (baseW * dragStartScale + dx) / baseW
|
||||||
const heightScale = (baseH * dragStartScale + dy) / baseH
|
const heightScale = (baseH * dragStartScale + dy) / baseH
|
||||||
applyScale(Math.max(widthScale, heightScale))
|
nextScale = clampScale(Math.max(widthScale, heightScale))
|
||||||
|
showPreviewScale(nextScale)
|
||||||
}
|
}
|
||||||
|
|
||||||
const startDrag = (ev) => {
|
const startDrag = (ev) => {
|
||||||
@@ -112,6 +131,8 @@ function initProportionalEditorResize() {
|
|||||||
dragStartX = ev.clientX
|
dragStartX = ev.clientX
|
||||||
dragStartY = ev.clientY
|
dragStartY = ev.clientY
|
||||||
dragStartScale = currentScale
|
dragStartScale = currentScale
|
||||||
|
nextScale = currentScale
|
||||||
|
showPreviewScale(currentScale)
|
||||||
handle.classList.add('dragging')
|
handle.classList.add('dragging')
|
||||||
if (typeof handle.setPointerCapture === 'function') {
|
if (typeof handle.setPointerCapture === 'function') {
|
||||||
try {
|
try {
|
||||||
@@ -209,6 +230,10 @@ function updateLockNowText(lockType) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showRandomList(content) {
|
||||||
|
$('#randomlist').stop(true, true).html(content).show()
|
||||||
|
}
|
||||||
|
|
||||||
applyDayjsLocaleByUiLanguage(typeof window.getUiLanguage === 'function' ? window.getUiLanguage() : 'auto')
|
applyDayjsLocaleByUiLanguage(typeof window.getUiLanguage === 'function' ? window.getUiLanguage() : 'auto')
|
||||||
|
|
||||||
if (isFullscreenMode()) {
|
if (isFullscreenMode()) {
|
||||||
@@ -994,7 +1019,7 @@ $('#search').click(function () {
|
|||||||
searchDom += '</div>'
|
searchDom += '</div>'
|
||||||
}
|
}
|
||||||
window.ViewImage && ViewImage.init('.random-image')
|
window.ViewImage && ViewImage.init('.random-image')
|
||||||
$("#randomlist").html(searchDom).slideDown(500);
|
showRandomList(searchDom)
|
||||||
hydrateV1PreviewImages(info)
|
hydrateV1PreviewImages(info)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1073,7 +1098,7 @@ function randDom(randomData){
|
|||||||
}
|
}
|
||||||
randomDom += '</div>'
|
randomDom += '</div>'
|
||||||
window.ViewImage && ViewImage.init('.random-image')
|
window.ViewImage && ViewImage.init('.random-image')
|
||||||
$("#randomlist").html(randomDom).slideDown(500);
|
showRandomList(randomDom)
|
||||||
hydrateV1PreviewImages(info)
|
hydrateV1PreviewImages(info)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user