14 Commits

Author SHA1 Message Date
jonny ae9b37d87a Add changelog section to README
Reorganized README by removing the version compatibility info and adding a new Changelog section with a link to the change log file. Moved the Ko-fi donation button to be more prominent.
2026-08-05 17:26:29 +08:00
jonny c4da5f790a Update Memos version support text
Refresh the changelog and localized compatibility strings to reflect support for Memos 0.28.x-0.30.x, and correct the older image preview note to 0.19.x-0.23.x.
2026-08-05 17:20:16 +08:00
jonny 1361cc15db Fix tags display and Memos API compatibility issues
Fix the tags not displaying issue by querying both /tag/suggestion and /tag endpoints and merging results. Simplify v0.23 filter to avoid 400 errors from numeric ID requirements. Add v0.18.2 support via numeric ID fallback in getMemoUid. Improve v0.23+ resource URL handling and add better error handling with debug logging for tags and random features.
2026-08-05 17:07:59 +08:00
jonny afbb7ac5cc Fix 0.20.x image preview URLs by prioritizing uid check
Reorder fallback checks in buildV1ResourceStreamUrl to prioritize uid-like identifiers before numeric IDs. This fixes image preview URLs in Memos 0.20.x that incorrectly used numeric IDs instead of proper UIDs. Applied to Chrome, Edge, and Firefox extensions. Updated changelog accordingly.
2026-08-05 12:19:16 +08:00
jonny e01ea6b0df Merge pull request #7 from Windyskr/agent/support-memos-0.29-attachments
fix: 适配 Memos 0.29.x 附件 API
2026-08-05 11:24:14 +08:00
Windyskr 5b0483007a fix: 对齐 Memos 0.29 更新接口 2026-08-05 00:30:09 +08:00
Windyskr e4529814a4 fix: 适配 Memos 0.29.x 附件 API 2026-08-05 00:06:19 +08:00
jonny 776ee343f4 Update README.md 2026-06-06 21:08:47 +08:00
jonny 4ea7d19e28 Create FUNDING.yml 2026-06-06 20:54:50 +08:00
jonny 6221eebcc5 Firefox version has passed the app store review 2026-04-27 19:07:32 +08:00
jonny e65a2a1878 Update manifest.json 2026-04-24 18:56:03 +08:00
jonny 3d995079f4 Add German, French, and Spanish localization
Add DE/FR/ES support across Chrome, Edge and Firefox builds: new _locales message files for de, fr, es. Include dayjs locale bundles (de.js, fr.js, es.js) and load them in popup.html. Update i18n.js to register new languages and labels, update oper.js to apply the corresponding dayjs locale (including auto-detection), add language buttons to the popup UI, bump manifest version, and record the change in change.log.
2026-04-24 18:50:18 +08:00
jonny bc0ce222ac Delete .DS_Store 2026-04-23 11:10:16 +08:00
jonny 4ef85a092b Create .gitignore 2026-04-23 11:05:44 +08:00
56 changed files with 2617 additions and 483 deletions
Vendored
BIN
View File
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
ko_fi: jonnyan404
+1
View File
@@ -0,0 +1 @@
*.DS_Store
+8 -2
View File
@@ -1,9 +1,15 @@
一款通过浏览器插件发布 [Memos](https://usememos.com/) 的插件。基于 iSpeak-bber 修改,原作者为 [DreamyTZK](https://www.antmoe.com/)。 一款通过浏览器插件发布 [Memos](https://usememos.com/) 的插件。基于 iSpeak-bber 修改,原作者为 [DreamyTZK](https://www.antmoe.com/)。
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/jonnyan404)
## 更新日志
- 当前支持memos `v0.15.0 - v0.30.x` [点击查看详细更新日志](https://raw.githubusercontent.com/Jonnyan404/memos-bber/refs/heads/main/change.log)
## 在线商店安装 ## 在线商店安装
- 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: 使用离线安装包或者直接使用 chrome 扩展 - (等待上架)Edge: 使用离线安装包或者直接使用 chrome 扩展
## 移动端 ## 移动端
@@ -16,4 +22,4 @@ Android
### 使用教程 ### 使用教程
- 在文本框输入想搜索的关键字,点击搜索按钮 - 在文本框输入想搜索的关键字,点击搜索按钮
- 随机和搜索功能在`0.24`以上版本支持私有权限的 memo,其它版本不支持 - 随机和搜索功能在 `0.24` 以上版本支持私有权限的 memo,其他版本不支持
+3 -1
View File
@@ -1,4 +1,6 @@
## 更新日志 ## 更新日志
- 20260805 适配 Memos 0.28.x-0.30.x Attachment API,修复附件上传、绑定和预览地址;修复 0.19.x-0.23.x 图片预览 URL 错误使用 numeric id 的问题以及标签不显示问题
- 20260425 新增德语/法语/西班牙语支持
- 20260423 优化 firefox 抖动问题和支持手机版,支持 edge 浏览器扩展 - 20260423 优化 firefox 抖动问题和支持手机版,支持 edge 浏览器扩展
- 20260422 调整发送设置,支持仅发送附件 - 20260422 调整发送设置,支持仅发送附件
#### 20260421 更新匹配 0.27.x #### 20260421 更新匹配 0.27.x
@@ -68,4 +70,4 @@
2022.10.24 添加 visiblity 发送设置。 2022.10.24 添加 visiblity 发送设置。
</details> </details>
+197
View File
@@ -0,0 +1,197 @@
{
"extName": {
"message": "Memos"
},
"actionTitle": {
"message": "An Memos senden"
},
"extDescription": {
"message": "memos: Ein leichtgewichtiges, selbst gehostetes Memo-Hub."
},
"sendTo": {
"message": "An Memos senden \"%s\""
},
"sendLinkTo": {
"message": "Link an Memos senden"
},
"sendImageTo": {
"message": "Bild an Memos senden"
},
"saveBtn": {
"message": "Speichern"
},
"supportedMemosVersion": {
"message": "Kompatibel mit Memos v0.15.0 - 0.30.x"
},
"settingsConnectionTitle": {
"message": "Verbindung"
},
"settingsConnectionDesc": {
"message": "Konfiguriere die URL der Memos-Instanz und das Zugriffstoken."
},
"settingsPostingTitle": {
"message": "Verfassen"
},
"settingsPostingDesc": {
"message": "Standardtext für Uploads ohne Text"
},
"placeApiUrl": {
"message": "URL der Memos-Instanz"
},
"placeApiTokens": {
"message": "Memos-Zugriffstoken"
},
"placeContent": {
"message": "Was geht dir durch den Kopf..."
},
"lockPrivate": {
"message": "Privat"
},
"lockProtected": {
"message": "Geschützt"
},
"lockPublic": {
"message": "Öffentlich"
},
"submitBtn": {
"message": "Speichern"
},
"placeHideInput": {
"message": "Standardname für den Tag \"Privat\""
},
"placeShowInput": {
"message": "Standardname für den Tag \"Für alle sichtbar\""
},
"placeAttachmentOnlyDefaultText": {
"message": "Standardtext für Uploads ohne Text (leer lassen, um den eingebauten Text zu verwenden)"
},
"uploadedListTitle": {
"message": "Hochgeladene Dateien, zum Neuordnen ziehen"
},
"uploadedListEmpty": {
"message": "Keine hochgeladenen Dateien"
},
"tipReorder": {
"message": "Zum Neuordnen ziehen"
},
"tipDeleteAttachment": {
"message": "Löschen"
},
"attachmentDeleteSuccess": {
"message": "Gelöscht"
},
"attachmentDeleteFailed": {
"message": "Löschen fehlgeschlagen 😭"
},
"picDrag": {
"message": "Bild zum Hochladen hierher ziehen"
},
"picCancelDrag": {
"message": "Upload abbrechen"
},
"picUploading": {
"message": "Bild wird hochgeladen..."
},
"picSuccess": {
"message": "Upload abgeschlossen"
},
"picFailed": {
"message": "Upload fehlgeschlagen"
},
"picPending": {
"message": "Bild wird noch hochgeladen"
},
"saveSuccess": {
"message": "Einstellungen gespeichert!"
},
"searchNow": {
"message": "Wonach suchst du?"
},
"searchNone": {
"message": "Versuche es mit einem anderen Wort!"
},
"archiveSuccess": {
"message": "Archiviert 😊"
},
"archiveFailed": {
"message": "Archivieren fehlgeschlagen 😭"
},
"getTabFailed": {
"message": "Tab konnte nicht gelesen werden 😭"
},
"memoUploading": {
"message": "Wird gesendet"
},
"memoSuccess": {
"message": "Erfolgreich! 😊"
},
"memoFailed": {
"message": "Fehlgeschlagen! 😭"
},
"invalidToken": {
"message": "Ungültiges Token oder ungültige URL 😭"
},
"tipOpenSite": {
"message": "Memos öffnen"
},
"tipSettings": {
"message": "Einstellungen"
},
"tipTags": {
"message": "Tag einfügen"
},
"tipTodo": {
"message": "Todo einfügen"
},
"tipUpload": {
"message": "Datei hochladen"
},
"tipLink": {
"message": "Link des aktuellen Tabs einfügen"
},
"tipRandom": {
"message": "Zufälliges Memo"
},
"tipSearch": {
"message": "Suchen"
},
"tipVisibility": {
"message": "Sichtbarkeit"
},
"tipSend": {
"message": "Senden (Strg/⌘+Enter)"
},
"tipLanguage": {
"message": "Sprache"
},
"langAuto": {
"message": "Automatisch"
},
"langEnglish": {
"message": "English"
},
"langGerman": {
"message": "Deutsch"
},
"langFrench": {
"message": "Français"
},
"langSpanish": {
"message": "Español"
},
"langChineseSimplified": {
"message": "简体中文"
},
"langJapanese": {
"message": "日本語"
},
"langKorean": {
"message": "한국어"
},
"tipFullscreen": {
"message": "Vollbild-Editor öffnen"
},
"tipResize": {
"message": "Zum Ändern der Größe ziehen (Minimum: Standardgröße)"
}
}
+10 -1
View File
@@ -21,7 +21,7 @@
"message": "Save" "message": "Save"
}, },
"supportedMemosVersion": { "supportedMemosVersion": {
"message": "Compatible with Memos v0.15.0 - 0.27.x" "message": "Compatible with Memos v0.15.0 - 0.30.x"
}, },
"settingsConnectionTitle": { "settingsConnectionTitle": {
"message": "Connection" "message": "Connection"
@@ -170,6 +170,15 @@
"langEnglish": { "langEnglish": {
"message": "English" "message": "English"
}, },
"langGerman": {
"message": "Deutsch"
},
"langFrench": {
"message": "Français"
},
"langSpanish": {
"message": "Español"
},
"langChineseSimplified": { "langChineseSimplified": {
"message": "简体中文" "message": "简体中文"
}, },
+197
View File
@@ -0,0 +1,197 @@
{
"extName": {
"message": "Memos"
},
"actionTitle": {
"message": "Enviar a Memos"
},
"extDescription": {
"message": "memos: un centro de notas ligero y autoalojado."
},
"sendTo": {
"message": "Enviar a Memos \"%s\""
},
"sendLinkTo": {
"message": "Enviar enlace a Memos"
},
"sendImageTo": {
"message": "Enviar imagen a Memos"
},
"saveBtn": {
"message": "Guardar"
},
"supportedMemosVersion": {
"message": "Compatible con Memos v0.15.0 - 0.30.x"
},
"settingsConnectionTitle": {
"message": "Conexión"
},
"settingsConnectionDesc": {
"message": "Configura la URL del sitio de Memos y el token de acceso."
},
"settingsPostingTitle": {
"message": "Publicación"
},
"settingsPostingDesc": {
"message": "Texto predeterminado para envíos solo con adjuntos"
},
"placeApiUrl": {
"message": "URL del sitio de Memos"
},
"placeApiTokens": {
"message": "Tokens de acceso de Memos"
},
"placeContent": {
"message": "¿Qué tienes en mente...?"
},
"lockPrivate": {
"message": "Privado"
},
"lockProtected": {
"message": "Protegido"
},
"lockPublic": {
"message": "Público"
},
"submitBtn": {
"message": "Guardar"
},
"placeHideInput": {
"message": "Nombre predeterminado de la etiqueta \"Privado\""
},
"placeShowInput": {
"message": "Nombre predeterminado de la etiqueta \"Visible para todos\""
},
"placeAttachmentOnlyDefaultText": {
"message": "Texto predeterminado para envíos solo con adjuntos (déjalo vacío para usar el texto integrado)"
},
"uploadedListTitle": {
"message": "Archivos subidos, arrastra para reordenar"
},
"uploadedListEmpty": {
"message": "No hay archivos subidos"
},
"tipReorder": {
"message": "Arrastra para reordenar"
},
"tipDeleteAttachment": {
"message": "Eliminar"
},
"attachmentDeleteSuccess": {
"message": "Eliminado"
},
"attachmentDeleteFailed": {
"message": "Error al eliminar 😭"
},
"picDrag": {
"message": "Arrastra la imagen para subirla"
},
"picCancelDrag": {
"message": "Cancelar subida"
},
"picUploading": {
"message": "Subiendo la imagen..."
},
"picSuccess": {
"message": "Subida completada"
},
"picFailed": {
"message": "La subida falló"
},
"picPending": {
"message": "La imagen aún se está subiendo"
},
"saveSuccess": {
"message": "¡Configuración guardada!"
},
"searchNow": {
"message": "¿Qué estás buscando?"
},
"searchNone": {
"message": "¡Prueba con otra palabra!"
},
"archiveSuccess": {
"message": "Archivado 😊"
},
"archiveFailed": {
"message": "Error al archivar 😭"
},
"getTabFailed": {
"message": "Error al obtener la pestaña 😭"
},
"memoUploading": {
"message": "Enviando"
},
"memoSuccess": {
"message": "¡Éxito! 😊"
},
"memoFailed": {
"message": "¡Falló! 😭"
},
"invalidToken": {
"message": "Token o URL no válidos 😭"
},
"tipOpenSite": {
"message": "Abrir Memos"
},
"tipSettings": {
"message": "Configuración"
},
"tipTags": {
"message": "Insertar etiqueta"
},
"tipTodo": {
"message": "Insertar tarea"
},
"tipUpload": {
"message": "Subir archivo"
},
"tipLink": {
"message": "Insertar enlace de la pestaña actual"
},
"tipRandom": {
"message": "Memo aleatorio"
},
"tipSearch": {
"message": "Buscar"
},
"tipVisibility": {
"message": "Visibilidad"
},
"tipSend": {
"message": "Enviar (Ctrl/⌘+Enter)"
},
"tipLanguage": {
"message": "Idioma"
},
"langAuto": {
"message": "Auto"
},
"langEnglish": {
"message": "English"
},
"langGerman": {
"message": "Deutsch"
},
"langFrench": {
"message": "Français"
},
"langSpanish": {
"message": "Español"
},
"langChineseSimplified": {
"message": "简体中文"
},
"langJapanese": {
"message": "日本語"
},
"langKorean": {
"message": "한국어"
},
"tipFullscreen": {
"message": "Abrir editor en pantalla completa"
},
"tipResize": {
"message": "Arrastra para cambiar el tamaño (mín.: tamaño predeterminado)"
}
}
+197
View File
@@ -0,0 +1,197 @@
{
"extName": {
"message": "Memos"
},
"actionTitle": {
"message": "Envoyer à Memos"
},
"extDescription": {
"message": "memos : un hub de mémos léger et auto-hébergé."
},
"sendTo": {
"message": "Envoyer à Memos \"%s\""
},
"sendLinkTo": {
"message": "Envoyer le lien vers Memos"
},
"sendImageTo": {
"message": "Envoyer l'image vers Memos"
},
"saveBtn": {
"message": "Enregistrer"
},
"supportedMemosVersion": {
"message": "Compatible avec Memos v0.15.0 - 0.30.x"
},
"settingsConnectionTitle": {
"message": "Connexion"
},
"settingsConnectionDesc": {
"message": "Configurez l'URL du site Memos et le jeton d'accès."
},
"settingsPostingTitle": {
"message": "Publication"
},
"settingsPostingDesc": {
"message": "Texte par défaut pour les envois avec pièce jointe uniquement"
},
"placeApiUrl": {
"message": "URL du site Memos"
},
"placeApiTokens": {
"message": "Jetons d'accès Memos"
},
"placeContent": {
"message": "À quoi pensez-vous..."
},
"lockPrivate": {
"message": "Privé"
},
"lockProtected": {
"message": "Protégé"
},
"lockPublic": {
"message": "Public"
},
"submitBtn": {
"message": "Enregistrer"
},
"placeHideInput": {
"message": "Nom par défaut du tag \"Privé\""
},
"placeShowInput": {
"message": "Nom par défaut du tag \"Visible par tous\""
},
"placeAttachmentOnlyDefaultText": {
"message": "Texte par défaut pour les envois avec pièce jointe uniquement (laisser vide pour utiliser le texte intégré)"
},
"uploadedListTitle": {
"message": "Fichiers téléversés, faites glisser pour réorganiser"
},
"uploadedListEmpty": {
"message": "Aucun fichier téléversé"
},
"tipReorder": {
"message": "Glisser pour réorganiser"
},
"tipDeleteAttachment": {
"message": "Supprimer"
},
"attachmentDeleteSuccess": {
"message": "Supprimé"
},
"attachmentDeleteFailed": {
"message": "Échec de la suppression 😭"
},
"picDrag": {
"message": "Faites glisser l'image pour la téléverser"
},
"picCancelDrag": {
"message": "Annuler le téléversement"
},
"picUploading": {
"message": "Téléversement de l'image..."
},
"picSuccess": {
"message": "Téléversement terminé"
},
"picFailed": {
"message": "Échec du téléversement"
},
"picPending": {
"message": "Le téléversement de l'image est en cours"
},
"saveSuccess": {
"message": "Paramètres enregistrés !"
},
"searchNow": {
"message": "Que recherchez-vous ?"
},
"searchNone": {
"message": "Essayez un autre mot !"
},
"archiveSuccess": {
"message": "Archivé 😊"
},
"archiveFailed": {
"message": "Échec de l'archivage 😭"
},
"getTabFailed": {
"message": "Échec de la récupération de l'onglet 😭"
},
"memoUploading": {
"message": "Envoi en cours"
},
"memoSuccess": {
"message": "Succès ! 😊"
},
"memoFailed": {
"message": "Échec ! 😭"
},
"invalidToken": {
"message": "Jeton ou URL invalide 😭"
},
"tipOpenSite": {
"message": "Ouvrir Memos"
},
"tipSettings": {
"message": "Paramètres"
},
"tipTags": {
"message": "Insérer un tag"
},
"tipTodo": {
"message": "Insérer une tâche"
},
"tipUpload": {
"message": "Téléverser un fichier"
},
"tipLink": {
"message": "Insérer le lien de l'onglet actuel"
},
"tipRandom": {
"message": "Mémo aléatoire"
},
"tipSearch": {
"message": "Rechercher"
},
"tipVisibility": {
"message": "Visibilité"
},
"tipSend": {
"message": "Envoyer (Ctrl/⌘+Entrée)"
},
"tipLanguage": {
"message": "Langue"
},
"langAuto": {
"message": "Auto"
},
"langEnglish": {
"message": "English"
},
"langGerman": {
"message": "Deutsch"
},
"langFrench": {
"message": "Français"
},
"langSpanish": {
"message": "Español"
},
"langChineseSimplified": {
"message": "简体中文"
},
"langJapanese": {
"message": "日本語"
},
"langKorean": {
"message": "한국어"
},
"tipFullscreen": {
"message": "Ouvrir l'éditeur en plein écran"
},
"tipResize": {
"message": "Glisser pour redimensionner (min. : taille par défaut)"
}
}
+10 -1
View File
@@ -21,7 +21,7 @@
"message": "保存" "message": "保存"
}, },
"supportedMemosVersion": { "supportedMemosVersion": {
"message": "Memos v0.15.0 - 0.27.x に対応" "message": "Memos v0.15.0 - 0.30.x に対応"
}, },
"settingsConnectionTitle": { "settingsConnectionTitle": {
"message": "接続設定" "message": "接続設定"
@@ -170,6 +170,15 @@
"langEnglish": { "langEnglish": {
"message": "English" "message": "English"
}, },
"langGerman": {
"message": "Deutsch"
},
"langFrench": {
"message": "Français"
},
"langSpanish": {
"message": "Español"
},
"langChineseSimplified": { "langChineseSimplified": {
"message": "简体中文" "message": "简体中文"
}, },
+10 -1
View File
@@ -21,7 +21,7 @@
"message": "저장" "message": "저장"
}, },
"supportedMemosVersion": { "supportedMemosVersion": {
"message": "Memos v0.15.0 - 0.27.x 호환" "message": "Memos v0.15.0 - 0.30.x 호환"
}, },
"settingsConnectionTitle": { "settingsConnectionTitle": {
"message": "연결 설정" "message": "연결 설정"
@@ -170,6 +170,15 @@
"langEnglish": { "langEnglish": {
"message": "English" "message": "English"
}, },
"langGerman": {
"message": "Deutsch"
},
"langFrench": {
"message": "Français"
},
"langSpanish": {
"message": "Español"
},
"langChineseSimplified": { "langChineseSimplified": {
"message": "简体中文" "message": "简体中文"
}, },
+10 -1
View File
@@ -21,7 +21,7 @@
"message": "保存" "message": "保存"
}, },
"supportedMemosVersion": { "supportedMemosVersion": {
"message": "兼容 Memos v0.15.0 - 0.27.x" "message": "兼容 Memos v0.15.0 - 0.30.x"
}, },
"settingsConnectionTitle": { "settingsConnectionTitle": {
"message": "连接设置" "message": "连接设置"
@@ -170,6 +170,15 @@
"langEnglish": { "langEnglish": {
"message": "English" "message": "English"
}, },
"langGerman": {
"message": "Deutsch"
},
"langFrench": {
"message": "Français"
},
"langSpanish": {
"message": "Español"
},
"langChineseSimplified": { "langChineseSimplified": {
"message": "简体中文" "message": "简体中文"
}, },
+42 -26
View File
@@ -187,6 +187,7 @@
function normalizeUploadedItem(entity, fallbackFilename) { function normalizeUploadedItem(entity, fallbackFilename) {
if (!entity) return null if (!entity) return null
entity = entity.attachment || entity.resource || entity
const inferredId = (function () { const inferredId = (function () {
const value = entity.id != null ? entity.id : entity.ID != null ? entity.ID : entity.Id 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 === 'number' && Number.isFinite(value)) return Math.floor(value)
@@ -204,7 +205,11 @@
name: name, name: name,
filename: entity.filename || fallbackFilename || name, filename: entity.filename || fallbackFilename || name,
createTime: entity.createTime || entity.createdTs || entity.createdAt, createTime: entity.createTime || entity.createdTs || entity.createdAt,
type: entity.type type: entity.type,
uid: entity.uid,
size: entity.size,
memo: entity.memo,
externalLink: entity.externalLink
} }
} }
@@ -244,10 +249,10 @@
} }
if (flavor === 'v023' && global.MemosApiV023) { if (flavor === 'v023' && global.MemosApiV023) {
const filterExpr = global.MemosApiV023.buildFilter({ // v0.23 filter requires `creator == "users/{numericId}"`. Some stored userids are
rowStatus: 'NORMAL', // usernames, which causes 400. Also `row_status` can cause 400 on some builds.
creator: 'users/' + info.userid // Let the server identify the current user from the token and return all visible memos.
}) const filterExpr = global.MemosApiV023.buildFilter({})
global.MemosApiV023.listMemos( global.MemosApiV023.listMemos(
info, info,
{ pageSize: 1000, filterExpr: filterExpr }, { pageSize: 1000, filterExpr: filterExpr },
@@ -259,15 +264,10 @@
return return
} }
if (global.MemosApiModern) { if (global.MemosApiV023) {
global.MemosApiModern.fetchMemosWithFallback( global.MemosApiV023.listMemos(info, { pageSize: 1000 }, function (data) {
info, if (success) success(collectTags(info, extractMemos(data)))
'?pageSize=1000', }, fail)
function (data) {
if (success) success(collectTags(info, extractMemos(data)))
},
fail
)
} }
} }
@@ -328,7 +328,10 @@
if (flavor === FLAVOR_V020_V021 && global.MemosApiV020V021) { if (flavor === FLAVOR_V020_V021 && global.MemosApiV020V021) {
global.MemosApiV020V021.listMemos(info, { limit: 1000, rowStatus: 'NORMAL' }, function (data) { global.MemosApiV020V021.listMemos(info, { limit: 1000, rowStatus: 'NORMAL' }, function (data) {
if (success) success(keepLegacyVisibleMemos(extractMemos(data))) const raw = extractMemos(data)
const filtered = keepLegacyVisibleMemos(raw)
//console.log('[memos-bber] v020-v021 random raw count:', raw.length, 'filtered count:', filtered.length)
if (success) success(filtered)
}, fail) }, fail)
return return
} }
@@ -364,10 +367,12 @@
} }
function uploadFile(file, options, success, fail) { function uploadFile(file, options, success, fail) {
const oldName = String(file && file.name ? file.name : 'upload').split('.') const sourceName = String(file && file.name ? file.name : 'upload')
const fileExt = String(file && file.name ? file.name : '').split('.').pop() const lastDot = sourceName.lastIndexOf('.')
const filenameBase = lastDot > 0 ? sourceName.slice(0, lastDot) : sourceName
const fileExt = lastDot > 0 ? sourceName.slice(lastDot) : ''
const now = global.dayjs().format('YYYYMMDDHHmmss') const now = global.dayjs().format('YYYYMMDDHHmmss')
const nextName = oldName[0] + '_' + now + (fileExt ? '.' + fileExt : '') const nextName = filenameBase + '_' + now + fileExt
if (flavor === FLAVOR_V020_V021 && global.MemosApiV020V021) { if (flavor === FLAVOR_V020_V021 && global.MemosApiV020V021) {
global.MemosApiV020V021.uploadResourceBlob( global.MemosApiV020V021.uploadResourceBlob(
@@ -396,7 +401,7 @@
info, info,
payload, payload,
function (resp) { function (resp) {
const entity = (resp && resp.resource) || resp const entity = (resp && (resp.attachment || resp.resource)) || resp
if (success) success(normalizeUploadedItem(entity, nextName)) if (success) success(normalizeUploadedItem(entity, nextName))
}, },
fail fail
@@ -416,9 +421,9 @@
} }
requestJson({ requestJson({
url: info.apiUrl + 'api/v1/' + memoName, url: info.apiUrl + 'api/v1/' + memoName + '?updateMask=state',
type: 'PATCH', type: 'PATCH',
data: JSON.stringify({ state: 'ARCHIVED' }), data: JSON.stringify({ name: memoName, state: 'ARCHIVED' }),
contentType: 'application/json', contentType: 'application/json',
dataType: 'json', dataType: 'json',
headers: { Authorization: 'Bearer ' + info.apiTokens } headers: { Authorization: 'Bearer ' + info.apiTokens }
@@ -458,24 +463,35 @@
return return
} }
const uploadedItems = Array.isArray(payload.resourceIdList) ? payload.resourceIdList : []
const attachmentReferences = uploadedItems
.filter(function (item) {
return item && typeof item.name === 'string' && item.name.indexOf('attachments/') === 0
})
.map(function (item) {
return { name: item.name }
})
const canCreateWithAttachments = flavor === 'modern' && attachmentReferences.length === uploadedItems.length
requestJson({ requestJson({
url: info.apiUrl + 'api/v1/memos', url: info.apiUrl + 'api/v1/memos',
type: 'POST', type: 'POST',
data: JSON.stringify({ data: JSON.stringify({
content: payload.content, content: payload.content,
visibility: payload.visibility visibility: payload.visibility,
...(canCreateWithAttachments ? { attachments: attachmentReferences } : {})
}), }),
contentType: 'application/json', contentType: 'application/json',
dataType: 'json', dataType: 'json',
headers: { Authorization: 'Bearer ' + info.apiTokens } headers: { Authorization: 'Bearer ' + info.apiTokens }
}, function (data) { }, function (data) {
const createdName = data && data.name ? data.name : data && data.memo && data.memo.name ? data.memo.name : '' const createdName = data && data.name ? data.name : data && data.memo && data.memo.name ? data.memo.name : ''
const resources = Array.isArray(payload.resourceIdList) ? payload.resourceIdList : [] const resources = uploadedItems
if (!createdName) { if (!createdName) {
if (success) success(data) if (success) success(data)
return return
} }
if (resources.length === 0) { if (resources.length === 0 || canCreateWithAttachments) {
getMemo(createdName, success, fail) getMemo(createdName, success, fail)
return return
} }
@@ -497,7 +513,7 @@
return { return {
flavor: flavor, flavor: flavor,
needsAuthenticatedImagePreview: function () { needsAuthenticatedImagePreview: function () {
return flavor === FLAVOR_V020_V021 return true
}, },
listTags: listTags, listTags: listTags,
searchMemos: searchMemos, searchMemos: searchMemos,
@@ -518,4 +534,4 @@
probeFlavor: probeFlavor, probeFlavor: probeFlavor,
resolve: resolve resolve: resolve
} }
})(window) })(window)
+47 -108
View File
@@ -151,10 +151,53 @@
return copy return copy
} }
function uploadResource() {
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 (xhr) {
if (!xhr || xhr.status !== 400) {
if (onFail) onFail(xhr)
return
}
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 (xhr2) {
if (onFail) onFail(xhr2)
})
})
}
// Attachment visibility was removed in Memos v0.26. Send the current
// attachment shape first and retain the resource endpoint as a legacy fallback.
global.$ global.$
.ajax({ .ajax({
url: urlAttachments, url: urlAttachments,
data: JSON.stringify(payload), data: JSON.stringify(stripVisibility(payload)),
type: 'POST', type: 'POST',
cache: false, cache: false,
processData: false, processData: false,
@@ -166,115 +209,11 @@
onSuccess(data, 'attachments') onSuccess(data, 'attachments')
}) })
.fail(function (xhr) { .fail(function (xhr) {
if (xhr && xhr.status === 400) { if (isNotFoundLikeXhr(xhr)) {
global.$ uploadResource()
.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 return
} }
if (onFail) onFail(xhr)
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)
})
}) })
} }
+56 -9
View File
@@ -201,22 +201,69 @@
function getTagSuggestion(info, success, fail) { function getTagSuggestion(info, success, fail) {
const headers = { Authorization: 'Bearer ' + info.apiTokens } const headers = { Authorization: 'Bearer ' + info.apiTokens }
function normalizeTagList(data) {
const list = Array.isArray(data) ? data : Array.isArray(data.tags) ? data.tags : []
return 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)
}
function normalizeSuggestionList(data) {
const list = Array.isArray(data) ? data : []
return list
.map(function (s) {
return String(s).replace(/^#/, '').trim()
})
.filter(Boolean)
}
function mergeUnique(a, b) {
return [...new Set((a || []).concat(b || []))]
}
let suggestionList = []
let listList = []
let pending = 2
function finish() {
if (--pending !== 0) return
if (success) success(mergeUnique(suggestionList, listList))
}
requestGet( requestGet(
info.apiUrl + 'api/v1/tag/suggestion', info.apiUrl + 'api/v1/tag/suggestion',
headers, headers,
function (data) { function (data) {
const list = Array.isArray(data) ? data : [] suggestionList = normalizeSuggestionList(data)
const out = list finish()
.map(function (s) { },
return String(s).replace(/^#/, '').trim() function (xhr) {
}) // Ignore failures from the suggestion endpoint; the list endpoint may still work.
.filter(Boolean) if (xhr && xhr.status !== 404 && xhr.status !== 405 && fail) fail(xhr)
if (success) success(out) else finish()
}
)
requestGet(
info.apiUrl + 'api/v1/tag',
headers,
function (data) {
listList = normalizeTagList(data)
finish()
}, },
function (xhr) { function (xhr) {
// Some forks might only expose list.
if (isNotFoundLikeXhr(xhr)) { if (isNotFoundLikeXhr(xhr)) {
getTagList(info, success, fail) finish()
return return
} }
if (fail) fail(xhr) if (fail) fail(xhr)
+1
View File
@@ -0,0 +1 @@
!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n(require("dayjs")):"function"==typeof define&&define.amd?define(["dayjs"],n):(e="undefined"!=typeof globalThis?globalThis:e||self).dayjs_locale_de=n(e.dayjs)}(this,(function(e){"use strict";function n(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var t=n(e),a={name:"de",weekdays:"Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag".split("_"),weekdaysShort:"So._Mo._Di._Mi._Do._Fr._Sa.".split("_"),weekdaysMin:"So_Mo_Di_Mi_Do_Fr_Sa".split("_"),months:"Januar_Februar_März_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember".split("_"),monthsShort:"Jan._Feb._März_Apr._Mai_Juni_Juli_Aug._Sept._Okt._Nov._Dez.".split("_"),weekStart:1,formats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY HH:mm",LLLL:"dddd, D. MMMM YYYY HH:mm"},relativeTime:{future:"in %s",past:"vor %s",s:"ein paar Sekunden",m:"einer Minute",mm:"%d Minuten",h:"einer Stunde",hh:"%d Stunden",d:"einem Tag",dd:"%d Tagen",M:"einem Monat",MM:"%d Monaten",y:"einem Jahr",yy:"%d Jahren"},ordinal:function(e){return e+"."}};return t.default.locale(a,null,!0),a}));
+1
View File
@@ -0,0 +1 @@
!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n(require("dayjs")):"function"==typeof define&&define.amd?define(["dayjs"],n):(e="undefined"!=typeof globalThis?globalThis:e||self).dayjs_locale_es=n(e.dayjs)}(this,(function(e){"use strict";function n(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var t=n(e),a={name:"es",weekdays:"domingo_lunes_martes_miércoles_jueves_viernes_sábado".split("_"),weekdaysShort:"dom._lun._mar._mié._jue._vie._sáb.".split("_"),weekdaysMin:"do_lu_ma_mi_ju_vi_sá".split("_"),months:"enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre".split("_"),monthsShort:"ene._feb._mar._abr._may._jun._jul._ago._sept._oct._nov._dic.".split("_"),weekStart:1,formats:{LT:"H:mm",LTS:"H:mm:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY H:mm",LLLL:"dddd, D [de] MMMM [de] YYYY H:mm"},relativeTime:{future:"en %s",past:"hace %s",s:"unos segundos",m:"un minuto",mm:"%d minutos",h:"una hora",hh:"%d horas",d:"un día",dd:"%d días",M:"un mes",MM:"%d meses",y:"un año",yy:"%d años"},ordinal:function(e){return e+"º"}};return t.default.locale(a,null,!0),a}));
+1
View File
@@ -0,0 +1 @@
!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n(require("dayjs")):"function"==typeof define&&define.amd?define(["dayjs"],n):(e="undefined"!=typeof globalThis?globalThis:e||self).dayjs_locale_fr=n(e.dayjs)}(this,(function(e){"use strict";function n(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var t=n(e),a={name:"fr",weekdays:"dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi".split("_"),weekdaysShort:"dim._lun._mar._mer._jeu._ven._sam.".split("_"),weekdaysMin:"di_lu_ma_me_je_ve_sa".split("_"),months:"janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre".split("_"),monthsShort:"janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.".split("_"),weekStart:1,formats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},relativeTime:{future:"dans %s",past:"il y a %s",s:"quelques secondes",m:"une minute",mm:"%d minutes",h:"une heure",hh:"%d heures",d:"un jour",dd:"%d jours",M:"un mois",MM:"%d mois",y:"un an",yy:"%d ans"},ordinal:function(e){return e+(1===e?"er":"")}};return t.default.locale(a,null,!0),a}));
+7 -1
View File
@@ -1,6 +1,6 @@
const UI_LANGUAGE_STORAGE_KEY = 'uiLanguage' const UI_LANGUAGE_STORAGE_KEY = 'uiLanguage'
const SUPPORTED_UI_LANGUAGES = new Set(['auto', 'en', 'zh_CN', 'ja', 'ko']) const SUPPORTED_UI_LANGUAGES = new Set(['auto', 'en', 'de', 'fr', 'es', 'zh_CN', 'ja', 'ko'])
function normalizeUiLanguage(value) { function normalizeUiLanguage(value) {
const lang = String(value || 'auto') const lang = String(value || 'auto')
@@ -49,6 +49,9 @@ let overrideMessages = null
function getLanguageToggleLabel(lang) { function getLanguageToggleLabel(lang) {
if (lang === 'en') return 'EN' if (lang === 'en') return 'EN'
if (lang === 'de') return 'DE'
if (lang === 'fr') return 'FR'
if (lang === 'es') return 'ES'
if (lang === 'zh_CN') return '中' if (lang === 'zh_CN') return '中'
if (lang === 'ja') return '日' if (lang === 'ja') return '日'
if (lang === 'ko') return '한' if (lang === 'ko') return '한'
@@ -132,6 +135,9 @@ function applyStaticI18n() {
// Language switcher // Language switcher
setText('langOptionAuto', 'langAuto') setText('langOptionAuto', 'langAuto')
setText('langOptionEn', 'langEnglish') setText('langOptionEn', 'langEnglish')
setText('langOptionDe', 'langGerman')
setText('langOptionFr', 'langFrench')
setText('langOptionEs', 'langSpanish')
setText('langOptionZhCN', 'langChineseSimplified') setText('langOptionZhCN', 'langChineseSimplified')
setText('langOptionJa', 'langJapanese') setText('langOptionJa', 'langJapanese')
setText('langOptionKo', 'langKorean') setText('langOptionKo', 'langKorean')
+75 -11
View File
@@ -162,6 +162,21 @@ function msg(key) {
function applyDayjsLocaleByUiLanguage(uiLang) { function applyDayjsLocaleByUiLanguage(uiLang) {
const lang = String(uiLang || 'auto') const lang = String(uiLang || 'auto')
if (lang === 'de') {
dayjs.locale('de')
return
}
if (lang === 'fr') {
dayjs.locale('fr')
return
}
if (lang === 'es') {
dayjs.locale('es')
return
}
if (lang === 'zh_CN') { if (lang === 'zh_CN') {
dayjs.locale('zh-cn') dayjs.locale('zh-cn')
return return
@@ -184,6 +199,18 @@ function applyDayjsLocaleByUiLanguage(uiLang) {
// auto: best-effort infer from browser UI language // auto: best-effort infer from browser UI language
const ui = String(chrome.i18n.getUILanguage ? chrome.i18n.getUILanguage() : '').toLowerCase() const ui = String(chrome.i18n.getUILanguage ? chrome.i18n.getUILanguage() : '').toLowerCase()
if (ui.startsWith('de')) {
dayjs.locale('de')
return
}
if (ui.startsWith('fr')) {
dayjs.locale('fr')
return
}
if (ui.startsWith('es')) {
dayjs.locale('es')
return
}
if (ui.startsWith('zh')) { if (ui.startsWith('zh')) {
dayjs.locale('zh-cn') dayjs.locale('zh-cn')
return return
@@ -294,6 +321,8 @@ function getMemoUid(memo) {
if (!memo) return '' if (!memo) return ''
if (memo.uid != null && memo.uid !== '') return String(memo.uid) if (memo.uid != null && memo.uid !== '') return String(memo.uid)
if (typeof memo.name === 'string' && memo.name) return memo.name.split('/').pop() if (typeof memo.name === 'string' && memo.name) return memo.name.split('/').pop()
// v0.18.2 only exposes a numeric id.
if (memo.id != null && memo.id !== '') return String(memo.id)
return '' return ''
} }
@@ -453,6 +482,13 @@ function buildV1ResourceStreamUrl(info, resource) {
} }
if (root && !root.endsWith('/')) root += '/' if (root && !root.endsWith('/')) root += '/'
if (resource.externalLink) return resource.externalLink
const isV023 = info.apiFlavor === 'v023'
const name = typeof resource.name === 'string' ? resource.name.trim() : ''
const filename = typeof resource.filename === 'string' ? resource.filename : ''
function isImageResource(r) { function isImageResource(r) {
if (!r) return false if (!r) return false
const t = typeof r.type === 'string' ? r.type.toLowerCase() : '' const t = typeof r.type === 'string' ? r.type.toLowerCase() : ''
@@ -461,6 +497,22 @@ function buildV1ResourceStreamUrl(info, resource) {
return /\.(png|jpe?g|gif|webp|bmp|svg|avif|heic)$/.test(fn) return /\.(png|jpe?g|gif|webp|bmp|svg|avif|heic)$/.test(fn)
} }
// v0.23+: resources are served as static files via /file/{name}/{filename}.
if (isV023) {
if (resource.externalLink) return resource.externalLink
const resourceName = name.indexOf('resources/') === 0 ? name : ''
if (resourceName && filename) {
const url = root + 'file/' + resourceName + '/' + filename.split('/').map(encodeURIComponent).join('/')
return isImageResource(resource) ? url + '?thumbnail=true' : url
}
return ''
}
if (name.indexOf('attachments/') === 0 && filename) {
const url = root + 'file/' + name.split('/').map(encodeURIComponent).join('/') + '/' + encodeURIComponent(filename)
return isImageResource(resource) ? url + '?thumbnail=true' : url
}
function isProbablyUid(s) { function isProbablyUid(s) {
if (typeof s !== 'string') return false if (typeof s !== 'string') return false
const v = s.trim() const v = s.trim()
@@ -480,6 +532,11 @@ function buildV1ResourceStreamUrl(info, resource) {
const uid = typeof uidRaw === 'string' ? uidRaw : uidRaw != null ? String(uidRaw) : '' const uid = typeof uidRaw === 'string' ? uidRaw : uidRaw != null ? String(uidRaw) : ''
if (uid.trim() !== '') return buildStreamUrl(uid.trim()) if (uid.trim() !== '') return buildStreamUrl(uid.trim())
// Fallback for older resource shapes.
// In some memo payloads, the uid may appear as `name` directly.
// Example: name="ETU6hjuR..." should map to /o/r/:uid, not /file/:name/:filename.
if (isProbablyUid(name)) return buildStreamUrl(name.trim())
// Legacy versions (e.g. v0.18) may only expose numeric `id` without `uid/name`. // Legacy versions (e.g. v0.18) may only expose numeric `id` without `uid/name`.
const idRaw = resource.id != null ? resource.id : resource.ID != null ? resource.ID : resource.Id const idRaw = resource.id != null ? resource.id : resource.ID != null ? resource.ID : resource.Id
const id = typeof idRaw === 'number' && Number.isFinite(idRaw) const id = typeof idRaw === 'number' && Number.isFinite(idRaw)
@@ -489,14 +546,7 @@ function buildV1ResourceStreamUrl(info, resource) {
: '' : ''
if (id) return buildStreamUrl(id) if (id) return buildStreamUrl(id)
// Fallback for older resource shapes. const fileId = resource.publicId || filename
const name = typeof resource.name === 'string' ? resource.name : ''
// In some memo payloads, the uid may appear as `name` directly.
// Example: name="ETU6hjuR..." should map to /o/r/:uid, not /file/:name/:filename.
if (isProbablyUid(name)) return buildStreamUrl(name.trim())
const fileId = resource.publicId || resource.filename
if (name && fileId) return root + 'file/' + name + '/' + fileId if (name && fileId) return root + 'file/' + name + '/' + fileId
return '' return ''
} }
@@ -886,11 +936,17 @@ $('#opensite').click(function () {
// 0.23.1版本 GET api/v1/{parent}/tags 接口已移除,参考 https://github.com/usememos/memos/issues/4161 // 0.23.1版本 GET api/v1/{parent}/tags 接口已移除,参考 https://github.com/usememos/memos/issues/4161
$('#tags').click(function () { $('#tags').click(function () {
get_info(function (info) { get_info(function (info) {
//console.log('[memos-bber] tags click info:', info)
if (info.apiUrl) { if (info.apiUrl) {
var tagDom = ""; var tagDom = "";
const adapter = getApiAdapter(info) const adapter = getApiAdapter(info)
if (!adapter) {
//console.warn('[memos-bber] no adapter for tags')
return
}
const renderTags = function (tags) { const renderTags = function (tags) {
//console.log('[memos-bber] tags result:', tags)
const uniTags = [...new Set((Array.isArray(tags) ? tags : []).filter(Boolean))] const uniTags = [...new Set((Array.isArray(tags) ? tags : []).filter(Boolean))]
$.each(uniTags, function (_, tag) { $.each(uniTags, function (_, tag) {
tagDom += '<span class="item-container">#' + tag + '</span>'; tagDom += '<span class="item-container">#' + tag + '</span>';
@@ -899,7 +955,8 @@ $('#tags').click(function () {
$("#taglist").html(tagDom).slideToggle(500) $("#taglist").html(tagDom).slideToggle(500)
} }
adapter.listTags(renderTags, function () { adapter.listTags(renderTags, function (err) {
//console.error('[memos-bber] tags failed:', err)
$.message({ message: msg('placeApiUrl') }) $.message({ message: msg('placeApiUrl') })
}) })
} else { } else {
@@ -1017,17 +1074,24 @@ $('#search').click(function () {
$('#random').click(function () { $('#random').click(function () {
get_info(function (info) { get_info(function (info) {
//console.log('[memos-bber] random click info:', info)
if (info.status) { if (info.status) {
$("#randomlist").html('').hide() $("#randomlist").html('').hide()
const adapter = getApiAdapter(info) const adapter = getApiAdapter(info)
if (!adapter) {
//console.warn('[memos-bber] no adapter for random')
return
}
adapter.listRandomMemos( adapter.listRandomMemos(
function (memos) { function (memos) {
console.log('[memos-bber] random result count:', memos && memos.length)
let randomNum = Math.floor(Math.random() * (memos.length)); let randomNum = Math.floor(Math.random() * (memos.length));
var randomData = memos[randomNum] var randomData = memos[randomNum]
randDom(randomData) randDom(randomData)
}, },
function () { function (err) {
//console.error('[memos-bber] random failed:', err)
$.message({ message: msg('placeApiUrl') }) $.message({ message: msg('placeApiUrl') })
} }
) )
@@ -1256,4 +1320,4 @@ function sendText(preparedContent) {
}) })
} }
}) })
} }
+1 -1
View File
@@ -2,7 +2,7 @@
"manifest_version": 3, "manifest_version": 3,
"name": "__MSG_extName__", "name": "__MSG_extName__",
"default_locale": "en", "default_locale": "en",
"version": "2026.04.24", "version": "2026.08.05",
"action": { "action": {
"default_popup": "popup.html", "default_popup": "popup.html",
"default_icon": "assets/logo_24x24.png", "default_icon": "assets/logo_24x24.png",
+6
View File
@@ -25,6 +25,9 @@
<div id="langMenu" class="lang-menu hidden" role="menu" aria-labelledby="langToggle"> <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="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="langOptionEn" class="lang-menu-item" type="button" data-lang="en" role="menuitemradio" aria-checked="false"></button>
<button id="langOptionDe" class="lang-menu-item" type="button" data-lang="de" role="menuitemradio" aria-checked="false"></button>
<button id="langOptionFr" class="lang-menu-item" type="button" data-lang="fr" role="menuitemradio" aria-checked="false"></button>
<button id="langOptionEs" class="lang-menu-item" type="button" data-lang="es" 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="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="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> <button id="langOptionKo" class="lang-menu-item" type="button" data-lang="ko" role="menuitemradio" aria-checked="false"></button>
@@ -180,6 +183,9 @@
<script src="../js/jquery.min.js"></script> <script src="../js/jquery.min.js"></script>
<script src="../js/message.js"></script> <script src="../js/message.js"></script>
<script src="../js/dayjs.min.js"></script> <script src="../js/dayjs.min.js"></script>
<script src="../js/de.js"></script>
<script src="../js/fr.js"></script>
<script src="../js/es.js"></script>
<script src="../js/zh-cn.js"></script> <script src="../js/zh-cn.js"></script>
<script src="../js/ja.js"></script> <script src="../js/ja.js"></script>
<script src="../js/ko.js"></script> <script src="../js/ko.js"></script>
+197
View File
@@ -0,0 +1,197 @@
{
"extName": {
"message": "Memos"
},
"actionTitle": {
"message": "An Memos senden"
},
"extDescription": {
"message": "memos: Ein leichtgewichtiges, selbst gehostetes Memo-Hub."
},
"sendTo": {
"message": "An Memos senden \"%s\""
},
"sendLinkTo": {
"message": "Link an Memos senden"
},
"sendImageTo": {
"message": "Bild an Memos senden"
},
"saveBtn": {
"message": "Speichern"
},
"supportedMemosVersion": {
"message": "Kompatibel mit Memos v0.15.0 - 0.30.x"
},
"settingsConnectionTitle": {
"message": "Verbindung"
},
"settingsConnectionDesc": {
"message": "Konfiguriere die URL der Memos-Instanz und das Zugriffstoken."
},
"settingsPostingTitle": {
"message": "Verfassen"
},
"settingsPostingDesc": {
"message": "Standardtext für Uploads ohne Text"
},
"placeApiUrl": {
"message": "URL der Memos-Instanz"
},
"placeApiTokens": {
"message": "Memos-Zugriffstoken"
},
"placeContent": {
"message": "Was geht dir durch den Kopf..."
},
"lockPrivate": {
"message": "Privat"
},
"lockProtected": {
"message": "Geschützt"
},
"lockPublic": {
"message": "Öffentlich"
},
"submitBtn": {
"message": "Speichern"
},
"placeHideInput": {
"message": "Standardname für den Tag \"Privat\""
},
"placeShowInput": {
"message": "Standardname für den Tag \"Für alle sichtbar\""
},
"placeAttachmentOnlyDefaultText": {
"message": "Standardtext für Uploads ohne Text (leer lassen, um den eingebauten Text zu verwenden)"
},
"uploadedListTitle": {
"message": "Hochgeladene Dateien, zum Neuordnen ziehen"
},
"uploadedListEmpty": {
"message": "Keine hochgeladenen Dateien"
},
"tipReorder": {
"message": "Zum Neuordnen ziehen"
},
"tipDeleteAttachment": {
"message": "Löschen"
},
"attachmentDeleteSuccess": {
"message": "Gelöscht"
},
"attachmentDeleteFailed": {
"message": "Löschen fehlgeschlagen 😭"
},
"picDrag": {
"message": "Bild zum Hochladen hierher ziehen"
},
"picCancelDrag": {
"message": "Upload abbrechen"
},
"picUploading": {
"message": "Bild wird hochgeladen..."
},
"picSuccess": {
"message": "Upload abgeschlossen"
},
"picFailed": {
"message": "Upload fehlgeschlagen"
},
"picPending": {
"message": "Bild wird noch hochgeladen"
},
"saveSuccess": {
"message": "Einstellungen gespeichert!"
},
"searchNow": {
"message": "Wonach suchst du?"
},
"searchNone": {
"message": "Versuche es mit einem anderen Wort!"
},
"archiveSuccess": {
"message": "Archiviert 😊"
},
"archiveFailed": {
"message": "Archivieren fehlgeschlagen 😭"
},
"getTabFailed": {
"message": "Tab konnte nicht gelesen werden 😭"
},
"memoUploading": {
"message": "Wird gesendet"
},
"memoSuccess": {
"message": "Erfolgreich! 😊"
},
"memoFailed": {
"message": "Fehlgeschlagen! 😭"
},
"invalidToken": {
"message": "Ungültiges Token oder ungültige URL 😭"
},
"tipOpenSite": {
"message": "Memos öffnen"
},
"tipSettings": {
"message": "Einstellungen"
},
"tipTags": {
"message": "Tag einfügen"
},
"tipTodo": {
"message": "Todo einfügen"
},
"tipUpload": {
"message": "Datei hochladen"
},
"tipLink": {
"message": "Link des aktuellen Tabs einfügen"
},
"tipRandom": {
"message": "Zufälliges Memo"
},
"tipSearch": {
"message": "Suchen"
},
"tipVisibility": {
"message": "Sichtbarkeit"
},
"tipSend": {
"message": "Senden (Strg/⌘+Enter)"
},
"tipLanguage": {
"message": "Sprache"
},
"langAuto": {
"message": "Automatisch"
},
"langEnglish": {
"message": "English"
},
"langGerman": {
"message": "Deutsch"
},
"langFrench": {
"message": "Français"
},
"langSpanish": {
"message": "Español"
},
"langChineseSimplified": {
"message": "简体中文"
},
"langJapanese": {
"message": "日本語"
},
"langKorean": {
"message": "한국어"
},
"tipFullscreen": {
"message": "Vollbild-Editor öffnen"
},
"tipResize": {
"message": "Zum Ändern der Größe ziehen (Minimum: Standardgröße)"
}
}
+10 -1
View File
@@ -21,7 +21,7 @@
"message": "Save" "message": "Save"
}, },
"supportedMemosVersion": { "supportedMemosVersion": {
"message": "Compatible with Memos v0.15.0 - 0.27.x" "message": "Compatible with Memos v0.15.0 - 0.30.x"
}, },
"settingsConnectionTitle": { "settingsConnectionTitle": {
"message": "Connection" "message": "Connection"
@@ -170,6 +170,15 @@
"langEnglish": { "langEnglish": {
"message": "English" "message": "English"
}, },
"langGerman": {
"message": "Deutsch"
},
"langFrench": {
"message": "Français"
},
"langSpanish": {
"message": "Español"
},
"langChineseSimplified": { "langChineseSimplified": {
"message": "简体中文" "message": "简体中文"
}, },
+197
View File
@@ -0,0 +1,197 @@
{
"extName": {
"message": "Memos"
},
"actionTitle": {
"message": "Enviar a Memos"
},
"extDescription": {
"message": "memos: un centro de notas ligero y autoalojado."
},
"sendTo": {
"message": "Enviar a Memos \"%s\""
},
"sendLinkTo": {
"message": "Enviar enlace a Memos"
},
"sendImageTo": {
"message": "Enviar imagen a Memos"
},
"saveBtn": {
"message": "Guardar"
},
"supportedMemosVersion": {
"message": "Compatible con Memos v0.15.0 - 0.30.x"
},
"settingsConnectionTitle": {
"message": "Conexión"
},
"settingsConnectionDesc": {
"message": "Configura la URL del sitio de Memos y el token de acceso."
},
"settingsPostingTitle": {
"message": "Publicación"
},
"settingsPostingDesc": {
"message": "Texto predeterminado para envíos solo con adjuntos"
},
"placeApiUrl": {
"message": "URL del sitio de Memos"
},
"placeApiTokens": {
"message": "Tokens de acceso de Memos"
},
"placeContent": {
"message": "¿Qué tienes en mente...?"
},
"lockPrivate": {
"message": "Privado"
},
"lockProtected": {
"message": "Protegido"
},
"lockPublic": {
"message": "Público"
},
"submitBtn": {
"message": "Guardar"
},
"placeHideInput": {
"message": "Nombre predeterminado de la etiqueta \"Privado\""
},
"placeShowInput": {
"message": "Nombre predeterminado de la etiqueta \"Visible para todos\""
},
"placeAttachmentOnlyDefaultText": {
"message": "Texto predeterminado para envíos solo con adjuntos (déjalo vacío para usar el texto integrado)"
},
"uploadedListTitle": {
"message": "Archivos subidos, arrastra para reordenar"
},
"uploadedListEmpty": {
"message": "No hay archivos subidos"
},
"tipReorder": {
"message": "Arrastra para reordenar"
},
"tipDeleteAttachment": {
"message": "Eliminar"
},
"attachmentDeleteSuccess": {
"message": "Eliminado"
},
"attachmentDeleteFailed": {
"message": "Error al eliminar 😭"
},
"picDrag": {
"message": "Arrastra la imagen para subirla"
},
"picCancelDrag": {
"message": "Cancelar subida"
},
"picUploading": {
"message": "Subiendo la imagen..."
},
"picSuccess": {
"message": "Subida completada"
},
"picFailed": {
"message": "La subida falló"
},
"picPending": {
"message": "La imagen aún se está subiendo"
},
"saveSuccess": {
"message": "¡Configuración guardada!"
},
"searchNow": {
"message": "¿Qué estás buscando?"
},
"searchNone": {
"message": "¡Prueba con otra palabra!"
},
"archiveSuccess": {
"message": "Archivado 😊"
},
"archiveFailed": {
"message": "Error al archivar 😭"
},
"getTabFailed": {
"message": "Error al obtener la pestaña 😭"
},
"memoUploading": {
"message": "Enviando"
},
"memoSuccess": {
"message": "¡Éxito! 😊"
},
"memoFailed": {
"message": "¡Falló! 😭"
},
"invalidToken": {
"message": "Token o URL no válidos 😭"
},
"tipOpenSite": {
"message": "Abrir Memos"
},
"tipSettings": {
"message": "Configuración"
},
"tipTags": {
"message": "Insertar etiqueta"
},
"tipTodo": {
"message": "Insertar tarea"
},
"tipUpload": {
"message": "Subir archivo"
},
"tipLink": {
"message": "Insertar enlace de la pestaña actual"
},
"tipRandom": {
"message": "Memo aleatorio"
},
"tipSearch": {
"message": "Buscar"
},
"tipVisibility": {
"message": "Visibilidad"
},
"tipSend": {
"message": "Enviar (Ctrl/⌘+Enter)"
},
"tipLanguage": {
"message": "Idioma"
},
"langAuto": {
"message": "Auto"
},
"langEnglish": {
"message": "English"
},
"langGerman": {
"message": "Deutsch"
},
"langFrench": {
"message": "Français"
},
"langSpanish": {
"message": "Español"
},
"langChineseSimplified": {
"message": "简体中文"
},
"langJapanese": {
"message": "日本語"
},
"langKorean": {
"message": "한국어"
},
"tipFullscreen": {
"message": "Abrir editor en pantalla completa"
},
"tipResize": {
"message": "Arrastra para cambiar el tamaño (mín.: tamaño predeterminado)"
}
}
+197
View File
@@ -0,0 +1,197 @@
{
"extName": {
"message": "Memos"
},
"actionTitle": {
"message": "Envoyer à Memos"
},
"extDescription": {
"message": "memos : un hub de mémos léger et auto-hébergé."
},
"sendTo": {
"message": "Envoyer à Memos \"%s\""
},
"sendLinkTo": {
"message": "Envoyer le lien vers Memos"
},
"sendImageTo": {
"message": "Envoyer l'image vers Memos"
},
"saveBtn": {
"message": "Enregistrer"
},
"supportedMemosVersion": {
"message": "Compatible avec Memos v0.15.0 - 0.30.x"
},
"settingsConnectionTitle": {
"message": "Connexion"
},
"settingsConnectionDesc": {
"message": "Configurez l'URL du site Memos et le jeton d'accès."
},
"settingsPostingTitle": {
"message": "Publication"
},
"settingsPostingDesc": {
"message": "Texte par défaut pour les envois avec pièce jointe uniquement"
},
"placeApiUrl": {
"message": "URL du site Memos"
},
"placeApiTokens": {
"message": "Jetons d'accès Memos"
},
"placeContent": {
"message": "À quoi pensez-vous..."
},
"lockPrivate": {
"message": "Privé"
},
"lockProtected": {
"message": "Protégé"
},
"lockPublic": {
"message": "Public"
},
"submitBtn": {
"message": "Enregistrer"
},
"placeHideInput": {
"message": "Nom par défaut du tag \"Privé\""
},
"placeShowInput": {
"message": "Nom par défaut du tag \"Visible par tous\""
},
"placeAttachmentOnlyDefaultText": {
"message": "Texte par défaut pour les envois avec pièce jointe uniquement (laisser vide pour utiliser le texte intégré)"
},
"uploadedListTitle": {
"message": "Fichiers téléversés, faites glisser pour réorganiser"
},
"uploadedListEmpty": {
"message": "Aucun fichier téléversé"
},
"tipReorder": {
"message": "Glisser pour réorganiser"
},
"tipDeleteAttachment": {
"message": "Supprimer"
},
"attachmentDeleteSuccess": {
"message": "Supprimé"
},
"attachmentDeleteFailed": {
"message": "Échec de la suppression 😭"
},
"picDrag": {
"message": "Faites glisser l'image pour la téléverser"
},
"picCancelDrag": {
"message": "Annuler le téléversement"
},
"picUploading": {
"message": "Téléversement de l'image..."
},
"picSuccess": {
"message": "Téléversement terminé"
},
"picFailed": {
"message": "Échec du téléversement"
},
"picPending": {
"message": "Le téléversement de l'image est en cours"
},
"saveSuccess": {
"message": "Paramètres enregistrés !"
},
"searchNow": {
"message": "Que recherchez-vous ?"
},
"searchNone": {
"message": "Essayez un autre mot !"
},
"archiveSuccess": {
"message": "Archivé 😊"
},
"archiveFailed": {
"message": "Échec de l'archivage 😭"
},
"getTabFailed": {
"message": "Échec de la récupération de l'onglet 😭"
},
"memoUploading": {
"message": "Envoi en cours"
},
"memoSuccess": {
"message": "Succès ! 😊"
},
"memoFailed": {
"message": "Échec ! 😭"
},
"invalidToken": {
"message": "Jeton ou URL invalide 😭"
},
"tipOpenSite": {
"message": "Ouvrir Memos"
},
"tipSettings": {
"message": "Paramètres"
},
"tipTags": {
"message": "Insérer un tag"
},
"tipTodo": {
"message": "Insérer une tâche"
},
"tipUpload": {
"message": "Téléverser un fichier"
},
"tipLink": {
"message": "Insérer le lien de l'onglet actuel"
},
"tipRandom": {
"message": "Mémo aléatoire"
},
"tipSearch": {
"message": "Rechercher"
},
"tipVisibility": {
"message": "Visibilité"
},
"tipSend": {
"message": "Envoyer (Ctrl/⌘+Entrée)"
},
"tipLanguage": {
"message": "Langue"
},
"langAuto": {
"message": "Auto"
},
"langEnglish": {
"message": "English"
},
"langGerman": {
"message": "Deutsch"
},
"langFrench": {
"message": "Français"
},
"langSpanish": {
"message": "Español"
},
"langChineseSimplified": {
"message": "简体中文"
},
"langJapanese": {
"message": "日本語"
},
"langKorean": {
"message": "한국어"
},
"tipFullscreen": {
"message": "Ouvrir l'éditeur en plein écran"
},
"tipResize": {
"message": "Glisser pour redimensionner (min. : taille par défaut)"
}
}
+10 -1
View File
@@ -21,7 +21,7 @@
"message": "保存" "message": "保存"
}, },
"supportedMemosVersion": { "supportedMemosVersion": {
"message": "Memos v0.15.0 - 0.27.x に対応" "message": "Memos v0.15.0 - 0.30.x に対応"
}, },
"settingsConnectionTitle": { "settingsConnectionTitle": {
"message": "接続設定" "message": "接続設定"
@@ -170,6 +170,15 @@
"langEnglish": { "langEnglish": {
"message": "English" "message": "English"
}, },
"langGerman": {
"message": "Deutsch"
},
"langFrench": {
"message": "Français"
},
"langSpanish": {
"message": "Español"
},
"langChineseSimplified": { "langChineseSimplified": {
"message": "简体中文" "message": "简体中文"
}, },
+10 -1
View File
@@ -21,7 +21,7 @@
"message": "저장" "message": "저장"
}, },
"supportedMemosVersion": { "supportedMemosVersion": {
"message": "Memos v0.15.0 - 0.27.x 호환" "message": "Memos v0.15.0 - 0.30.x 호환"
}, },
"settingsConnectionTitle": { "settingsConnectionTitle": {
"message": "연결 설정" "message": "연결 설정"
@@ -170,6 +170,15 @@
"langEnglish": { "langEnglish": {
"message": "English" "message": "English"
}, },
"langGerman": {
"message": "Deutsch"
},
"langFrench": {
"message": "Français"
},
"langSpanish": {
"message": "Español"
},
"langChineseSimplified": { "langChineseSimplified": {
"message": "简体中文" "message": "简体中文"
}, },
+10 -1
View File
@@ -21,7 +21,7 @@
"message": "保存" "message": "保存"
}, },
"supportedMemosVersion": { "supportedMemosVersion": {
"message": "兼容 Memos v0.15.0 - 0.27.x" "message": "兼容 Memos v0.15.0 - 0.30.x"
}, },
"settingsConnectionTitle": { "settingsConnectionTitle": {
"message": "连接设置" "message": "连接设置"
@@ -170,6 +170,15 @@
"langEnglish": { "langEnglish": {
"message": "English" "message": "English"
}, },
"langGerman": {
"message": "Deutsch"
},
"langFrench": {
"message": "Français"
},
"langSpanish": {
"message": "Español"
},
"langChineseSimplified": { "langChineseSimplified": {
"message": "简体中文" "message": "简体中文"
}, },
+42 -26
View File
@@ -187,6 +187,7 @@
function normalizeUploadedItem(entity, fallbackFilename) { function normalizeUploadedItem(entity, fallbackFilename) {
if (!entity) return null if (!entity) return null
entity = entity.attachment || entity.resource || entity
const inferredId = (function () { const inferredId = (function () {
const value = entity.id != null ? entity.id : entity.ID != null ? entity.ID : entity.Id 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 === 'number' && Number.isFinite(value)) return Math.floor(value)
@@ -204,7 +205,11 @@
name: name, name: name,
filename: entity.filename || fallbackFilename || name, filename: entity.filename || fallbackFilename || name,
createTime: entity.createTime || entity.createdTs || entity.createdAt, createTime: entity.createTime || entity.createdTs || entity.createdAt,
type: entity.type type: entity.type,
uid: entity.uid,
size: entity.size,
memo: entity.memo,
externalLink: entity.externalLink
} }
} }
@@ -244,10 +249,10 @@
} }
if (flavor === 'v023' && global.MemosApiV023) { if (flavor === 'v023' && global.MemosApiV023) {
const filterExpr = global.MemosApiV023.buildFilter({ // v0.23 filter requires `creator == "users/{numericId}"`. Some stored userids are
rowStatus: 'NORMAL', // usernames, which causes 400. Also `row_status` can cause 400 on some builds.
creator: 'users/' + info.userid // Let the server identify the current user from the token and return all visible memos.
}) const filterExpr = global.MemosApiV023.buildFilter({})
global.MemosApiV023.listMemos( global.MemosApiV023.listMemos(
info, info,
{ pageSize: 1000, filterExpr: filterExpr }, { pageSize: 1000, filterExpr: filterExpr },
@@ -259,15 +264,10 @@
return return
} }
if (global.MemosApiModern) { if (global.MemosApiV023) {
global.MemosApiModern.fetchMemosWithFallback( global.MemosApiV023.listMemos(info, { pageSize: 1000 }, function (data) {
info, if (success) success(collectTags(info, extractMemos(data)))
'?pageSize=1000', }, fail)
function (data) {
if (success) success(collectTags(info, extractMemos(data)))
},
fail
)
} }
} }
@@ -328,7 +328,10 @@
if (flavor === FLAVOR_V020_V021 && global.MemosApiV020V021) { if (flavor === FLAVOR_V020_V021 && global.MemosApiV020V021) {
global.MemosApiV020V021.listMemos(info, { limit: 1000, rowStatus: 'NORMAL' }, function (data) { global.MemosApiV020V021.listMemos(info, { limit: 1000, rowStatus: 'NORMAL' }, function (data) {
if (success) success(keepLegacyVisibleMemos(extractMemos(data))) const raw = extractMemos(data)
const filtered = keepLegacyVisibleMemos(raw)
console.log('[memos-bber] v020-v021 random raw count:', raw.length, 'filtered count:', filtered.length)
if (success) success(filtered)
}, fail) }, fail)
return return
} }
@@ -364,10 +367,12 @@
} }
function uploadFile(file, options, success, fail) { function uploadFile(file, options, success, fail) {
const oldName = String(file && file.name ? file.name : 'upload').split('.') const sourceName = String(file && file.name ? file.name : 'upload')
const fileExt = String(file && file.name ? file.name : '').split('.').pop() const lastDot = sourceName.lastIndexOf('.')
const filenameBase = lastDot > 0 ? sourceName.slice(0, lastDot) : sourceName
const fileExt = lastDot > 0 ? sourceName.slice(lastDot) : ''
const now = global.dayjs().format('YYYYMMDDHHmmss') const now = global.dayjs().format('YYYYMMDDHHmmss')
const nextName = oldName[0] + '_' + now + (fileExt ? '.' + fileExt : '') const nextName = filenameBase + '_' + now + fileExt
if (flavor === FLAVOR_V020_V021 && global.MemosApiV020V021) { if (flavor === FLAVOR_V020_V021 && global.MemosApiV020V021) {
global.MemosApiV020V021.uploadResourceBlob( global.MemosApiV020V021.uploadResourceBlob(
@@ -396,7 +401,7 @@
info, info,
payload, payload,
function (resp) { function (resp) {
const entity = (resp && resp.resource) || resp const entity = (resp && (resp.attachment || resp.resource)) || resp
if (success) success(normalizeUploadedItem(entity, nextName)) if (success) success(normalizeUploadedItem(entity, nextName))
}, },
fail fail
@@ -416,9 +421,9 @@
} }
requestJson({ requestJson({
url: info.apiUrl + 'api/v1/' + memoName, url: info.apiUrl + 'api/v1/' + memoName + '?updateMask=state',
type: 'PATCH', type: 'PATCH',
data: JSON.stringify({ state: 'ARCHIVED' }), data: JSON.stringify({ name: memoName, state: 'ARCHIVED' }),
contentType: 'application/json', contentType: 'application/json',
dataType: 'json', dataType: 'json',
headers: { Authorization: 'Bearer ' + info.apiTokens } headers: { Authorization: 'Bearer ' + info.apiTokens }
@@ -458,24 +463,35 @@
return return
} }
const uploadedItems = Array.isArray(payload.resourceIdList) ? payload.resourceIdList : []
const attachmentReferences = uploadedItems
.filter(function (item) {
return item && typeof item.name === 'string' && item.name.indexOf('attachments/') === 0
})
.map(function (item) {
return { name: item.name }
})
const canCreateWithAttachments = flavor === 'modern' && attachmentReferences.length === uploadedItems.length
requestJson({ requestJson({
url: info.apiUrl + 'api/v1/memos', url: info.apiUrl + 'api/v1/memos',
type: 'POST', type: 'POST',
data: JSON.stringify({ data: JSON.stringify({
content: payload.content, content: payload.content,
visibility: payload.visibility visibility: payload.visibility,
...(canCreateWithAttachments ? { attachments: attachmentReferences } : {})
}), }),
contentType: 'application/json', contentType: 'application/json',
dataType: 'json', dataType: 'json',
headers: { Authorization: 'Bearer ' + info.apiTokens } headers: { Authorization: 'Bearer ' + info.apiTokens }
}, function (data) { }, function (data) {
const createdName = data && data.name ? data.name : data && data.memo && data.memo.name ? data.memo.name : '' const createdName = data && data.name ? data.name : data && data.memo && data.memo.name ? data.memo.name : ''
const resources = Array.isArray(payload.resourceIdList) ? payload.resourceIdList : [] const resources = uploadedItems
if (!createdName) { if (!createdName) {
if (success) success(data) if (success) success(data)
return return
} }
if (resources.length === 0) { if (resources.length === 0 || canCreateWithAttachments) {
getMemo(createdName, success, fail) getMemo(createdName, success, fail)
return return
} }
@@ -497,7 +513,7 @@
return { return {
flavor: flavor, flavor: flavor,
needsAuthenticatedImagePreview: function () { needsAuthenticatedImagePreview: function () {
return flavor === FLAVOR_V020_V021 return true
}, },
listTags: listTags, listTags: listTags,
searchMemos: searchMemos, searchMemos: searchMemos,
@@ -518,4 +534,4 @@
probeFlavor: probeFlavor, probeFlavor: probeFlavor,
resolve: resolve resolve: resolve
} }
})(window) })(window)
+47 -108
View File
@@ -151,10 +151,53 @@
return copy return copy
} }
function uploadResource() {
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 (xhr) {
if (!xhr || xhr.status !== 400) {
if (onFail) onFail(xhr)
return
}
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 (xhr2) {
if (onFail) onFail(xhr2)
})
})
}
// Attachment visibility was removed in Memos v0.26. Send the current
// attachment shape first and retain the resource endpoint as a legacy fallback.
global.$ global.$
.ajax({ .ajax({
url: urlAttachments, url: urlAttachments,
data: JSON.stringify(payload), data: JSON.stringify(stripVisibility(payload)),
type: 'POST', type: 'POST',
cache: false, cache: false,
processData: false, processData: false,
@@ -166,115 +209,11 @@
onSuccess(data, 'attachments') onSuccess(data, 'attachments')
}) })
.fail(function (xhr) { .fail(function (xhr) {
if (xhr && xhr.status === 400) { if (isNotFoundLikeXhr(xhr)) {
global.$ uploadResource()
.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 return
} }
if (onFail) onFail(xhr)
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)
})
}) })
} }
+56 -9
View File
@@ -201,22 +201,69 @@
function getTagSuggestion(info, success, fail) { function getTagSuggestion(info, success, fail) {
const headers = { Authorization: 'Bearer ' + info.apiTokens } const headers = { Authorization: 'Bearer ' + info.apiTokens }
function normalizeTagList(data) {
const list = Array.isArray(data) ? data : Array.isArray(data.tags) ? data.tags : []
return 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)
}
function normalizeSuggestionList(data) {
const list = Array.isArray(data) ? data : []
return list
.map(function (s) {
return String(s).replace(/^#/, '').trim()
})
.filter(Boolean)
}
function mergeUnique(a, b) {
return [...new Set((a || []).concat(b || []))]
}
let suggestionList = []
let listList = []
let pending = 2
function finish() {
if (--pending !== 0) return
if (success) success(mergeUnique(suggestionList, listList))
}
requestGet( requestGet(
info.apiUrl + 'api/v1/tag/suggestion', info.apiUrl + 'api/v1/tag/suggestion',
headers, headers,
function (data) { function (data) {
const list = Array.isArray(data) ? data : [] suggestionList = normalizeSuggestionList(data)
const out = list finish()
.map(function (s) { },
return String(s).replace(/^#/, '').trim() function (xhr) {
}) // Ignore failures from the suggestion endpoint; the list endpoint may still work.
.filter(Boolean) if (xhr && xhr.status !== 404 && xhr.status !== 405 && fail) fail(xhr)
if (success) success(out) else finish()
}
)
requestGet(
info.apiUrl + 'api/v1/tag',
headers,
function (data) {
listList = normalizeTagList(data)
finish()
}, },
function (xhr) { function (xhr) {
// Some forks might only expose list.
if (isNotFoundLikeXhr(xhr)) { if (isNotFoundLikeXhr(xhr)) {
getTagList(info, success, fail) finish()
return return
} }
if (fail) fail(xhr) if (fail) fail(xhr)
+1
View File
@@ -0,0 +1 @@
!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n(require("dayjs")):"function"==typeof define&&define.amd?define(["dayjs"],n):(e="undefined"!=typeof globalThis?globalThis:e||self).dayjs_locale_de=n(e.dayjs)}(this,(function(e){"use strict";function n(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var t=n(e),a={name:"de",weekdays:"Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag".split("_"),weekdaysShort:"So._Mo._Di._Mi._Do._Fr._Sa.".split("_"),weekdaysMin:"So_Mo_Di_Mi_Do_Fr_Sa".split("_"),months:"Januar_Februar_März_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember".split("_"),monthsShort:"Jan._Feb._März_Apr._Mai_Juni_Juli_Aug._Sept._Okt._Nov._Dez.".split("_"),weekStart:1,formats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY HH:mm",LLLL:"dddd, D. MMMM YYYY HH:mm"},relativeTime:{future:"in %s",past:"vor %s",s:"ein paar Sekunden",m:"einer Minute",mm:"%d Minuten",h:"einer Stunde",hh:"%d Stunden",d:"einem Tag",dd:"%d Tagen",M:"einem Monat",MM:"%d Monaten",y:"einem Jahr",yy:"%d Jahren"},ordinal:function(e){return e+"."}};return t.default.locale(a,null,!0),a}));
+1
View File
@@ -0,0 +1 @@
!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n(require("dayjs")):"function"==typeof define&&define.amd?define(["dayjs"],n):(e="undefined"!=typeof globalThis?globalThis:e||self).dayjs_locale_es=n(e.dayjs)}(this,(function(e){"use strict";function n(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var t=n(e),a={name:"es",weekdays:"domingo_lunes_martes_miércoles_jueves_viernes_sábado".split("_"),weekdaysShort:"dom._lun._mar._mié._jue._vie._sáb.".split("_"),weekdaysMin:"do_lu_ma_mi_ju_vi_sá".split("_"),months:"enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre".split("_"),monthsShort:"ene._feb._mar._abr._may._jun._jul._ago._sept._oct._nov._dic.".split("_"),weekStart:1,formats:{LT:"H:mm",LTS:"H:mm:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY H:mm",LLLL:"dddd, D [de] MMMM [de] YYYY H:mm"},relativeTime:{future:"en %s",past:"hace %s",s:"unos segundos",m:"un minuto",mm:"%d minutos",h:"una hora",hh:"%d horas",d:"un día",dd:"%d días",M:"un mes",MM:"%d meses",y:"un año",yy:"%d años"},ordinal:function(e){return e+"º"}};return t.default.locale(a,null,!0),a}));
+1
View File
@@ -0,0 +1 @@
!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n(require("dayjs")):"function"==typeof define&&define.amd?define(["dayjs"],n):(e="undefined"!=typeof globalThis?globalThis:e||self).dayjs_locale_fr=n(e.dayjs)}(this,(function(e){"use strict";function n(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var t=n(e),a={name:"fr",weekdays:"dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi".split("_"),weekdaysShort:"dim._lun._mar._mer._jeu._ven._sam.".split("_"),weekdaysMin:"di_lu_ma_me_je_ve_sa".split("_"),months:"janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre".split("_"),monthsShort:"janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.".split("_"),weekStart:1,formats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},relativeTime:{future:"dans %s",past:"il y a %s",s:"quelques secondes",m:"une minute",mm:"%d minutes",h:"une heure",hh:"%d heures",d:"un jour",dd:"%d jours",M:"un mois",MM:"%d mois",y:"un an",yy:"%d ans"},ordinal:function(e){return e+(1===e?"er":"")}};return t.default.locale(a,null,!0),a}));
+7 -1
View File
@@ -1,6 +1,6 @@
const UI_LANGUAGE_STORAGE_KEY = 'uiLanguage' const UI_LANGUAGE_STORAGE_KEY = 'uiLanguage'
const SUPPORTED_UI_LANGUAGES = new Set(['auto', 'en', 'zh_CN', 'ja', 'ko']) const SUPPORTED_UI_LANGUAGES = new Set(['auto', 'en', 'de', 'fr', 'es', 'zh_CN', 'ja', 'ko'])
function normalizeUiLanguage(value) { function normalizeUiLanguage(value) {
const lang = String(value || 'auto') const lang = String(value || 'auto')
@@ -49,6 +49,9 @@ let overrideMessages = null
function getLanguageToggleLabel(lang) { function getLanguageToggleLabel(lang) {
if (lang === 'en') return 'EN' if (lang === 'en') return 'EN'
if (lang === 'de') return 'DE'
if (lang === 'fr') return 'FR'
if (lang === 'es') return 'ES'
if (lang === 'zh_CN') return '中' if (lang === 'zh_CN') return '中'
if (lang === 'ja') return '日' if (lang === 'ja') return '日'
if (lang === 'ko') return '한' if (lang === 'ko') return '한'
@@ -132,6 +135,9 @@ function applyStaticI18n() {
// Language switcher // Language switcher
setText('langOptionAuto', 'langAuto') setText('langOptionAuto', 'langAuto')
setText('langOptionEn', 'langEnglish') setText('langOptionEn', 'langEnglish')
setText('langOptionDe', 'langGerman')
setText('langOptionFr', 'langFrench')
setText('langOptionEs', 'langSpanish')
setText('langOptionZhCN', 'langChineseSimplified') setText('langOptionZhCN', 'langChineseSimplified')
setText('langOptionJa', 'langJapanese') setText('langOptionJa', 'langJapanese')
setText('langOptionKo', 'langKorean') setText('langOptionKo', 'langKorean')
+75 -11
View File
@@ -162,6 +162,21 @@ function msg(key) {
function applyDayjsLocaleByUiLanguage(uiLang) { function applyDayjsLocaleByUiLanguage(uiLang) {
const lang = String(uiLang || 'auto') const lang = String(uiLang || 'auto')
if (lang === 'de') {
dayjs.locale('de')
return
}
if (lang === 'fr') {
dayjs.locale('fr')
return
}
if (lang === 'es') {
dayjs.locale('es')
return
}
if (lang === 'zh_CN') { if (lang === 'zh_CN') {
dayjs.locale('zh-cn') dayjs.locale('zh-cn')
return return
@@ -184,6 +199,18 @@ function applyDayjsLocaleByUiLanguage(uiLang) {
// auto: best-effort infer from browser UI language // auto: best-effort infer from browser UI language
const ui = String(chrome.i18n.getUILanguage ? chrome.i18n.getUILanguage() : '').toLowerCase() const ui = String(chrome.i18n.getUILanguage ? chrome.i18n.getUILanguage() : '').toLowerCase()
if (ui.startsWith('de')) {
dayjs.locale('de')
return
}
if (ui.startsWith('fr')) {
dayjs.locale('fr')
return
}
if (ui.startsWith('es')) {
dayjs.locale('es')
return
}
if (ui.startsWith('zh')) { if (ui.startsWith('zh')) {
dayjs.locale('zh-cn') dayjs.locale('zh-cn')
return return
@@ -294,6 +321,8 @@ function getMemoUid(memo) {
if (!memo) return '' if (!memo) return ''
if (memo.uid != null && memo.uid !== '') return String(memo.uid) if (memo.uid != null && memo.uid !== '') return String(memo.uid)
if (typeof memo.name === 'string' && memo.name) return memo.name.split('/').pop() if (typeof memo.name === 'string' && memo.name) return memo.name.split('/').pop()
// v0.18.2 only exposes a numeric id.
if (memo.id != null && memo.id !== '') return String(memo.id)
return '' return ''
} }
@@ -453,6 +482,13 @@ function buildV1ResourceStreamUrl(info, resource) {
} }
if (root && !root.endsWith('/')) root += '/' if (root && !root.endsWith('/')) root += '/'
if (resource.externalLink) return resource.externalLink
const isV023 = info.apiFlavor === 'v023'
const name = typeof resource.name === 'string' ? resource.name.trim() : ''
const filename = typeof resource.filename === 'string' ? resource.filename : ''
function isImageResource(r) { function isImageResource(r) {
if (!r) return false if (!r) return false
const t = typeof r.type === 'string' ? r.type.toLowerCase() : '' const t = typeof r.type === 'string' ? r.type.toLowerCase() : ''
@@ -461,6 +497,22 @@ function buildV1ResourceStreamUrl(info, resource) {
return /\.(png|jpe?g|gif|webp|bmp|svg|avif|heic)$/.test(fn) return /\.(png|jpe?g|gif|webp|bmp|svg|avif|heic)$/.test(fn)
} }
// v0.23+: resources are served as static files via /file/{name}/{filename}.
if (isV023) {
if (resource.externalLink) return resource.externalLink
const resourceName = name.indexOf('resources/') === 0 ? name : ''
if (resourceName && filename) {
const url = root + 'file/' + resourceName + '/' + filename.split('/').map(encodeURIComponent).join('/')
return isImageResource(resource) ? url + '?thumbnail=true' : url
}
return ''
}
if (name.indexOf('attachments/') === 0 && filename) {
const url = root + 'file/' + name.split('/').map(encodeURIComponent).join('/') + '/' + encodeURIComponent(filename)
return isImageResource(resource) ? url + '?thumbnail=true' : url
}
function isProbablyUid(s) { function isProbablyUid(s) {
if (typeof s !== 'string') return false if (typeof s !== 'string') return false
const v = s.trim() const v = s.trim()
@@ -480,6 +532,11 @@ function buildV1ResourceStreamUrl(info, resource) {
const uid = typeof uidRaw === 'string' ? uidRaw : uidRaw != null ? String(uidRaw) : '' const uid = typeof uidRaw === 'string' ? uidRaw : uidRaw != null ? String(uidRaw) : ''
if (uid.trim() !== '') return buildStreamUrl(uid.trim()) if (uid.trim() !== '') return buildStreamUrl(uid.trim())
// Fallback for older resource shapes.
// In some memo payloads, the uid may appear as `name` directly.
// Example: name="ETU6hjuR..." should map to /o/r/:uid, not /file/:name/:filename.
if (isProbablyUid(name)) return buildStreamUrl(name.trim())
// Legacy versions (e.g. v0.18) may only expose numeric `id` without `uid/name`. // Legacy versions (e.g. v0.18) may only expose numeric `id` without `uid/name`.
const idRaw = resource.id != null ? resource.id : resource.ID != null ? resource.ID : resource.Id const idRaw = resource.id != null ? resource.id : resource.ID != null ? resource.ID : resource.Id
const id = typeof idRaw === 'number' && Number.isFinite(idRaw) const id = typeof idRaw === 'number' && Number.isFinite(idRaw)
@@ -489,14 +546,7 @@ function buildV1ResourceStreamUrl(info, resource) {
: '' : ''
if (id) return buildStreamUrl(id) if (id) return buildStreamUrl(id)
// Fallback for older resource shapes. const fileId = resource.publicId || filename
const name = typeof resource.name === 'string' ? resource.name : ''
// In some memo payloads, the uid may appear as `name` directly.
// Example: name="ETU6hjuR..." should map to /o/r/:uid, not /file/:name/:filename.
if (isProbablyUid(name)) return buildStreamUrl(name.trim())
const fileId = resource.publicId || resource.filename
if (name && fileId) return root + 'file/' + name + '/' + fileId if (name && fileId) return root + 'file/' + name + '/' + fileId
return '' return ''
} }
@@ -886,11 +936,17 @@ $('#opensite').click(function () {
// 0.23.1版本 GET api/v1/{parent}/tags 接口已移除,参考 https://github.com/usememos/memos/issues/4161 // 0.23.1版本 GET api/v1/{parent}/tags 接口已移除,参考 https://github.com/usememos/memos/issues/4161
$('#tags').click(function () { $('#tags').click(function () {
get_info(function (info) { get_info(function (info) {
console.log('[memos-bber] tags click info:', info)
if (info.apiUrl) { if (info.apiUrl) {
var tagDom = ""; var tagDom = "";
const adapter = getApiAdapter(info) const adapter = getApiAdapter(info)
if (!adapter) {
console.warn('[memos-bber] no adapter for tags')
return
}
const renderTags = function (tags) { const renderTags = function (tags) {
console.log('[memos-bber] tags result:', tags)
const uniTags = [...new Set((Array.isArray(tags) ? tags : []).filter(Boolean))] const uniTags = [...new Set((Array.isArray(tags) ? tags : []).filter(Boolean))]
$.each(uniTags, function (_, tag) { $.each(uniTags, function (_, tag) {
tagDom += '<span class="item-container">#' + tag + '</span>'; tagDom += '<span class="item-container">#' + tag + '</span>';
@@ -899,7 +955,8 @@ $('#tags').click(function () {
$("#taglist").html(tagDom).slideToggle(500) $("#taglist").html(tagDom).slideToggle(500)
} }
adapter.listTags(renderTags, function () { adapter.listTags(renderTags, function (err) {
console.error('[memos-bber] tags failed:', err)
$.message({ message: msg('placeApiUrl') }) $.message({ message: msg('placeApiUrl') })
}) })
} else { } else {
@@ -1017,17 +1074,24 @@ $('#search').click(function () {
$('#random').click(function () { $('#random').click(function () {
get_info(function (info) { get_info(function (info) {
console.log('[memos-bber] random click info:', info)
if (info.status) { if (info.status) {
$("#randomlist").html('').hide() $("#randomlist").html('').hide()
const adapter = getApiAdapter(info) const adapter = getApiAdapter(info)
if (!adapter) {
console.warn('[memos-bber] no adapter for random')
return
}
adapter.listRandomMemos( adapter.listRandomMemos(
function (memos) { function (memos) {
console.log('[memos-bber] random result count:', memos && memos.length)
let randomNum = Math.floor(Math.random() * (memos.length)); let randomNum = Math.floor(Math.random() * (memos.length));
var randomData = memos[randomNum] var randomData = memos[randomNum]
randDom(randomData) randDom(randomData)
}, },
function () { function (err) {
console.error('[memos-bber] random failed:', err)
$.message({ message: msg('placeApiUrl') }) $.message({ message: msg('placeApiUrl') })
} }
) )
@@ -1256,4 +1320,4 @@ function sendText(preparedContent) {
}) })
} }
}) })
} }
+1 -1
View File
@@ -2,7 +2,7 @@
"manifest_version": 3, "manifest_version": 3,
"name": "__MSG_extName__", "name": "__MSG_extName__",
"default_locale": "en", "default_locale": "en",
"version": "2026.04.24", "version": "2026.08.05",
"action": { "action": {
"default_popup": "popup.html", "default_popup": "popup.html",
"default_icon": "assets/logo_24x24.png", "default_icon": "assets/logo_24x24.png",
+6
View File
@@ -25,6 +25,9 @@
<div id="langMenu" class="lang-menu hidden" role="menu" aria-labelledby="langToggle"> <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="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="langOptionEn" class="lang-menu-item" type="button" data-lang="en" role="menuitemradio" aria-checked="false"></button>
<button id="langOptionDe" class="lang-menu-item" type="button" data-lang="de" role="menuitemradio" aria-checked="false"></button>
<button id="langOptionFr" class="lang-menu-item" type="button" data-lang="fr" role="menuitemradio" aria-checked="false"></button>
<button id="langOptionEs" class="lang-menu-item" type="button" data-lang="es" 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="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="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> <button id="langOptionKo" class="lang-menu-item" type="button" data-lang="ko" role="menuitemradio" aria-checked="false"></button>
@@ -180,6 +183,9 @@
<script src="../js/jquery.min.js"></script> <script src="../js/jquery.min.js"></script>
<script src="../js/message.js"></script> <script src="../js/message.js"></script>
<script src="../js/dayjs.min.js"></script> <script src="../js/dayjs.min.js"></script>
<script src="../js/de.js"></script>
<script src="../js/fr.js"></script>
<script src="../js/es.js"></script>
<script src="../js/zh-cn.js"></script> <script src="../js/zh-cn.js"></script>
<script src="../js/ja.js"></script> <script src="../js/ja.js"></script>
<script src="../js/ko.js"></script> <script src="../js/ko.js"></script>
+197
View File
@@ -0,0 +1,197 @@
{
"extName": {
"message": "Memos"
},
"actionTitle": {
"message": "An Memos senden"
},
"extDescription": {
"message": "memos: Ein leichtgewichtiges, selbst gehostetes Memo-Hub."
},
"sendTo": {
"message": "An Memos senden \"%s\""
},
"sendLinkTo": {
"message": "Link an Memos senden"
},
"sendImageTo": {
"message": "Bild an Memos senden"
},
"saveBtn": {
"message": "Speichern"
},
"supportedMemosVersion": {
"message": "Kompatibel mit Memos v0.15.0 - 0.30.x"
},
"settingsConnectionTitle": {
"message": "Verbindung"
},
"settingsConnectionDesc": {
"message": "Konfiguriere die URL der Memos-Instanz und das Zugriffstoken."
},
"settingsPostingTitle": {
"message": "Verfassen"
},
"settingsPostingDesc": {
"message": "Standardtext für Uploads ohne Text"
},
"placeApiUrl": {
"message": "URL der Memos-Instanz"
},
"placeApiTokens": {
"message": "Memos-Zugriffstoken"
},
"placeContent": {
"message": "Was geht dir durch den Kopf..."
},
"lockPrivate": {
"message": "Privat"
},
"lockProtected": {
"message": "Geschützt"
},
"lockPublic": {
"message": "Öffentlich"
},
"submitBtn": {
"message": "Speichern"
},
"placeHideInput": {
"message": "Standardname für den Tag \"Privat\""
},
"placeShowInput": {
"message": "Standardname für den Tag \"Für alle sichtbar\""
},
"placeAttachmentOnlyDefaultText": {
"message": "Standardtext für Uploads ohne Text (leer lassen, um den eingebauten Text zu verwenden)"
},
"uploadedListTitle": {
"message": "Hochgeladene Dateien, zum Neuordnen ziehen"
},
"uploadedListEmpty": {
"message": "Keine hochgeladenen Dateien"
},
"tipReorder": {
"message": "Zum Neuordnen ziehen"
},
"tipDeleteAttachment": {
"message": "Löschen"
},
"attachmentDeleteSuccess": {
"message": "Gelöscht"
},
"attachmentDeleteFailed": {
"message": "Löschen fehlgeschlagen 😭"
},
"picDrag": {
"message": "Bild zum Hochladen hierher ziehen"
},
"picCancelDrag": {
"message": "Upload abbrechen"
},
"picUploading": {
"message": "Bild wird hochgeladen..."
},
"picSuccess": {
"message": "Upload abgeschlossen"
},
"picFailed": {
"message": "Upload fehlgeschlagen"
},
"picPending": {
"message": "Bild wird noch hochgeladen"
},
"saveSuccess": {
"message": "Einstellungen gespeichert!"
},
"searchNow": {
"message": "Wonach suchst du?"
},
"searchNone": {
"message": "Versuche es mit einem anderen Wort!"
},
"archiveSuccess": {
"message": "Archiviert 😊"
},
"archiveFailed": {
"message": "Archivieren fehlgeschlagen 😭"
},
"getTabFailed": {
"message": "Tab konnte nicht gelesen werden 😭"
},
"memoUploading": {
"message": "Wird gesendet"
},
"memoSuccess": {
"message": "Erfolgreich! 😊"
},
"memoFailed": {
"message": "Fehlgeschlagen! 😭"
},
"invalidToken": {
"message": "Ungültiges Token oder ungültige URL 😭"
},
"tipOpenSite": {
"message": "Memos öffnen"
},
"tipSettings": {
"message": "Einstellungen"
},
"tipTags": {
"message": "Tag einfügen"
},
"tipTodo": {
"message": "Todo einfügen"
},
"tipUpload": {
"message": "Datei hochladen"
},
"tipLink": {
"message": "Link des aktuellen Tabs einfügen"
},
"tipRandom": {
"message": "Zufälliges Memo"
},
"tipSearch": {
"message": "Suchen"
},
"tipVisibility": {
"message": "Sichtbarkeit"
},
"tipSend": {
"message": "Senden (Strg/⌘+Enter)"
},
"tipLanguage": {
"message": "Sprache"
},
"langAuto": {
"message": "Automatisch"
},
"langEnglish": {
"message": "English"
},
"langGerman": {
"message": "Deutsch"
},
"langFrench": {
"message": "Français"
},
"langSpanish": {
"message": "Español"
},
"langChineseSimplified": {
"message": "简体中文"
},
"langJapanese": {
"message": "日本語"
},
"langKorean": {
"message": "한국어"
},
"tipFullscreen": {
"message": "Vollbild-Editor öffnen"
},
"tipResize": {
"message": "Zum Ändern der Größe ziehen (Minimum: Standardgröße)"
}
}
+10 -1
View File
@@ -21,7 +21,7 @@
"message": "Save" "message": "Save"
}, },
"supportedMemosVersion": { "supportedMemosVersion": {
"message": "Compatible with Memos v0.15.0 - 0.27.x" "message": "Compatible with Memos v0.15.0 - 0.30.x"
}, },
"settingsConnectionTitle": { "settingsConnectionTitle": {
"message": "Connection" "message": "Connection"
@@ -170,6 +170,15 @@
"langEnglish": { "langEnglish": {
"message": "English" "message": "English"
}, },
"langGerman": {
"message": "Deutsch"
},
"langFrench": {
"message": "Français"
},
"langSpanish": {
"message": "Español"
},
"langChineseSimplified": { "langChineseSimplified": {
"message": "简体中文" "message": "简体中文"
}, },
+197
View File
@@ -0,0 +1,197 @@
{
"extName": {
"message": "Memos"
},
"actionTitle": {
"message": "Enviar a Memos"
},
"extDescription": {
"message": "memos: un centro de notas ligero y autoalojado."
},
"sendTo": {
"message": "Enviar a Memos \"%s\""
},
"sendLinkTo": {
"message": "Enviar enlace a Memos"
},
"sendImageTo": {
"message": "Enviar imagen a Memos"
},
"saveBtn": {
"message": "Guardar"
},
"supportedMemosVersion": {
"message": "Compatible con Memos v0.15.0 - 0.30.x"
},
"settingsConnectionTitle": {
"message": "Conexión"
},
"settingsConnectionDesc": {
"message": "Configura la URL del sitio de Memos y el token de acceso."
},
"settingsPostingTitle": {
"message": "Publicación"
},
"settingsPostingDesc": {
"message": "Texto predeterminado para envíos solo con adjuntos"
},
"placeApiUrl": {
"message": "URL del sitio de Memos"
},
"placeApiTokens": {
"message": "Tokens de acceso de Memos"
},
"placeContent": {
"message": "¿Qué tienes en mente...?"
},
"lockPrivate": {
"message": "Privado"
},
"lockProtected": {
"message": "Protegido"
},
"lockPublic": {
"message": "Público"
},
"submitBtn": {
"message": "Guardar"
},
"placeHideInput": {
"message": "Nombre predeterminado de la etiqueta \"Privado\""
},
"placeShowInput": {
"message": "Nombre predeterminado de la etiqueta \"Visible para todos\""
},
"placeAttachmentOnlyDefaultText": {
"message": "Texto predeterminado para envíos solo con adjuntos (déjalo vacío para usar el texto integrado)"
},
"uploadedListTitle": {
"message": "Archivos subidos, arrastra para reordenar"
},
"uploadedListEmpty": {
"message": "No hay archivos subidos"
},
"tipReorder": {
"message": "Arrastra para reordenar"
},
"tipDeleteAttachment": {
"message": "Eliminar"
},
"attachmentDeleteSuccess": {
"message": "Eliminado"
},
"attachmentDeleteFailed": {
"message": "Error al eliminar 😭"
},
"picDrag": {
"message": "Arrastra la imagen para subirla"
},
"picCancelDrag": {
"message": "Cancelar subida"
},
"picUploading": {
"message": "Subiendo la imagen..."
},
"picSuccess": {
"message": "Subida completada"
},
"picFailed": {
"message": "La subida falló"
},
"picPending": {
"message": "La imagen aún se está subiendo"
},
"saveSuccess": {
"message": "¡Configuración guardada!"
},
"searchNow": {
"message": "¿Qué estás buscando?"
},
"searchNone": {
"message": "¡Prueba con otra palabra!"
},
"archiveSuccess": {
"message": "Archivado 😊"
},
"archiveFailed": {
"message": "Error al archivar 😭"
},
"getTabFailed": {
"message": "Error al obtener la pestaña 😭"
},
"memoUploading": {
"message": "Enviando"
},
"memoSuccess": {
"message": "¡Éxito! 😊"
},
"memoFailed": {
"message": "¡Falló! 😭"
},
"invalidToken": {
"message": "Token o URL no válidos 😭"
},
"tipOpenSite": {
"message": "Abrir Memos"
},
"tipSettings": {
"message": "Configuración"
},
"tipTags": {
"message": "Insertar etiqueta"
},
"tipTodo": {
"message": "Insertar tarea"
},
"tipUpload": {
"message": "Subir archivo"
},
"tipLink": {
"message": "Insertar enlace de la pestaña actual"
},
"tipRandom": {
"message": "Memo aleatorio"
},
"tipSearch": {
"message": "Buscar"
},
"tipVisibility": {
"message": "Visibilidad"
},
"tipSend": {
"message": "Enviar (Ctrl/⌘+Enter)"
},
"tipLanguage": {
"message": "Idioma"
},
"langAuto": {
"message": "Auto"
},
"langEnglish": {
"message": "English"
},
"langGerman": {
"message": "Deutsch"
},
"langFrench": {
"message": "Français"
},
"langSpanish": {
"message": "Español"
},
"langChineseSimplified": {
"message": "简体中文"
},
"langJapanese": {
"message": "日本語"
},
"langKorean": {
"message": "한국어"
},
"tipFullscreen": {
"message": "Abrir editor en pantalla completa"
},
"tipResize": {
"message": "Arrastra para cambiar el tamaño (mín.: tamaño predeterminado)"
}
}
+197
View File
@@ -0,0 +1,197 @@
{
"extName": {
"message": "Memos"
},
"actionTitle": {
"message": "Envoyer à Memos"
},
"extDescription": {
"message": "memos : un hub de mémos léger et auto-hébergé."
},
"sendTo": {
"message": "Envoyer à Memos \"%s\""
},
"sendLinkTo": {
"message": "Envoyer le lien vers Memos"
},
"sendImageTo": {
"message": "Envoyer l'image vers Memos"
},
"saveBtn": {
"message": "Enregistrer"
},
"supportedMemosVersion": {
"message": "Compatible avec Memos v0.15.0 - 0.30.x"
},
"settingsConnectionTitle": {
"message": "Connexion"
},
"settingsConnectionDesc": {
"message": "Configurez l'URL du site Memos et le jeton d'accès."
},
"settingsPostingTitle": {
"message": "Publication"
},
"settingsPostingDesc": {
"message": "Texte par défaut pour les envois avec pièce jointe uniquement"
},
"placeApiUrl": {
"message": "URL du site Memos"
},
"placeApiTokens": {
"message": "Jetons d'accès Memos"
},
"placeContent": {
"message": "À quoi pensez-vous..."
},
"lockPrivate": {
"message": "Privé"
},
"lockProtected": {
"message": "Protégé"
},
"lockPublic": {
"message": "Public"
},
"submitBtn": {
"message": "Enregistrer"
},
"placeHideInput": {
"message": "Nom par défaut du tag \"Privé\""
},
"placeShowInput": {
"message": "Nom par défaut du tag \"Visible par tous\""
},
"placeAttachmentOnlyDefaultText": {
"message": "Texte par défaut pour les envois avec pièce jointe uniquement (laisser vide pour utiliser le texte intégré)"
},
"uploadedListTitle": {
"message": "Fichiers téléversés, faites glisser pour réorganiser"
},
"uploadedListEmpty": {
"message": "Aucun fichier téléversé"
},
"tipReorder": {
"message": "Glisser pour réorganiser"
},
"tipDeleteAttachment": {
"message": "Supprimer"
},
"attachmentDeleteSuccess": {
"message": "Supprimé"
},
"attachmentDeleteFailed": {
"message": "Échec de la suppression 😭"
},
"picDrag": {
"message": "Faites glisser l'image pour la téléverser"
},
"picCancelDrag": {
"message": "Annuler le téléversement"
},
"picUploading": {
"message": "Téléversement de l'image..."
},
"picSuccess": {
"message": "Téléversement terminé"
},
"picFailed": {
"message": "Échec du téléversement"
},
"picPending": {
"message": "Le téléversement de l'image est en cours"
},
"saveSuccess": {
"message": "Paramètres enregistrés !"
},
"searchNow": {
"message": "Que recherchez-vous ?"
},
"searchNone": {
"message": "Essayez un autre mot !"
},
"archiveSuccess": {
"message": "Archivé 😊"
},
"archiveFailed": {
"message": "Échec de l'archivage 😭"
},
"getTabFailed": {
"message": "Échec de la récupération de l'onglet 😭"
},
"memoUploading": {
"message": "Envoi en cours"
},
"memoSuccess": {
"message": "Succès ! 😊"
},
"memoFailed": {
"message": "Échec ! 😭"
},
"invalidToken": {
"message": "Jeton ou URL invalide 😭"
},
"tipOpenSite": {
"message": "Ouvrir Memos"
},
"tipSettings": {
"message": "Paramètres"
},
"tipTags": {
"message": "Insérer un tag"
},
"tipTodo": {
"message": "Insérer une tâche"
},
"tipUpload": {
"message": "Téléverser un fichier"
},
"tipLink": {
"message": "Insérer le lien de l'onglet actuel"
},
"tipRandom": {
"message": "Mémo aléatoire"
},
"tipSearch": {
"message": "Rechercher"
},
"tipVisibility": {
"message": "Visibilité"
},
"tipSend": {
"message": "Envoyer (Ctrl/⌘+Entrée)"
},
"tipLanguage": {
"message": "Langue"
},
"langAuto": {
"message": "Auto"
},
"langEnglish": {
"message": "English"
},
"langGerman": {
"message": "Deutsch"
},
"langFrench": {
"message": "Français"
},
"langSpanish": {
"message": "Español"
},
"langChineseSimplified": {
"message": "简体中文"
},
"langJapanese": {
"message": "日本語"
},
"langKorean": {
"message": "한국어"
},
"tipFullscreen": {
"message": "Ouvrir l'éditeur en plein écran"
},
"tipResize": {
"message": "Glisser pour redimensionner (min. : taille par défaut)"
}
}
+10 -1
View File
@@ -21,7 +21,7 @@
"message": "保存" "message": "保存"
}, },
"supportedMemosVersion": { "supportedMemosVersion": {
"message": "Memos v0.15.0 - 0.27.x に対応" "message": "Memos v0.15.0 - 0.30.x に対応"
}, },
"settingsConnectionTitle": { "settingsConnectionTitle": {
"message": "接続設定" "message": "接続設定"
@@ -170,6 +170,15 @@
"langEnglish": { "langEnglish": {
"message": "English" "message": "English"
}, },
"langGerman": {
"message": "Deutsch"
},
"langFrench": {
"message": "Français"
},
"langSpanish": {
"message": "Español"
},
"langChineseSimplified": { "langChineseSimplified": {
"message": "简体中文" "message": "简体中文"
}, },
+10 -1
View File
@@ -21,7 +21,7 @@
"message": "저장" "message": "저장"
}, },
"supportedMemosVersion": { "supportedMemosVersion": {
"message": "Memos v0.15.0 - 0.27.x 호환" "message": "Memos v0.15.0 - 0.30.x 호환"
}, },
"settingsConnectionTitle": { "settingsConnectionTitle": {
"message": "연결 설정" "message": "연결 설정"
@@ -170,6 +170,15 @@
"langEnglish": { "langEnglish": {
"message": "English" "message": "English"
}, },
"langGerman": {
"message": "Deutsch"
},
"langFrench": {
"message": "Français"
},
"langSpanish": {
"message": "Español"
},
"langChineseSimplified": { "langChineseSimplified": {
"message": "简体中文" "message": "简体中文"
}, },
+10 -1
View File
@@ -21,7 +21,7 @@
"message": "保存" "message": "保存"
}, },
"supportedMemosVersion": { "supportedMemosVersion": {
"message": "兼容 Memos v0.15.0 - 0.27.x" "message": "兼容 Memos v0.15.0 - 0.30.x"
}, },
"settingsConnectionTitle": { "settingsConnectionTitle": {
"message": "连接设置" "message": "连接设置"
@@ -170,6 +170,15 @@
"langEnglish": { "langEnglish": {
"message": "English" "message": "English"
}, },
"langGerman": {
"message": "Deutsch"
},
"langFrench": {
"message": "Français"
},
"langSpanish": {
"message": "Español"
},
"langChineseSimplified": { "langChineseSimplified": {
"message": "简体中文" "message": "简体中文"
}, },
+42 -26
View File
@@ -187,6 +187,7 @@
function normalizeUploadedItem(entity, fallbackFilename) { function normalizeUploadedItem(entity, fallbackFilename) {
if (!entity) return null if (!entity) return null
entity = entity.attachment || entity.resource || entity
const inferredId = (function () { const inferredId = (function () {
const value = entity.id != null ? entity.id : entity.ID != null ? entity.ID : entity.Id 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 === 'number' && Number.isFinite(value)) return Math.floor(value)
@@ -204,7 +205,11 @@
name: name, name: name,
filename: entity.filename || fallbackFilename || name, filename: entity.filename || fallbackFilename || name,
createTime: entity.createTime || entity.createdTs || entity.createdAt, createTime: entity.createTime || entity.createdTs || entity.createdAt,
type: entity.type type: entity.type,
uid: entity.uid,
size: entity.size,
memo: entity.memo,
externalLink: entity.externalLink
} }
} }
@@ -244,10 +249,10 @@
} }
if (flavor === 'v023' && global.MemosApiV023) { if (flavor === 'v023' && global.MemosApiV023) {
const filterExpr = global.MemosApiV023.buildFilter({ // v0.23 filter requires `creator == "users/{numericId}"`. Some stored userids are
rowStatus: 'NORMAL', // usernames, which causes 400. Also `row_status` can cause 400 on some builds.
creator: 'users/' + info.userid // Let the server identify the current user from the token and return all visible memos.
}) const filterExpr = global.MemosApiV023.buildFilter({})
global.MemosApiV023.listMemos( global.MemosApiV023.listMemos(
info, info,
{ pageSize: 1000, filterExpr: filterExpr }, { pageSize: 1000, filterExpr: filterExpr },
@@ -259,15 +264,10 @@
return return
} }
if (global.MemosApiModern) { if (global.MemosApiV023) {
global.MemosApiModern.fetchMemosWithFallback( global.MemosApiV023.listMemos(info, { pageSize: 1000 }, function (data) {
info, if (success) success(collectTags(info, extractMemos(data)))
'?pageSize=1000', }, fail)
function (data) {
if (success) success(collectTags(info, extractMemos(data)))
},
fail
)
} }
} }
@@ -328,7 +328,10 @@
if (flavor === FLAVOR_V020_V021 && global.MemosApiV020V021) { if (flavor === FLAVOR_V020_V021 && global.MemosApiV020V021) {
global.MemosApiV020V021.listMemos(info, { limit: 1000, rowStatus: 'NORMAL' }, function (data) { global.MemosApiV020V021.listMemos(info, { limit: 1000, rowStatus: 'NORMAL' }, function (data) {
if (success) success(keepLegacyVisibleMemos(extractMemos(data))) const raw = extractMemos(data)
const filtered = keepLegacyVisibleMemos(raw)
console.log('[memos-bber] v020-v021 random raw count:', raw.length, 'filtered count:', filtered.length)
if (success) success(filtered)
}, fail) }, fail)
return return
} }
@@ -364,10 +367,12 @@
} }
function uploadFile(file, options, success, fail) { function uploadFile(file, options, success, fail) {
const oldName = String(file && file.name ? file.name : 'upload').split('.') const sourceName = String(file && file.name ? file.name : 'upload')
const fileExt = String(file && file.name ? file.name : '').split('.').pop() const lastDot = sourceName.lastIndexOf('.')
const filenameBase = lastDot > 0 ? sourceName.slice(0, lastDot) : sourceName
const fileExt = lastDot > 0 ? sourceName.slice(lastDot) : ''
const now = global.dayjs().format('YYYYMMDDHHmmss') const now = global.dayjs().format('YYYYMMDDHHmmss')
const nextName = oldName[0] + '_' + now + (fileExt ? '.' + fileExt : '') const nextName = filenameBase + '_' + now + fileExt
if (flavor === FLAVOR_V020_V021 && global.MemosApiV020V021) { if (flavor === FLAVOR_V020_V021 && global.MemosApiV020V021) {
global.MemosApiV020V021.uploadResourceBlob( global.MemosApiV020V021.uploadResourceBlob(
@@ -396,7 +401,7 @@
info, info,
payload, payload,
function (resp) { function (resp) {
const entity = (resp && resp.resource) || resp const entity = (resp && (resp.attachment || resp.resource)) || resp
if (success) success(normalizeUploadedItem(entity, nextName)) if (success) success(normalizeUploadedItem(entity, nextName))
}, },
fail fail
@@ -416,9 +421,9 @@
} }
requestJson({ requestJson({
url: info.apiUrl + 'api/v1/' + memoName, url: info.apiUrl + 'api/v1/' + memoName + '?updateMask=state',
type: 'PATCH', type: 'PATCH',
data: JSON.stringify({ state: 'ARCHIVED' }), data: JSON.stringify({ name: memoName, state: 'ARCHIVED' }),
contentType: 'application/json', contentType: 'application/json',
dataType: 'json', dataType: 'json',
headers: { Authorization: 'Bearer ' + info.apiTokens } headers: { Authorization: 'Bearer ' + info.apiTokens }
@@ -458,24 +463,35 @@
return return
} }
const uploadedItems = Array.isArray(payload.resourceIdList) ? payload.resourceIdList : []
const attachmentReferences = uploadedItems
.filter(function (item) {
return item && typeof item.name === 'string' && item.name.indexOf('attachments/') === 0
})
.map(function (item) {
return { name: item.name }
})
const canCreateWithAttachments = flavor === 'modern' && attachmentReferences.length === uploadedItems.length
requestJson({ requestJson({
url: info.apiUrl + 'api/v1/memos', url: info.apiUrl + 'api/v1/memos',
type: 'POST', type: 'POST',
data: JSON.stringify({ data: JSON.stringify({
content: payload.content, content: payload.content,
visibility: payload.visibility visibility: payload.visibility,
...(canCreateWithAttachments ? { attachments: attachmentReferences } : {})
}), }),
contentType: 'application/json', contentType: 'application/json',
dataType: 'json', dataType: 'json',
headers: { Authorization: 'Bearer ' + info.apiTokens } headers: { Authorization: 'Bearer ' + info.apiTokens }
}, function (data) { }, function (data) {
const createdName = data && data.name ? data.name : data && data.memo && data.memo.name ? data.memo.name : '' const createdName = data && data.name ? data.name : data && data.memo && data.memo.name ? data.memo.name : ''
const resources = Array.isArray(payload.resourceIdList) ? payload.resourceIdList : [] const resources = uploadedItems
if (!createdName) { if (!createdName) {
if (success) success(data) if (success) success(data)
return return
} }
if (resources.length === 0) { if (resources.length === 0 || canCreateWithAttachments) {
getMemo(createdName, success, fail) getMemo(createdName, success, fail)
return return
} }
@@ -497,7 +513,7 @@
return { return {
flavor: flavor, flavor: flavor,
needsAuthenticatedImagePreview: function () { needsAuthenticatedImagePreview: function () {
return flavor === FLAVOR_V020_V021 return true
}, },
listTags: listTags, listTags: listTags,
searchMemos: searchMemos, searchMemos: searchMemos,
@@ -518,4 +534,4 @@
probeFlavor: probeFlavor, probeFlavor: probeFlavor,
resolve: resolve resolve: resolve
} }
})(window) })(window)
+47 -108
View File
@@ -151,10 +151,53 @@
return copy return copy
} }
function uploadResource() {
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 (xhr) {
if (!xhr || xhr.status !== 400) {
if (onFail) onFail(xhr)
return
}
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 (xhr2) {
if (onFail) onFail(xhr2)
})
})
}
// Attachment visibility was removed in Memos v0.26. Send the current
// attachment shape first and retain the resource endpoint as a legacy fallback.
global.$ global.$
.ajax({ .ajax({
url: urlAttachments, url: urlAttachments,
data: JSON.stringify(payload), data: JSON.stringify(stripVisibility(payload)),
type: 'POST', type: 'POST',
cache: false, cache: false,
processData: false, processData: false,
@@ -166,115 +209,11 @@
onSuccess(data, 'attachments') onSuccess(data, 'attachments')
}) })
.fail(function (xhr) { .fail(function (xhr) {
if (xhr && xhr.status === 400) { if (isNotFoundLikeXhr(xhr)) {
global.$ uploadResource()
.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 return
} }
if (onFail) onFail(xhr)
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)
})
}) })
} }
+56 -9
View File
@@ -201,22 +201,69 @@
function getTagSuggestion(info, success, fail) { function getTagSuggestion(info, success, fail) {
const headers = { Authorization: 'Bearer ' + info.apiTokens } const headers = { Authorization: 'Bearer ' + info.apiTokens }
function normalizeTagList(data) {
const list = Array.isArray(data) ? data : Array.isArray(data.tags) ? data.tags : []
return 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)
}
function normalizeSuggestionList(data) {
const list = Array.isArray(data) ? data : []
return list
.map(function (s) {
return String(s).replace(/^#/, '').trim()
})
.filter(Boolean)
}
function mergeUnique(a, b) {
return [...new Set((a || []).concat(b || []))]
}
let suggestionList = []
let listList = []
let pending = 2
function finish() {
if (--pending !== 0) return
if (success) success(mergeUnique(suggestionList, listList))
}
requestGet( requestGet(
info.apiUrl + 'api/v1/tag/suggestion', info.apiUrl + 'api/v1/tag/suggestion',
headers, headers,
function (data) { function (data) {
const list = Array.isArray(data) ? data : [] suggestionList = normalizeSuggestionList(data)
const out = list finish()
.map(function (s) { },
return String(s).replace(/^#/, '').trim() function (xhr) {
}) // Ignore failures from the suggestion endpoint; the list endpoint may still work.
.filter(Boolean) if (xhr && xhr.status !== 404 && xhr.status !== 405 && fail) fail(xhr)
if (success) success(out) else finish()
}
)
requestGet(
info.apiUrl + 'api/v1/tag',
headers,
function (data) {
listList = normalizeTagList(data)
finish()
}, },
function (xhr) { function (xhr) {
// Some forks might only expose list.
if (isNotFoundLikeXhr(xhr)) { if (isNotFoundLikeXhr(xhr)) {
getTagList(info, success, fail) finish()
return return
} }
if (fail) fail(xhr) if (fail) fail(xhr)
+1
View File
@@ -0,0 +1 @@
!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n(require("dayjs")):"function"==typeof define&&define.amd?define(["dayjs"],n):(e="undefined"!=typeof globalThis?globalThis:e||self).dayjs_locale_de=n(e.dayjs)}(this,(function(e){"use strict";function n(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var t=n(e),a={name:"de",weekdays:"Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag".split("_"),weekdaysShort:"So._Mo._Di._Mi._Do._Fr._Sa.".split("_"),weekdaysMin:"So_Mo_Di_Mi_Do_Fr_Sa".split("_"),months:"Januar_Februar_März_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember".split("_"),monthsShort:"Jan._Feb._März_Apr._Mai_Juni_Juli_Aug._Sept._Okt._Nov._Dez.".split("_"),weekStart:1,formats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY HH:mm",LLLL:"dddd, D. MMMM YYYY HH:mm"},relativeTime:{future:"in %s",past:"vor %s",s:"ein paar Sekunden",m:"einer Minute",mm:"%d Minuten",h:"einer Stunde",hh:"%d Stunden",d:"einem Tag",dd:"%d Tagen",M:"einem Monat",MM:"%d Monaten",y:"einem Jahr",yy:"%d Jahren"},ordinal:function(e){return e+"."}};return t.default.locale(a,null,!0),a}));
+1
View File
@@ -0,0 +1 @@
!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n(require("dayjs")):"function"==typeof define&&define.amd?define(["dayjs"],n):(e="undefined"!=typeof globalThis?globalThis:e||self).dayjs_locale_es=n(e.dayjs)}(this,(function(e){"use strict";function n(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var t=n(e),a={name:"es",weekdays:"domingo_lunes_martes_miércoles_jueves_viernes_sábado".split("_"),weekdaysShort:"dom._lun._mar._mié._jue._vie._sáb.".split("_"),weekdaysMin:"do_lu_ma_mi_ju_vi_sá".split("_"),months:"enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre".split("_"),monthsShort:"ene._feb._mar._abr._may._jun._jul._ago._sept._oct._nov._dic.".split("_"),weekStart:1,formats:{LT:"H:mm",LTS:"H:mm:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY H:mm",LLLL:"dddd, D [de] MMMM [de] YYYY H:mm"},relativeTime:{future:"en %s",past:"hace %s",s:"unos segundos",m:"un minuto",mm:"%d minutos",h:"una hora",hh:"%d horas",d:"un día",dd:"%d días",M:"un mes",MM:"%d meses",y:"un año",yy:"%d años"},ordinal:function(e){return e+"º"}};return t.default.locale(a,null,!0),a}));
+1
View File
@@ -0,0 +1 @@
!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n(require("dayjs")):"function"==typeof define&&define.amd?define(["dayjs"],n):(e="undefined"!=typeof globalThis?globalThis:e||self).dayjs_locale_fr=n(e.dayjs)}(this,(function(e){"use strict";function n(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var t=n(e),a={name:"fr",weekdays:"dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi".split("_"),weekdaysShort:"dim._lun._mar._mer._jeu._ven._sam.".split("_"),weekdaysMin:"di_lu_ma_me_je_ve_sa".split("_"),months:"janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre".split("_"),monthsShort:"janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.".split("_"),weekStart:1,formats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},relativeTime:{future:"dans %s",past:"il y a %s",s:"quelques secondes",m:"une minute",mm:"%d minutes",h:"une heure",hh:"%d heures",d:"un jour",dd:"%d jours",M:"un mois",MM:"%d mois",y:"un an",yy:"%d ans"},ordinal:function(e){return e+(1===e?"er":"")}};return t.default.locale(a,null,!0),a}));
+7 -1
View File
@@ -1,6 +1,6 @@
const UI_LANGUAGE_STORAGE_KEY = 'uiLanguage' const UI_LANGUAGE_STORAGE_KEY = 'uiLanguage'
const SUPPORTED_UI_LANGUAGES = new Set(['auto', 'en', 'zh_CN', 'ja', 'ko']) const SUPPORTED_UI_LANGUAGES = new Set(['auto', 'en', 'de', 'fr', 'es', 'zh_CN', 'ja', 'ko'])
function normalizeUiLanguage(value) { function normalizeUiLanguage(value) {
const lang = String(value || 'auto') const lang = String(value || 'auto')
@@ -49,6 +49,9 @@ let overrideMessages = null
function getLanguageToggleLabel(lang) { function getLanguageToggleLabel(lang) {
if (lang === 'en') return 'EN' if (lang === 'en') return 'EN'
if (lang === 'de') return 'DE'
if (lang === 'fr') return 'FR'
if (lang === 'es') return 'ES'
if (lang === 'zh_CN') return '中' if (lang === 'zh_CN') return '中'
if (lang === 'ja') return '日' if (lang === 'ja') return '日'
if (lang === 'ko') return '한' if (lang === 'ko') return '한'
@@ -132,6 +135,9 @@ function applyStaticI18n() {
// Language switcher // Language switcher
setText('langOptionAuto', 'langAuto') setText('langOptionAuto', 'langAuto')
setText('langOptionEn', 'langEnglish') setText('langOptionEn', 'langEnglish')
setText('langOptionDe', 'langGerman')
setText('langOptionFr', 'langFrench')
setText('langOptionEs', 'langSpanish')
setText('langOptionZhCN', 'langChineseSimplified') setText('langOptionZhCN', 'langChineseSimplified')
setText('langOptionJa', 'langJapanese') setText('langOptionJa', 'langJapanese')
setText('langOptionKo', 'langKorean') setText('langOptionKo', 'langKorean')
+75 -11
View File
@@ -183,6 +183,21 @@ function msg(key) {
function applyDayjsLocaleByUiLanguage(uiLang) { function applyDayjsLocaleByUiLanguage(uiLang) {
const lang = String(uiLang || 'auto') const lang = String(uiLang || 'auto')
if (lang === 'de') {
dayjs.locale('de')
return
}
if (lang === 'fr') {
dayjs.locale('fr')
return
}
if (lang === 'es') {
dayjs.locale('es')
return
}
if (lang === 'zh_CN') { if (lang === 'zh_CN') {
dayjs.locale('zh-cn') dayjs.locale('zh-cn')
return return
@@ -205,6 +220,18 @@ function applyDayjsLocaleByUiLanguage(uiLang) {
// auto: best-effort infer from browser UI language // auto: best-effort infer from browser UI language
const ui = String(chrome.i18n.getUILanguage ? chrome.i18n.getUILanguage() : '').toLowerCase() const ui = String(chrome.i18n.getUILanguage ? chrome.i18n.getUILanguage() : '').toLowerCase()
if (ui.startsWith('de')) {
dayjs.locale('de')
return
}
if (ui.startsWith('fr')) {
dayjs.locale('fr')
return
}
if (ui.startsWith('es')) {
dayjs.locale('es')
return
}
if (ui.startsWith('zh')) { if (ui.startsWith('zh')) {
dayjs.locale('zh-cn') dayjs.locale('zh-cn')
return return
@@ -319,6 +346,8 @@ function getMemoUid(memo) {
if (!memo) return '' if (!memo) return ''
if (memo.uid != null && memo.uid !== '') return String(memo.uid) if (memo.uid != null && memo.uid !== '') return String(memo.uid)
if (typeof memo.name === 'string' && memo.name) return memo.name.split('/').pop() if (typeof memo.name === 'string' && memo.name) return memo.name.split('/').pop()
// v0.18.2 only exposes a numeric id.
if (memo.id != null && memo.id !== '') return String(memo.id)
return '' return ''
} }
@@ -478,6 +507,13 @@ function buildV1ResourceStreamUrl(info, resource) {
} }
if (root && !root.endsWith('/')) root += '/' if (root && !root.endsWith('/')) root += '/'
if (resource.externalLink) return resource.externalLink
const isV023 = info.apiFlavor === 'v023'
const name = typeof resource.name === 'string' ? resource.name.trim() : ''
const filename = typeof resource.filename === 'string' ? resource.filename : ''
function isImageResource(r) { function isImageResource(r) {
if (!r) return false if (!r) return false
const t = typeof r.type === 'string' ? r.type.toLowerCase() : '' const t = typeof r.type === 'string' ? r.type.toLowerCase() : ''
@@ -486,6 +522,22 @@ function buildV1ResourceStreamUrl(info, resource) {
return /\.(png|jpe?g|gif|webp|bmp|svg|avif|heic)$/.test(fn) return /\.(png|jpe?g|gif|webp|bmp|svg|avif|heic)$/.test(fn)
} }
// v0.23+: resources are served as static files via /file/{name}/{filename}.
if (isV023) {
if (resource.externalLink) return resource.externalLink
const resourceName = name.indexOf('resources/') === 0 ? name : ''
if (resourceName && filename) {
const url = root + 'file/' + resourceName + '/' + filename.split('/').map(encodeURIComponent).join('/')
return isImageResource(resource) ? url + '?thumbnail=true' : url
}
return ''
}
if (name.indexOf('attachments/') === 0 && filename) {
const url = root + 'file/' + name.split('/').map(encodeURIComponent).join('/') + '/' + encodeURIComponent(filename)
return isImageResource(resource) ? url + '?thumbnail=true' : url
}
function isProbablyUid(s) { function isProbablyUid(s) {
if (typeof s !== 'string') return false if (typeof s !== 'string') return false
const v = s.trim() const v = s.trim()
@@ -505,6 +557,11 @@ function buildV1ResourceStreamUrl(info, resource) {
const uid = typeof uidRaw === 'string' ? uidRaw : uidRaw != null ? String(uidRaw) : '' const uid = typeof uidRaw === 'string' ? uidRaw : uidRaw != null ? String(uidRaw) : ''
if (uid.trim() !== '') return buildStreamUrl(uid.trim()) if (uid.trim() !== '') return buildStreamUrl(uid.trim())
// Fallback for older resource shapes.
// In some memo payloads, the uid may appear as `name` directly.
// Example: name="ETU6hjuR..." should map to /o/r/:uid, not /file/:name/:filename.
if (isProbablyUid(name)) return buildStreamUrl(name.trim())
// Legacy versions (e.g. v0.18) may only expose numeric `id` without `uid/name`. // Legacy versions (e.g. v0.18) may only expose numeric `id` without `uid/name`.
const idRaw = resource.id != null ? resource.id : resource.ID != null ? resource.ID : resource.Id const idRaw = resource.id != null ? resource.id : resource.ID != null ? resource.ID : resource.Id
const id = typeof idRaw === 'number' && Number.isFinite(idRaw) const id = typeof idRaw === 'number' && Number.isFinite(idRaw)
@@ -514,14 +571,7 @@ function buildV1ResourceStreamUrl(info, resource) {
: '' : ''
if (id) return buildStreamUrl(id) if (id) return buildStreamUrl(id)
// Fallback for older resource shapes. const fileId = resource.publicId || filename
const name = typeof resource.name === 'string' ? resource.name : ''
// In some memo payloads, the uid may appear as `name` directly.
// Example: name="ETU6hjuR..." should map to /o/r/:uid, not /file/:name/:filename.
if (isProbablyUid(name)) return buildStreamUrl(name.trim())
const fileId = resource.publicId || resource.filename
if (name && fileId) return root + 'file/' + name + '/' + fileId if (name && fileId) return root + 'file/' + name + '/' + fileId
return '' return ''
} }
@@ -911,11 +961,17 @@ $('#opensite').click(function () {
// 0.23.1版本 GET api/v1/{parent}/tags 接口已移除,参考 https://github.com/usememos/memos/issues/4161 // 0.23.1版本 GET api/v1/{parent}/tags 接口已移除,参考 https://github.com/usememos/memos/issues/4161
$('#tags').click(function () { $('#tags').click(function () {
get_info(function (info) { get_info(function (info) {
console.log('[memos-bber] tags click info:', info)
if (info.apiUrl) { if (info.apiUrl) {
var tagDom = ""; var tagDom = "";
const adapter = getApiAdapter(info) const adapter = getApiAdapter(info)
if (!adapter) {
console.warn('[memos-bber] no adapter for tags')
return
}
const renderTags = function (tags) { const renderTags = function (tags) {
console.log('[memos-bber] tags result:', tags)
const uniTags = [...new Set((Array.isArray(tags) ? tags : []).filter(Boolean))] const uniTags = [...new Set((Array.isArray(tags) ? tags : []).filter(Boolean))]
$.each(uniTags, function (_, tag) { $.each(uniTags, function (_, tag) {
tagDom += '<span class="item-container">#' + tag + '</span>'; tagDom += '<span class="item-container">#' + tag + '</span>';
@@ -924,7 +980,8 @@ $('#tags').click(function () {
$("#taglist").html(tagDom).slideToggle(500) $("#taglist").html(tagDom).slideToggle(500)
} }
adapter.listTags(renderTags, function () { adapter.listTags(renderTags, function (err) {
console.error('[memos-bber] tags failed:', err)
$.message({ message: msg('placeApiUrl') }) $.message({ message: msg('placeApiUrl') })
}) })
} else { } else {
@@ -1042,17 +1099,24 @@ $('#search').click(function () {
$('#random').click(function () { $('#random').click(function () {
get_info(function (info) { get_info(function (info) {
console.log('[memos-bber] random click info:', info)
if (info.status) { if (info.status) {
$("#randomlist").html('').hide() $("#randomlist").html('').hide()
const adapter = getApiAdapter(info) const adapter = getApiAdapter(info)
if (!adapter) {
console.warn('[memos-bber] no adapter for random')
return
}
adapter.listRandomMemos( adapter.listRandomMemos(
function (memos) { function (memos) {
console.log('[memos-bber] random result count:', memos && memos.length)
let randomNum = Math.floor(Math.random() * (memos.length)); let randomNum = Math.floor(Math.random() * (memos.length));
var randomData = memos[randomNum] var randomData = memos[randomNum]
randDom(randomData) randDom(randomData)
}, },
function () { function (err) {
console.error('[memos-bber] random failed:', err)
$.message({ message: msg('placeApiUrl') }) $.message({ message: msg('placeApiUrl') })
} }
) )
@@ -1281,4 +1345,4 @@ function sendText(preparedContent) {
}) })
} }
}) })
} }
+1 -1
View File
@@ -2,7 +2,7 @@
"manifest_version": 2, "manifest_version": 2,
"name": "__MSG_extName__", "name": "__MSG_extName__",
"default_locale": "en", "default_locale": "en",
"version": "2026.4.24", "version": "2026.8.5",
"browser_action": { "browser_action": {
"default_popup": "popup.html", "default_popup": "popup.html",
"default_icon": "assets/logo_24x24.png", "default_icon": "assets/logo_24x24.png",
+6
View File
@@ -25,6 +25,9 @@
<div id="langMenu" class="lang-menu hidden" role="menu" aria-labelledby="langToggle"> <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="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="langOptionEn" class="lang-menu-item" type="button" data-lang="en" role="menuitemradio" aria-checked="false"></button>
<button id="langOptionDe" class="lang-menu-item" type="button" data-lang="de" role="menuitemradio" aria-checked="false"></button>
<button id="langOptionFr" class="lang-menu-item" type="button" data-lang="fr" role="menuitemradio" aria-checked="false"></button>
<button id="langOptionEs" class="lang-menu-item" type="button" data-lang="es" 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="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="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> <button id="langOptionKo" class="lang-menu-item" type="button" data-lang="ko" role="menuitemradio" aria-checked="false"></button>
@@ -180,6 +183,9 @@
<script src="../js/jquery.min.js"></script> <script src="../js/jquery.min.js"></script>
<script src="../js/message.js"></script> <script src="../js/message.js"></script>
<script src="../js/dayjs.min.js"></script> <script src="../js/dayjs.min.js"></script>
<script src="../js/de.js"></script>
<script src="../js/fr.js"></script>
<script src="../js/es.js"></script>
<script src="../js/zh-cn.js"></script> <script src="../js/zh-cn.js"></script>
<script src="../js/ja.js"></script> <script src="../js/ja.js"></script>
<script src="../js/ko.js"></script> <script src="../js/ko.js"></script>