fix: 适配 Memos 0.29.x 附件 API

This commit is contained in:
Windyskr
2026-08-05 00:06:19 +08:00
parent 776ee343f4
commit e4529814a4
35 changed files with 272 additions and 383 deletions
+3 -1
View File
@@ -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,其他版本不支持
+1
View File
@@ -1,4 +1,5 @@
## 更新日志
- 20260805 适配 Memos 0.29.x Attachment API,修复附件上传、绑定和预览地址
- 20260425 新增德语/法语/西班牙语支持
- 20260423 优化 firefox 抖动问题和支持手机版,支持 edge 浏览器扩展
- 20260422 调整发送设置,支持仅发送附件
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -21,7 +21,7 @@
"message": "保存"
},
"supportedMemosVersion": {
"message": "Memos v0.15.0 - 0.27.x に対応"
"message": "Memos v0.15.0 - 0.29.x に対応"
},
"settingsConnectionTitle": {
"message": "接続設定"
+1 -1
View File
@@ -21,7 +21,7 @@
"message": "저장"
},
"supportedMemosVersion": {
"message": "Memos v0.15.0 - 0.27.x 호환"
"message": "Memos v0.15.0 - 0.29.x 호환"
},
"settingsConnectionTitle": {
"message": "연결 설정"
+1 -1
View File
@@ -21,7 +21,7 @@
"message": "保存"
},
"supportedMemosVersion": {
"message": "兼容 Memos v0.15.0 - 0.27.x"
"message": "兼容 Memos v0.15.0 - 0.29.x"
},
"settingsConnectionTitle": {
"message": "连接设置"
+22 -6
View File
@@ -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,
+47 -108
View File
@@ -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)
})
}
+10 -3
View File
@@ -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 ''
}
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -21,7 +21,7 @@
"message": "保存"
},
"supportedMemosVersion": {
"message": "Memos v0.15.0 - 0.27.x に対応"
"message": "Memos v0.15.0 - 0.29.x に対応"
},
"settingsConnectionTitle": {
"message": "接続設定"
+1 -1
View File
@@ -21,7 +21,7 @@
"message": "저장"
},
"supportedMemosVersion": {
"message": "Memos v0.15.0 - 0.27.x 호환"
"message": "Memos v0.15.0 - 0.29.x 호환"
},
"settingsConnectionTitle": {
"message": "연결 설정"
+1 -1
View File
@@ -21,7 +21,7 @@
"message": "保存"
},
"supportedMemosVersion": {
"message": "兼容 Memos v0.15.0 - 0.27.x"
"message": "兼容 Memos v0.15.0 - 0.29.x"
},
"settingsConnectionTitle": {
"message": "连接设置"
+22 -6
View File
@@ -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,
+47 -108
View File
@@ -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)
})
}
+10 -3
View File
@@ -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 ''
}
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -21,7 +21,7 @@
"message": "保存"
},
"supportedMemosVersion": {
"message": "Memos v0.15.0 - 0.27.x に対応"
"message": "Memos v0.15.0 - 0.29.x に対応"
},
"settingsConnectionTitle": {
"message": "接続設定"
+1 -1
View File
@@ -21,7 +21,7 @@
"message": "저장"
},
"supportedMemosVersion": {
"message": "Memos v0.15.0 - 0.27.x 호환"
"message": "Memos v0.15.0 - 0.29.x 호환"
},
"settingsConnectionTitle": {
"message": "연결 설정"
+1 -1
View File
@@ -21,7 +21,7 @@
"message": "保存"
},
"supportedMemosVersion": {
"message": "兼容 Memos v0.15.0 - 0.27.x"
"message": "兼容 Memos v0.15.0 - 0.29.x"
},
"settingsConnectionTitle": {
"message": "连接设置"
+22 -6
View File
@@ -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,
+47 -108
View File
@@ -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)
})
}
+10 -3
View File
@@ -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 ''
}
+1 -1
View File
@@ -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",