diff --git a/README.md b/README.md index 779e5a1..3b07673 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ 一款通过浏览器插件发布 [Memos](https://usememos.com/) 的插件。基于 iSpeak-bber 修改,原作者为 [DreamyTZK](https://www.antmoe.com/)。 +兼容 Memos v0.15.0 - v0.29.x,支持新版 Attachment API。 + [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/jonnyan404) ## 在线商店安装 @@ -18,4 +20,4 @@ Android ### 使用教程 - 在文本框输入想搜索的关键字,点击搜索按钮 -- 随机和搜索功能在`0.24`以上版本支持私有权限的 memo,其它版本不支持 +- 随机和搜索功能在 `0.24` 以上版本支持私有权限的 memo,其他版本不支持 diff --git a/change.log b/change.log index 37f35c9..c815983 100644 --- a/change.log +++ b/change.log @@ -1,4 +1,5 @@ ## 更新日志 +- 20260805 适配 Memos 0.29.x Attachment API,修复附件上传、绑定和预览地址 - 20260425 新增德语/法语/西班牙语支持 - 20260423 优化 firefox 抖动问题和支持手机版,支持 edge 浏览器扩展 - 20260422 调整发送设置,支持仅发送附件 @@ -69,4 +70,4 @@ 2022.10.24 添加 visiblity 发送设置。 - \ No newline at end of file + diff --git a/chrome/_locales/de/messages.json b/chrome/_locales/de/messages.json index 611e0b4..d3edf9c 100644 --- a/chrome/_locales/de/messages.json +++ b/chrome/_locales/de/messages.json @@ -21,7 +21,7 @@ "message": "Speichern" }, "supportedMemosVersion": { - "message": "Kompatibel mit Memos v0.15.0 - 0.27.x" + "message": "Kompatibel mit Memos v0.15.0 - 0.29.x" }, "settingsConnectionTitle": { "message": "Verbindung" diff --git a/chrome/_locales/en/messages.json b/chrome/_locales/en/messages.json index 99ad187..6bdc903 100644 --- a/chrome/_locales/en/messages.json +++ b/chrome/_locales/en/messages.json @@ -21,7 +21,7 @@ "message": "Save" }, "supportedMemosVersion": { - "message": "Compatible with Memos v0.15.0 - 0.27.x" + "message": "Compatible with Memos v0.15.0 - 0.29.x" }, "settingsConnectionTitle": { "message": "Connection" diff --git a/chrome/_locales/es/messages.json b/chrome/_locales/es/messages.json index 1d9d6b3..b584e01 100644 --- a/chrome/_locales/es/messages.json +++ b/chrome/_locales/es/messages.json @@ -21,7 +21,7 @@ "message": "Guardar" }, "supportedMemosVersion": { - "message": "Compatible con Memos v0.15.0 - 0.27.x" + "message": "Compatible con Memos v0.15.0 - 0.29.x" }, "settingsConnectionTitle": { "message": "Conexión" diff --git a/chrome/_locales/fr/messages.json b/chrome/_locales/fr/messages.json index 0362bfc..5c81dad 100644 --- a/chrome/_locales/fr/messages.json +++ b/chrome/_locales/fr/messages.json @@ -21,7 +21,7 @@ "message": "Enregistrer" }, "supportedMemosVersion": { - "message": "Compatible avec Memos v0.15.0 - 0.27.x" + "message": "Compatible avec Memos v0.15.0 - 0.29.x" }, "settingsConnectionTitle": { "message": "Connexion" diff --git a/chrome/_locales/ja/messages.json b/chrome/_locales/ja/messages.json index 9fadab7..b3ea4e4 100644 --- a/chrome/_locales/ja/messages.json +++ b/chrome/_locales/ja/messages.json @@ -21,7 +21,7 @@ "message": "保存" }, "supportedMemosVersion": { - "message": "Memos v0.15.0 - 0.27.x に対応" + "message": "Memos v0.15.0 - 0.29.x に対応" }, "settingsConnectionTitle": { "message": "接続設定" diff --git a/chrome/_locales/ko/messages.json b/chrome/_locales/ko/messages.json index 32ee389..1860337 100644 --- a/chrome/_locales/ko/messages.json +++ b/chrome/_locales/ko/messages.json @@ -21,7 +21,7 @@ "message": "저장" }, "supportedMemosVersion": { - "message": "Memos v0.15.0 - 0.27.x 호환" + "message": "Memos v0.15.0 - 0.29.x 호환" }, "settingsConnectionTitle": { "message": "연결 설정" diff --git a/chrome/_locales/zh_CN/messages.json b/chrome/_locales/zh_CN/messages.json index c4ae262..0af3270 100644 --- a/chrome/_locales/zh_CN/messages.json +++ b/chrome/_locales/zh_CN/messages.json @@ -21,7 +21,7 @@ "message": "保存" }, "supportedMemosVersion": { - "message": "兼容 Memos v0.15.0 - 0.27.x" + "message": "兼容 Memos v0.15.0 - 0.29.x" }, "settingsConnectionTitle": { "message": "连接设置" diff --git a/chrome/js/compat/memosApi.adapter.js b/chrome/js/compat/memosApi.adapter.js index d3efb23..333ced2 100644 --- a/chrome/js/compat/memosApi.adapter.js +++ b/chrome/js/compat/memosApi.adapter.js @@ -187,6 +187,7 @@ function normalizeUploadedItem(entity, fallbackFilename) { if (!entity) return null + entity = entity.attachment || entity.resource || entity const inferredId = (function () { const value = entity.id != null ? entity.id : entity.ID != null ? entity.ID : entity.Id if (typeof value === 'number' && Number.isFinite(value)) return Math.floor(value) @@ -204,7 +205,11 @@ name: name, filename: entity.filename || fallbackFilename || name, 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 } } @@ -396,7 +401,7 @@ info, payload, function (resp) { - const entity = (resp && resp.resource) || resp + const entity = (resp && (resp.attachment || resp.resource)) || resp if (success) success(normalizeUploadedItem(entity, nextName)) }, fail @@ -458,24 +463,35 @@ 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({ url: info.apiUrl + 'api/v1/memos', type: 'POST', data: JSON.stringify({ content: payload.content, - visibility: payload.visibility + visibility: payload.visibility, + ...(canCreateWithAttachments ? { attachments: attachmentReferences } : {}) }), contentType: 'application/json', dataType: 'json', headers: { Authorization: 'Bearer ' + info.apiTokens } }, function (data) { const createdName = data && data.name ? data.name : data && data.memo && data.memo.name ? data.memo.name : '' - const resources = Array.isArray(payload.resourceIdList) ? payload.resourceIdList : [] + const resources = uploadedItems if (!createdName) { if (success) success(data) return } - if (resources.length === 0) { + if (resources.length === 0 || canCreateWithAttachments) { getMemo(createdName, success, fail) return } @@ -497,7 +513,7 @@ return { flavor: flavor, needsAuthenticatedImagePreview: function () { - return flavor === FLAVOR_V020_V021 + return true }, listTags: listTags, searchMemos: searchMemos, @@ -518,4 +534,4 @@ probeFlavor: probeFlavor, resolve: resolve } -})(window) \ No newline at end of file +})(window) diff --git a/chrome/js/compat/memosApi.modern.js b/chrome/js/compat/memosApi.modern.js index 4449eed..66c4dec 100644 --- a/chrome/js/compat/memosApi.modern.js +++ b/chrome/js/compat/memosApi.modern.js @@ -151,10 +151,53 @@ 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.$ .ajax({ url: urlAttachments, - data: JSON.stringify(payload), + data: JSON.stringify(stripVisibility(payload)), type: 'POST', cache: false, processData: false, @@ -166,115 +209,11 @@ onSuccess(data, 'attachments') }) .fail(function (xhr) { - if (xhr && xhr.status === 400) { - global.$ - .ajax({ - url: urlAttachments, - data: JSON.stringify(stripVisibility(payload)), - type: 'POST', - cache: false, - processData: false, - contentType: 'application/json', - dataType: 'json', - headers: headers - }) - .done(function (data) { - onSuccess(data, 'attachments') - }) - .fail(function (xhrRetry) { - if (!isNotFoundLikeXhr(xhrRetry)) { - if (onFail) onFail(xhrRetry) - return - } - // fall through to resources below - xhr = xhrRetry - if (!isNotFoundLikeXhr(xhr)) { - if (onFail) onFail(xhr) - return - } - global.$ - .ajax({ - url: urlResources, - data: JSON.stringify(payload), - type: 'POST', - cache: false, - processData: false, - contentType: 'application/json', - dataType: 'json', - headers: headers - }) - .done(function (data) { - onSuccess(data, 'resources') - }) - .fail(function (xhr2) { - if (xhr2 && xhr2.status === 400) { - global.$ - .ajax({ - url: urlResources, - data: JSON.stringify(stripVisibility(payload)), - type: 'POST', - cache: false, - processData: false, - contentType: 'application/json', - dataType: 'json', - headers: headers - }) - .done(function (data) { - onSuccess(data, 'resources') - }) - .fail(function (xhr3) { - if (onFail) onFail(xhr3) - }) - return - } - if (onFail) onFail(xhr2) - }) - }) + if (isNotFoundLikeXhr(xhr)) { + uploadResource() return } - - if (!isNotFoundLikeXhr(xhr)) { - if (onFail) onFail(xhr) - return - } - - global.$ - .ajax({ - url: urlResources, - data: JSON.stringify(payload), - type: 'POST', - cache: false, - processData: false, - contentType: 'application/json', - dataType: 'json', - headers: headers - }) - .done(function (data) { - onSuccess(data, 'resources') - }) - .fail(function (xhr2) { - if (xhr2 && xhr2.status === 400) { - global.$ - .ajax({ - url: urlResources, - data: JSON.stringify(stripVisibility(payload)), - type: 'POST', - cache: false, - processData: false, - contentType: 'application/json', - dataType: 'json', - headers: headers - }) - .done(function (data) { - onSuccess(data, 'resources') - }) - .fail(function (xhr3) { - if (onFail) onFail(xhr3) - }) - return - } - if (onFail) onFail(xhr2) - }) + if (onFail) onFail(xhr) }) } diff --git a/chrome/js/oper.js b/chrome/js/oper.js index 99b3ede..cdda987 100644 --- a/chrome/js/oper.js +++ b/chrome/js/oper.js @@ -480,6 +480,15 @@ function buildV1ResourceStreamUrl(info, resource) { } if (root && !root.endsWith('/')) root += '/' + if (resource.externalLink) return resource.externalLink + + const name = typeof resource.name === 'string' ? resource.name.trim() : '' + const filename = typeof resource.filename === 'string' ? resource.filename : '' + 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 isImageResource(r) { if (!r) return false const t = typeof r.type === 'string' ? r.type.toLowerCase() : '' @@ -517,13 +526,11 @@ function buildV1ResourceStreamUrl(info, resource) { if (id) return buildStreamUrl(id) // Fallback for older resource shapes. - 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 + const fileId = resource.publicId || filename if (name && fileId) return root + 'file/' + name + '/' + fileId return '' } @@ -1283,4 +1290,4 @@ function sendText(preparedContent) { }) } }) -} \ No newline at end of file +} diff --git a/chrome/manifest.json b/chrome/manifest.json index ee61808..b6ffe59 100644 --- a/chrome/manifest.json +++ b/chrome/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 3, "name": "__MSG_extName__", "default_locale": "en", - "version": "2026.04.25", + "version": "2026.08.05", "action": { "default_popup": "popup.html", "default_icon": "assets/logo_24x24.png", diff --git a/edge/_locales/de/messages.json b/edge/_locales/de/messages.json index 611e0b4..d3edf9c 100644 --- a/edge/_locales/de/messages.json +++ b/edge/_locales/de/messages.json @@ -21,7 +21,7 @@ "message": "Speichern" }, "supportedMemosVersion": { - "message": "Kompatibel mit Memos v0.15.0 - 0.27.x" + "message": "Kompatibel mit Memos v0.15.0 - 0.29.x" }, "settingsConnectionTitle": { "message": "Verbindung" diff --git a/edge/_locales/en/messages.json b/edge/_locales/en/messages.json index 99ad187..6bdc903 100644 --- a/edge/_locales/en/messages.json +++ b/edge/_locales/en/messages.json @@ -21,7 +21,7 @@ "message": "Save" }, "supportedMemosVersion": { - "message": "Compatible with Memos v0.15.0 - 0.27.x" + "message": "Compatible with Memos v0.15.0 - 0.29.x" }, "settingsConnectionTitle": { "message": "Connection" diff --git a/edge/_locales/es/messages.json b/edge/_locales/es/messages.json index 1d9d6b3..b584e01 100644 --- a/edge/_locales/es/messages.json +++ b/edge/_locales/es/messages.json @@ -21,7 +21,7 @@ "message": "Guardar" }, "supportedMemosVersion": { - "message": "Compatible con Memos v0.15.0 - 0.27.x" + "message": "Compatible con Memos v0.15.0 - 0.29.x" }, "settingsConnectionTitle": { "message": "Conexión" diff --git a/edge/_locales/fr/messages.json b/edge/_locales/fr/messages.json index 0362bfc..5c81dad 100644 --- a/edge/_locales/fr/messages.json +++ b/edge/_locales/fr/messages.json @@ -21,7 +21,7 @@ "message": "Enregistrer" }, "supportedMemosVersion": { - "message": "Compatible avec Memos v0.15.0 - 0.27.x" + "message": "Compatible avec Memos v0.15.0 - 0.29.x" }, "settingsConnectionTitle": { "message": "Connexion" diff --git a/edge/_locales/ja/messages.json b/edge/_locales/ja/messages.json index 9fadab7..b3ea4e4 100644 --- a/edge/_locales/ja/messages.json +++ b/edge/_locales/ja/messages.json @@ -21,7 +21,7 @@ "message": "保存" }, "supportedMemosVersion": { - "message": "Memos v0.15.0 - 0.27.x に対応" + "message": "Memos v0.15.0 - 0.29.x に対応" }, "settingsConnectionTitle": { "message": "接続設定" diff --git a/edge/_locales/ko/messages.json b/edge/_locales/ko/messages.json index 32ee389..1860337 100644 --- a/edge/_locales/ko/messages.json +++ b/edge/_locales/ko/messages.json @@ -21,7 +21,7 @@ "message": "저장" }, "supportedMemosVersion": { - "message": "Memos v0.15.0 - 0.27.x 호환" + "message": "Memos v0.15.0 - 0.29.x 호환" }, "settingsConnectionTitle": { "message": "연결 설정" diff --git a/edge/_locales/zh_CN/messages.json b/edge/_locales/zh_CN/messages.json index c4ae262..0af3270 100644 --- a/edge/_locales/zh_CN/messages.json +++ b/edge/_locales/zh_CN/messages.json @@ -21,7 +21,7 @@ "message": "保存" }, "supportedMemosVersion": { - "message": "兼容 Memos v0.15.0 - 0.27.x" + "message": "兼容 Memos v0.15.0 - 0.29.x" }, "settingsConnectionTitle": { "message": "连接设置" diff --git a/edge/js/compat/memosApi.adapter.js b/edge/js/compat/memosApi.adapter.js index d3efb23..333ced2 100644 --- a/edge/js/compat/memosApi.adapter.js +++ b/edge/js/compat/memosApi.adapter.js @@ -187,6 +187,7 @@ function normalizeUploadedItem(entity, fallbackFilename) { if (!entity) return null + entity = entity.attachment || entity.resource || entity const inferredId = (function () { const value = entity.id != null ? entity.id : entity.ID != null ? entity.ID : entity.Id if (typeof value === 'number' && Number.isFinite(value)) return Math.floor(value) @@ -204,7 +205,11 @@ name: name, filename: entity.filename || fallbackFilename || name, 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 } } @@ -396,7 +401,7 @@ info, payload, function (resp) { - const entity = (resp && resp.resource) || resp + const entity = (resp && (resp.attachment || resp.resource)) || resp if (success) success(normalizeUploadedItem(entity, nextName)) }, fail @@ -458,24 +463,35 @@ 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({ url: info.apiUrl + 'api/v1/memos', type: 'POST', data: JSON.stringify({ content: payload.content, - visibility: payload.visibility + visibility: payload.visibility, + ...(canCreateWithAttachments ? { attachments: attachmentReferences } : {}) }), contentType: 'application/json', dataType: 'json', headers: { Authorization: 'Bearer ' + info.apiTokens } }, function (data) { const createdName = data && data.name ? data.name : data && data.memo && data.memo.name ? data.memo.name : '' - const resources = Array.isArray(payload.resourceIdList) ? payload.resourceIdList : [] + const resources = uploadedItems if (!createdName) { if (success) success(data) return } - if (resources.length === 0) { + if (resources.length === 0 || canCreateWithAttachments) { getMemo(createdName, success, fail) return } @@ -497,7 +513,7 @@ return { flavor: flavor, needsAuthenticatedImagePreview: function () { - return flavor === FLAVOR_V020_V021 + return true }, listTags: listTags, searchMemos: searchMemos, @@ -518,4 +534,4 @@ probeFlavor: probeFlavor, resolve: resolve } -})(window) \ No newline at end of file +})(window) diff --git a/edge/js/compat/memosApi.modern.js b/edge/js/compat/memosApi.modern.js index 4449eed..66c4dec 100644 --- a/edge/js/compat/memosApi.modern.js +++ b/edge/js/compat/memosApi.modern.js @@ -151,10 +151,53 @@ 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.$ .ajax({ url: urlAttachments, - data: JSON.stringify(payload), + data: JSON.stringify(stripVisibility(payload)), type: 'POST', cache: false, processData: false, @@ -166,115 +209,11 @@ onSuccess(data, 'attachments') }) .fail(function (xhr) { - if (xhr && xhr.status === 400) { - global.$ - .ajax({ - url: urlAttachments, - data: JSON.stringify(stripVisibility(payload)), - type: 'POST', - cache: false, - processData: false, - contentType: 'application/json', - dataType: 'json', - headers: headers - }) - .done(function (data) { - onSuccess(data, 'attachments') - }) - .fail(function (xhrRetry) { - if (!isNotFoundLikeXhr(xhrRetry)) { - if (onFail) onFail(xhrRetry) - return - } - // fall through to resources below - xhr = xhrRetry - if (!isNotFoundLikeXhr(xhr)) { - if (onFail) onFail(xhr) - return - } - global.$ - .ajax({ - url: urlResources, - data: JSON.stringify(payload), - type: 'POST', - cache: false, - processData: false, - contentType: 'application/json', - dataType: 'json', - headers: headers - }) - .done(function (data) { - onSuccess(data, 'resources') - }) - .fail(function (xhr2) { - if (xhr2 && xhr2.status === 400) { - global.$ - .ajax({ - url: urlResources, - data: JSON.stringify(stripVisibility(payload)), - type: 'POST', - cache: false, - processData: false, - contentType: 'application/json', - dataType: 'json', - headers: headers - }) - .done(function (data) { - onSuccess(data, 'resources') - }) - .fail(function (xhr3) { - if (onFail) onFail(xhr3) - }) - return - } - if (onFail) onFail(xhr2) - }) - }) + if (isNotFoundLikeXhr(xhr)) { + uploadResource() return } - - if (!isNotFoundLikeXhr(xhr)) { - if (onFail) onFail(xhr) - return - } - - global.$ - .ajax({ - url: urlResources, - data: JSON.stringify(payload), - type: 'POST', - cache: false, - processData: false, - contentType: 'application/json', - dataType: 'json', - headers: headers - }) - .done(function (data) { - onSuccess(data, 'resources') - }) - .fail(function (xhr2) { - if (xhr2 && xhr2.status === 400) { - global.$ - .ajax({ - url: urlResources, - data: JSON.stringify(stripVisibility(payload)), - type: 'POST', - cache: false, - processData: false, - contentType: 'application/json', - dataType: 'json', - headers: headers - }) - .done(function (data) { - onSuccess(data, 'resources') - }) - .fail(function (xhr3) { - if (onFail) onFail(xhr3) - }) - return - } - if (onFail) onFail(xhr2) - }) + if (onFail) onFail(xhr) }) } diff --git a/edge/js/oper.js b/edge/js/oper.js index 99b3ede..cdda987 100644 --- a/edge/js/oper.js +++ b/edge/js/oper.js @@ -480,6 +480,15 @@ function buildV1ResourceStreamUrl(info, resource) { } if (root && !root.endsWith('/')) root += '/' + if (resource.externalLink) return resource.externalLink + + const name = typeof resource.name === 'string' ? resource.name.trim() : '' + const filename = typeof resource.filename === 'string' ? resource.filename : '' + 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 isImageResource(r) { if (!r) return false const t = typeof r.type === 'string' ? r.type.toLowerCase() : '' @@ -517,13 +526,11 @@ function buildV1ResourceStreamUrl(info, resource) { if (id) return buildStreamUrl(id) // Fallback for older resource shapes. - 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 + const fileId = resource.publicId || filename if (name && fileId) return root + 'file/' + name + '/' + fileId return '' } @@ -1283,4 +1290,4 @@ function sendText(preparedContent) { }) } }) -} \ No newline at end of file +} diff --git a/edge/manifest.json b/edge/manifest.json index ee61808..b6ffe59 100644 --- a/edge/manifest.json +++ b/edge/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 3, "name": "__MSG_extName__", "default_locale": "en", - "version": "2026.04.25", + "version": "2026.08.05", "action": { "default_popup": "popup.html", "default_icon": "assets/logo_24x24.png", diff --git a/firefox/_locales/de/messages.json b/firefox/_locales/de/messages.json index 611e0b4..d3edf9c 100644 --- a/firefox/_locales/de/messages.json +++ b/firefox/_locales/de/messages.json @@ -21,7 +21,7 @@ "message": "Speichern" }, "supportedMemosVersion": { - "message": "Kompatibel mit Memos v0.15.0 - 0.27.x" + "message": "Kompatibel mit Memos v0.15.0 - 0.29.x" }, "settingsConnectionTitle": { "message": "Verbindung" diff --git a/firefox/_locales/en/messages.json b/firefox/_locales/en/messages.json index 99ad187..6bdc903 100644 --- a/firefox/_locales/en/messages.json +++ b/firefox/_locales/en/messages.json @@ -21,7 +21,7 @@ "message": "Save" }, "supportedMemosVersion": { - "message": "Compatible with Memos v0.15.0 - 0.27.x" + "message": "Compatible with Memos v0.15.0 - 0.29.x" }, "settingsConnectionTitle": { "message": "Connection" diff --git a/firefox/_locales/es/messages.json b/firefox/_locales/es/messages.json index 1d9d6b3..b584e01 100644 --- a/firefox/_locales/es/messages.json +++ b/firefox/_locales/es/messages.json @@ -21,7 +21,7 @@ "message": "Guardar" }, "supportedMemosVersion": { - "message": "Compatible con Memos v0.15.0 - 0.27.x" + "message": "Compatible con Memos v0.15.0 - 0.29.x" }, "settingsConnectionTitle": { "message": "Conexión" diff --git a/firefox/_locales/fr/messages.json b/firefox/_locales/fr/messages.json index 0362bfc..5c81dad 100644 --- a/firefox/_locales/fr/messages.json +++ b/firefox/_locales/fr/messages.json @@ -21,7 +21,7 @@ "message": "Enregistrer" }, "supportedMemosVersion": { - "message": "Compatible avec Memos v0.15.0 - 0.27.x" + "message": "Compatible avec Memos v0.15.0 - 0.29.x" }, "settingsConnectionTitle": { "message": "Connexion" diff --git a/firefox/_locales/ja/messages.json b/firefox/_locales/ja/messages.json index 9fadab7..b3ea4e4 100644 --- a/firefox/_locales/ja/messages.json +++ b/firefox/_locales/ja/messages.json @@ -21,7 +21,7 @@ "message": "保存" }, "supportedMemosVersion": { - "message": "Memos v0.15.0 - 0.27.x に対応" + "message": "Memos v0.15.0 - 0.29.x に対応" }, "settingsConnectionTitle": { "message": "接続設定" diff --git a/firefox/_locales/ko/messages.json b/firefox/_locales/ko/messages.json index 32ee389..1860337 100644 --- a/firefox/_locales/ko/messages.json +++ b/firefox/_locales/ko/messages.json @@ -21,7 +21,7 @@ "message": "저장" }, "supportedMemosVersion": { - "message": "Memos v0.15.0 - 0.27.x 호환" + "message": "Memos v0.15.0 - 0.29.x 호환" }, "settingsConnectionTitle": { "message": "연결 설정" diff --git a/firefox/_locales/zh_CN/messages.json b/firefox/_locales/zh_CN/messages.json index c4ae262..0af3270 100644 --- a/firefox/_locales/zh_CN/messages.json +++ b/firefox/_locales/zh_CN/messages.json @@ -21,7 +21,7 @@ "message": "保存" }, "supportedMemosVersion": { - "message": "兼容 Memos v0.15.0 - 0.27.x" + "message": "兼容 Memos v0.15.0 - 0.29.x" }, "settingsConnectionTitle": { "message": "连接设置" diff --git a/firefox/js/compat/memosApi.adapter.js b/firefox/js/compat/memosApi.adapter.js index d3efb23..333ced2 100644 --- a/firefox/js/compat/memosApi.adapter.js +++ b/firefox/js/compat/memosApi.adapter.js @@ -187,6 +187,7 @@ function normalizeUploadedItem(entity, fallbackFilename) { if (!entity) return null + entity = entity.attachment || entity.resource || entity const inferredId = (function () { const value = entity.id != null ? entity.id : entity.ID != null ? entity.ID : entity.Id if (typeof value === 'number' && Number.isFinite(value)) return Math.floor(value) @@ -204,7 +205,11 @@ name: name, filename: entity.filename || fallbackFilename || name, 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 } } @@ -396,7 +401,7 @@ info, payload, function (resp) { - const entity = (resp && resp.resource) || resp + const entity = (resp && (resp.attachment || resp.resource)) || resp if (success) success(normalizeUploadedItem(entity, nextName)) }, fail @@ -458,24 +463,35 @@ 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({ url: info.apiUrl + 'api/v1/memos', type: 'POST', data: JSON.stringify({ content: payload.content, - visibility: payload.visibility + visibility: payload.visibility, + ...(canCreateWithAttachments ? { attachments: attachmentReferences } : {}) }), contentType: 'application/json', dataType: 'json', headers: { Authorization: 'Bearer ' + info.apiTokens } }, function (data) { const createdName = data && data.name ? data.name : data && data.memo && data.memo.name ? data.memo.name : '' - const resources = Array.isArray(payload.resourceIdList) ? payload.resourceIdList : [] + const resources = uploadedItems if (!createdName) { if (success) success(data) return } - if (resources.length === 0) { + if (resources.length === 0 || canCreateWithAttachments) { getMemo(createdName, success, fail) return } @@ -497,7 +513,7 @@ return { flavor: flavor, needsAuthenticatedImagePreview: function () { - return flavor === FLAVOR_V020_V021 + return true }, listTags: listTags, searchMemos: searchMemos, @@ -518,4 +534,4 @@ probeFlavor: probeFlavor, resolve: resolve } -})(window) \ No newline at end of file +})(window) diff --git a/firefox/js/compat/memosApi.modern.js b/firefox/js/compat/memosApi.modern.js index 4449eed..66c4dec 100644 --- a/firefox/js/compat/memosApi.modern.js +++ b/firefox/js/compat/memosApi.modern.js @@ -151,10 +151,53 @@ 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.$ .ajax({ url: urlAttachments, - data: JSON.stringify(payload), + data: JSON.stringify(stripVisibility(payload)), type: 'POST', cache: false, processData: false, @@ -166,115 +209,11 @@ onSuccess(data, 'attachments') }) .fail(function (xhr) { - if (xhr && xhr.status === 400) { - global.$ - .ajax({ - url: urlAttachments, - data: JSON.stringify(stripVisibility(payload)), - type: 'POST', - cache: false, - processData: false, - contentType: 'application/json', - dataType: 'json', - headers: headers - }) - .done(function (data) { - onSuccess(data, 'attachments') - }) - .fail(function (xhrRetry) { - if (!isNotFoundLikeXhr(xhrRetry)) { - if (onFail) onFail(xhrRetry) - return - } - // fall through to resources below - xhr = xhrRetry - if (!isNotFoundLikeXhr(xhr)) { - if (onFail) onFail(xhr) - return - } - global.$ - .ajax({ - url: urlResources, - data: JSON.stringify(payload), - type: 'POST', - cache: false, - processData: false, - contentType: 'application/json', - dataType: 'json', - headers: headers - }) - .done(function (data) { - onSuccess(data, 'resources') - }) - .fail(function (xhr2) { - if (xhr2 && xhr2.status === 400) { - global.$ - .ajax({ - url: urlResources, - data: JSON.stringify(stripVisibility(payload)), - type: 'POST', - cache: false, - processData: false, - contentType: 'application/json', - dataType: 'json', - headers: headers - }) - .done(function (data) { - onSuccess(data, 'resources') - }) - .fail(function (xhr3) { - if (onFail) onFail(xhr3) - }) - return - } - if (onFail) onFail(xhr2) - }) - }) + if (isNotFoundLikeXhr(xhr)) { + uploadResource() return } - - if (!isNotFoundLikeXhr(xhr)) { - if (onFail) onFail(xhr) - return - } - - global.$ - .ajax({ - url: urlResources, - data: JSON.stringify(payload), - type: 'POST', - cache: false, - processData: false, - contentType: 'application/json', - dataType: 'json', - headers: headers - }) - .done(function (data) { - onSuccess(data, 'resources') - }) - .fail(function (xhr2) { - if (xhr2 && xhr2.status === 400) { - global.$ - .ajax({ - url: urlResources, - data: JSON.stringify(stripVisibility(payload)), - type: 'POST', - cache: false, - processData: false, - contentType: 'application/json', - dataType: 'json', - headers: headers - }) - .done(function (data) { - onSuccess(data, 'resources') - }) - .fail(function (xhr3) { - if (onFail) onFail(xhr3) - }) - return - } - if (onFail) onFail(xhr2) - }) + if (onFail) onFail(xhr) }) } diff --git a/firefox/js/oper.js b/firefox/js/oper.js index 7d4255c..be72188 100644 --- a/firefox/js/oper.js +++ b/firefox/js/oper.js @@ -505,6 +505,15 @@ function buildV1ResourceStreamUrl(info, resource) { } if (root && !root.endsWith('/')) root += '/' + if (resource.externalLink) return resource.externalLink + + const name = typeof resource.name === 'string' ? resource.name.trim() : '' + const filename = typeof resource.filename === 'string' ? resource.filename : '' + 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 isImageResource(r) { if (!r) return false const t = typeof r.type === 'string' ? r.type.toLowerCase() : '' @@ -542,13 +551,11 @@ function buildV1ResourceStreamUrl(info, resource) { if (id) return buildStreamUrl(id) // Fallback for older resource shapes. - 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 + const fileId = resource.publicId || filename if (name && fileId) return root + 'file/' + name + '/' + fileId return '' } @@ -1308,4 +1315,4 @@ function sendText(preparedContent) { }) } }) -} \ No newline at end of file +} diff --git a/firefox/manifest.json b/firefox/manifest.json index 334fedd..9e8fc5c 100644 --- a/firefox/manifest.json +++ b/firefox/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "__MSG_extName__", "default_locale": "en", - "version": "2026.4.25", + "version": "2026.8.5", "browser_action": { "default_popup": "popup.html", "default_icon": "assets/logo_24x24.png",