mirror of
https://github.com/Jonnyan404/memos-bber.git
synced 2026-08-23 00:41:39 +09:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c4da5f790a | |||
| 1361cc15db | |||
| afbb7ac5cc | |||
| e01ea6b0df | |||
| 5b0483007a | |||
| e4529814a4 |
@@ -1,5 +1,7 @@
|
|||||||
一款通过浏览器插件发布 [Memos](https://usememos.com/) 的插件。基于 iSpeak-bber 修改,原作者为 [DreamyTZK](https://www.antmoe.com/)。
|
一款通过浏览器插件发布 [Memos](https://usememos.com/) 的插件。基于 iSpeak-bber 修改,原作者为 [DreamyTZK](https://www.antmoe.com/)。
|
||||||
|
|
||||||
|
兼容 Memos v0.15.0 - v0.29.x,支持新版 Attachment API。
|
||||||
|
|
||||||
[](https://ko-fi.com/jonnyan404)
|
[](https://ko-fi.com/jonnyan404)
|
||||||
|
|
||||||
## 在线商店安装
|
## 在线商店安装
|
||||||
@@ -18,4 +20,4 @@ Android
|
|||||||
### 使用教程
|
### 使用教程
|
||||||
|
|
||||||
- 在文本框输入想搜索的关键字,点击搜索按钮
|
- 在文本框输入想搜索的关键字,点击搜索按钮
|
||||||
- 随机和搜索功能在`0.24`以上版本支持私有权限的 memo,其它版本不支持
|
- 随机和搜索功能在 `0.24` 以上版本支持私有权限的 memo,其他版本不支持
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
## 更新日志
|
## 更新日志
|
||||||
|
- 20260805 适配 Memos 0.28.x-0.30.x Attachment API,修复附件上传、绑定和预览地址;修复 0.19.x-0.23.x 图片预览 URL 错误使用 numeric id 的问题以及标签不显示问题
|
||||||
- 20260425 新增德语/法语/西班牙语支持
|
- 20260425 新增德语/法语/西班牙语支持
|
||||||
- 20260423 优化 firefox 抖动问题和支持手机版,支持 edge 浏览器扩展
|
- 20260423 优化 firefox 抖动问题和支持手机版,支持 edge 浏览器扩展
|
||||||
- 20260422 调整发送设置,支持仅发送附件
|
- 20260422 调整发送设置,支持仅发送附件
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
"message": "Speichern"
|
"message": "Speichern"
|
||||||
},
|
},
|
||||||
"supportedMemosVersion": {
|
"supportedMemosVersion": {
|
||||||
"message": "Kompatibel mit Memos v0.15.0 - 0.27.x"
|
"message": "Kompatibel mit Memos v0.15.0 - 0.30.x"
|
||||||
},
|
},
|
||||||
"settingsConnectionTitle": {
|
"settingsConnectionTitle": {
|
||||||
"message": "Verbindung"
|
"message": "Verbindung"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
"message": "Guardar"
|
"message": "Guardar"
|
||||||
},
|
},
|
||||||
"supportedMemosVersion": {
|
"supportedMemosVersion": {
|
||||||
"message": "Compatible con Memos v0.15.0 - 0.27.x"
|
"message": "Compatible con Memos v0.15.0 - 0.30.x"
|
||||||
},
|
},
|
||||||
"settingsConnectionTitle": {
|
"settingsConnectionTitle": {
|
||||||
"message": "Conexión"
|
"message": "Conexión"
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
"message": "Enregistrer"
|
"message": "Enregistrer"
|
||||||
},
|
},
|
||||||
"supportedMemosVersion": {
|
"supportedMemosVersion": {
|
||||||
"message": "Compatible avec Memos v0.15.0 - 0.27.x"
|
"message": "Compatible avec Memos v0.15.0 - 0.30.x"
|
||||||
},
|
},
|
||||||
"settingsConnectionTitle": {
|
"settingsConnectionTitle": {
|
||||||
"message": "Connexion"
|
"message": "Connexion"
|
||||||
|
|||||||
@@ -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": "接続設定"
|
||||||
|
|||||||
@@ -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": "연결 설정"
|
||||||
|
|||||||
@@ -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": "连接设置"
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
+47
-10
@@ -321,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 ''
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -480,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() : ''
|
||||||
@@ -488,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()
|
||||||
@@ -507,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)
|
||||||
@@ -516,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 ''
|
||||||
}
|
}
|
||||||
@@ -913,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>';
|
||||||
@@ -926,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 {
|
||||||
@@ -1044,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') })
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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.25",
|
"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",
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
"message": "Speichern"
|
"message": "Speichern"
|
||||||
},
|
},
|
||||||
"supportedMemosVersion": {
|
"supportedMemosVersion": {
|
||||||
"message": "Kompatibel mit Memos v0.15.0 - 0.27.x"
|
"message": "Kompatibel mit Memos v0.15.0 - 0.30.x"
|
||||||
},
|
},
|
||||||
"settingsConnectionTitle": {
|
"settingsConnectionTitle": {
|
||||||
"message": "Verbindung"
|
"message": "Verbindung"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
"message": "Guardar"
|
"message": "Guardar"
|
||||||
},
|
},
|
||||||
"supportedMemosVersion": {
|
"supportedMemosVersion": {
|
||||||
"message": "Compatible con Memos v0.15.0 - 0.27.x"
|
"message": "Compatible con Memos v0.15.0 - 0.30.x"
|
||||||
},
|
},
|
||||||
"settingsConnectionTitle": {
|
"settingsConnectionTitle": {
|
||||||
"message": "Conexión"
|
"message": "Conexión"
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
"message": "Enregistrer"
|
"message": "Enregistrer"
|
||||||
},
|
},
|
||||||
"supportedMemosVersion": {
|
"supportedMemosVersion": {
|
||||||
"message": "Compatible avec Memos v0.15.0 - 0.27.x"
|
"message": "Compatible avec Memos v0.15.0 - 0.30.x"
|
||||||
},
|
},
|
||||||
"settingsConnectionTitle": {
|
"settingsConnectionTitle": {
|
||||||
"message": "Connexion"
|
"message": "Connexion"
|
||||||
|
|||||||
@@ -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": "接続設定"
|
||||||
|
|||||||
@@ -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": "연결 설정"
|
||||||
|
|||||||
@@ -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": "连接设置"
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
+47
-10
@@ -321,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 ''
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -480,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() : ''
|
||||||
@@ -488,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()
|
||||||
@@ -507,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)
|
||||||
@@ -516,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 ''
|
||||||
}
|
}
|
||||||
@@ -913,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>';
|
||||||
@@ -926,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 {
|
||||||
@@ -1044,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') })
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
+1
-1
@@ -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.25",
|
"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",
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
"message": "Speichern"
|
"message": "Speichern"
|
||||||
},
|
},
|
||||||
"supportedMemosVersion": {
|
"supportedMemosVersion": {
|
||||||
"message": "Kompatibel mit Memos v0.15.0 - 0.27.x"
|
"message": "Kompatibel mit Memos v0.15.0 - 0.30.x"
|
||||||
},
|
},
|
||||||
"settingsConnectionTitle": {
|
"settingsConnectionTitle": {
|
||||||
"message": "Verbindung"
|
"message": "Verbindung"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
"message": "Guardar"
|
"message": "Guardar"
|
||||||
},
|
},
|
||||||
"supportedMemosVersion": {
|
"supportedMemosVersion": {
|
||||||
"message": "Compatible con Memos v0.15.0 - 0.27.x"
|
"message": "Compatible con Memos v0.15.0 - 0.30.x"
|
||||||
},
|
},
|
||||||
"settingsConnectionTitle": {
|
"settingsConnectionTitle": {
|
||||||
"message": "Conexión"
|
"message": "Conexión"
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
"message": "Enregistrer"
|
"message": "Enregistrer"
|
||||||
},
|
},
|
||||||
"supportedMemosVersion": {
|
"supportedMemosVersion": {
|
||||||
"message": "Compatible avec Memos v0.15.0 - 0.27.x"
|
"message": "Compatible avec Memos v0.15.0 - 0.30.x"
|
||||||
},
|
},
|
||||||
"settingsConnectionTitle": {
|
"settingsConnectionTitle": {
|
||||||
"message": "Connexion"
|
"message": "Connexion"
|
||||||
|
|||||||
@@ -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": "接続設定"
|
||||||
|
|||||||
@@ -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": "연결 설정"
|
||||||
|
|||||||
@@ -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": "连接设置"
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
+47
-10
@@ -346,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 ''
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -505,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() : ''
|
||||||
@@ -513,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()
|
||||||
@@ -532,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)
|
||||||
@@ -541,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 ''
|
||||||
}
|
}
|
||||||
@@ -938,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>';
|
||||||
@@ -951,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 {
|
||||||
@@ -1069,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') })
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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.25",
|
"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",
|
||||||
|
|||||||
Reference in New Issue
Block a user