diff --git a/change.log b/change.log
index 0e8c369..756ce6e 100644
--- a/change.log
+++ b/change.log
@@ -1,5 +1,5 @@
## 更新日志
-- 20260805 适配 Memos 0.29.x Attachment API,修复附件上传、绑定和预览地址;修复 0.20.x 图片预览 URL 错误使用 numeric id 的问题
+- 20260805 适配 Memos 0.29.x Attachment API,修复附件上传、绑定和预览地址;修复 0.20.x 图片预览 URL 错误使用 numeric id 的问题以及标签不显示问题
- 20260425 新增德语/法语/西班牙语支持
- 20260423 优化 firefox 抖动问题和支持手机版,支持 edge 浏览器扩展
- 20260422 调整发送设置,支持仅发送附件
diff --git a/chrome/js/compat/memosApi.adapter.js b/chrome/js/compat/memosApi.adapter.js
index 13d0f95..5c359c9 100644
--- a/chrome/js/compat/memosApi.adapter.js
+++ b/chrome/js/compat/memosApi.adapter.js
@@ -249,10 +249,10 @@
}
if (flavor === 'v023' && global.MemosApiV023) {
- const filterExpr = global.MemosApiV023.buildFilter({
- rowStatus: 'NORMAL',
- creator: 'users/' + info.userid
- })
+ // v0.23 filter requires `creator == "users/{numericId}"`. Some stored userids are
+ // usernames, which causes 400. Also `row_status` can cause 400 on some builds.
+ // Let the server identify the current user from the token and return all visible memos.
+ const filterExpr = global.MemosApiV023.buildFilter({})
global.MemosApiV023.listMemos(
info,
{ pageSize: 1000, filterExpr: filterExpr },
@@ -328,7 +328,10 @@
if (flavor === FLAVOR_V020_V021 && global.MemosApiV020V021) {
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)
return
}
diff --git a/chrome/js/compat/memosApi.v020-v021.js b/chrome/js/compat/memosApi.v020-v021.js
index bb874fd..cd6ba58 100644
--- a/chrome/js/compat/memosApi.v020-v021.js
+++ b/chrome/js/compat/memosApi.v020-v021.js
@@ -201,22 +201,69 @@
function getTagSuggestion(info, success, fail) {
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(
info.apiUrl + 'api/v1/tag/suggestion',
headers,
function (data) {
- const list = Array.isArray(data) ? data : []
- const out = list
- .map(function (s) {
- return String(s).replace(/^#/, '').trim()
- })
- .filter(Boolean)
- if (success) success(out)
+ suggestionList = normalizeSuggestionList(data)
+ finish()
+ },
+ function (xhr) {
+ // Ignore failures from the suggestion endpoint; the list endpoint may still work.
+ if (xhr && xhr.status !== 404 && xhr.status !== 405 && fail) fail(xhr)
+ else finish()
+ }
+ )
+
+ requestGet(
+ info.apiUrl + 'api/v1/tag',
+ headers,
+ function (data) {
+ listList = normalizeTagList(data)
+ finish()
},
function (xhr) {
- // Some forks might only expose list.
if (isNotFoundLikeXhr(xhr)) {
- getTagList(info, success, fail)
+ finish()
return
}
if (fail) fail(xhr)
diff --git a/chrome/js/oper.js b/chrome/js/oper.js
index 27813bd..fa41d8c 100644
--- a/chrome/js/oper.js
+++ b/chrome/js/oper.js
@@ -321,6 +321,8 @@ function getMemoUid(memo) {
if (!memo) return ''
if (memo.uid != null && memo.uid !== '') return String(memo.uid)
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 ''
}
@@ -482,12 +484,10 @@ function buildV1ResourceStreamUrl(info, resource) {
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 : ''
- 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
@@ -497,6 +497,22 @@ function buildV1ResourceStreamUrl(info, resource) {
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) {
if (typeof s !== 'string') return false
const v = s.trim()
@@ -920,11 +936,17 @@ $('#opensite').click(function () {
// 0.23.1版本 GET api/v1/{parent}/tags 接口已移除,参考 https://github.com/usememos/memos/issues/4161
$('#tags').click(function () {
get_info(function (info) {
+ //console.log('[memos-bber] tags click info:', info)
if (info.apiUrl) {
var tagDom = "";
const adapter = getApiAdapter(info)
+ if (!adapter) {
+ //console.warn('[memos-bber] no adapter for tags')
+ return
+ }
const renderTags = function (tags) {
+ //console.log('[memos-bber] tags result:', tags)
const uniTags = [...new Set((Array.isArray(tags) ? tags : []).filter(Boolean))]
$.each(uniTags, function (_, tag) {
tagDom += '#' + tag + '';
@@ -933,7 +955,8 @@ $('#tags').click(function () {
$("#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') })
})
} else {
@@ -1051,17 +1074,24 @@ $('#search').click(function () {
$('#random').click(function () {
get_info(function (info) {
+ //console.log('[memos-bber] random click info:', info)
if (info.status) {
$("#randomlist").html('').hide()
const adapter = getApiAdapter(info)
+ if (!adapter) {
+ //console.warn('[memos-bber] no adapter for random')
+ return
+ }
adapter.listRandomMemos(
function (memos) {
+ console.log('[memos-bber] random result count:', memos && memos.length)
let randomNum = Math.floor(Math.random() * (memos.length));
var randomData = memos[randomNum]
randDom(randomData)
},
- function () {
+ function (err) {
+ //console.error('[memos-bber] random failed:', err)
$.message({ message: msg('placeApiUrl') })
}
)
diff --git a/edge/js/compat/memosApi.adapter.js b/edge/js/compat/memosApi.adapter.js
index 13d0f95..0d01ebb 100644
--- a/edge/js/compat/memosApi.adapter.js
+++ b/edge/js/compat/memosApi.adapter.js
@@ -249,10 +249,10 @@
}
if (flavor === 'v023' && global.MemosApiV023) {
- const filterExpr = global.MemosApiV023.buildFilter({
- rowStatus: 'NORMAL',
- creator: 'users/' + info.userid
- })
+ // v0.23 filter requires `creator == "users/{numericId}"`. Some stored userids are
+ // usernames, which causes 400. Also `row_status` can cause 400 on some builds.
+ // Let the server identify the current user from the token and return all visible memos.
+ const filterExpr = global.MemosApiV023.buildFilter({})
global.MemosApiV023.listMemos(
info,
{ pageSize: 1000, filterExpr: filterExpr },
@@ -328,7 +328,10 @@
if (flavor === FLAVOR_V020_V021 && global.MemosApiV020V021) {
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)
return
}
diff --git a/edge/js/compat/memosApi.v020-v021.js b/edge/js/compat/memosApi.v020-v021.js
index bb874fd..cd6ba58 100644
--- a/edge/js/compat/memosApi.v020-v021.js
+++ b/edge/js/compat/memosApi.v020-v021.js
@@ -201,22 +201,69 @@
function getTagSuggestion(info, success, fail) {
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(
info.apiUrl + 'api/v1/tag/suggestion',
headers,
function (data) {
- const list = Array.isArray(data) ? data : []
- const out = list
- .map(function (s) {
- return String(s).replace(/^#/, '').trim()
- })
- .filter(Boolean)
- if (success) success(out)
+ suggestionList = normalizeSuggestionList(data)
+ finish()
+ },
+ function (xhr) {
+ // Ignore failures from the suggestion endpoint; the list endpoint may still work.
+ if (xhr && xhr.status !== 404 && xhr.status !== 405 && fail) fail(xhr)
+ else finish()
+ }
+ )
+
+ requestGet(
+ info.apiUrl + 'api/v1/tag',
+ headers,
+ function (data) {
+ listList = normalizeTagList(data)
+ finish()
},
function (xhr) {
- // Some forks might only expose list.
if (isNotFoundLikeXhr(xhr)) {
- getTagList(info, success, fail)
+ finish()
return
}
if (fail) fail(xhr)
diff --git a/edge/js/oper.js b/edge/js/oper.js
index 27813bd..6694df5 100644
--- a/edge/js/oper.js
+++ b/edge/js/oper.js
@@ -321,6 +321,8 @@ function getMemoUid(memo) {
if (!memo) return ''
if (memo.uid != null && memo.uid !== '') return String(memo.uid)
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 ''
}
@@ -482,12 +484,10 @@ function buildV1ResourceStreamUrl(info, resource) {
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 : ''
- 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
@@ -497,6 +497,22 @@ function buildV1ResourceStreamUrl(info, resource) {
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) {
if (typeof s !== 'string') return false
const v = s.trim()
@@ -920,11 +936,17 @@ $('#opensite').click(function () {
// 0.23.1版本 GET api/v1/{parent}/tags 接口已移除,参考 https://github.com/usememos/memos/issues/4161
$('#tags').click(function () {
get_info(function (info) {
+ console.log('[memos-bber] tags click info:', info)
if (info.apiUrl) {
var tagDom = "";
const adapter = getApiAdapter(info)
+ if (!adapter) {
+ console.warn('[memos-bber] no adapter for tags')
+ return
+ }
const renderTags = function (tags) {
+ console.log('[memos-bber] tags result:', tags)
const uniTags = [...new Set((Array.isArray(tags) ? tags : []).filter(Boolean))]
$.each(uniTags, function (_, tag) {
tagDom += '#' + tag + '';
@@ -933,7 +955,8 @@ $('#tags').click(function () {
$("#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') })
})
} else {
@@ -1051,17 +1074,24 @@ $('#search').click(function () {
$('#random').click(function () {
get_info(function (info) {
+ console.log('[memos-bber] random click info:', info)
if (info.status) {
$("#randomlist").html('').hide()
const adapter = getApiAdapter(info)
+ if (!adapter) {
+ console.warn('[memos-bber] no adapter for random')
+ return
+ }
adapter.listRandomMemos(
function (memos) {
+ console.log('[memos-bber] random result count:', memos && memos.length)
let randomNum = Math.floor(Math.random() * (memos.length));
var randomData = memos[randomNum]
randDom(randomData)
},
- function () {
+ function (err) {
+ console.error('[memos-bber] random failed:', err)
$.message({ message: msg('placeApiUrl') })
}
)
diff --git a/firefox/js/compat/memosApi.adapter.js b/firefox/js/compat/memosApi.adapter.js
index 13d0f95..0d01ebb 100644
--- a/firefox/js/compat/memosApi.adapter.js
+++ b/firefox/js/compat/memosApi.adapter.js
@@ -249,10 +249,10 @@
}
if (flavor === 'v023' && global.MemosApiV023) {
- const filterExpr = global.MemosApiV023.buildFilter({
- rowStatus: 'NORMAL',
- creator: 'users/' + info.userid
- })
+ // v0.23 filter requires `creator == "users/{numericId}"`. Some stored userids are
+ // usernames, which causes 400. Also `row_status` can cause 400 on some builds.
+ // Let the server identify the current user from the token and return all visible memos.
+ const filterExpr = global.MemosApiV023.buildFilter({})
global.MemosApiV023.listMemos(
info,
{ pageSize: 1000, filterExpr: filterExpr },
@@ -328,7 +328,10 @@
if (flavor === FLAVOR_V020_V021 && global.MemosApiV020V021) {
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)
return
}
diff --git a/firefox/js/compat/memosApi.v020-v021.js b/firefox/js/compat/memosApi.v020-v021.js
index bb874fd..cd6ba58 100644
--- a/firefox/js/compat/memosApi.v020-v021.js
+++ b/firefox/js/compat/memosApi.v020-v021.js
@@ -201,22 +201,69 @@
function getTagSuggestion(info, success, fail) {
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(
info.apiUrl + 'api/v1/tag/suggestion',
headers,
function (data) {
- const list = Array.isArray(data) ? data : []
- const out = list
- .map(function (s) {
- return String(s).replace(/^#/, '').trim()
- })
- .filter(Boolean)
- if (success) success(out)
+ suggestionList = normalizeSuggestionList(data)
+ finish()
+ },
+ function (xhr) {
+ // Ignore failures from the suggestion endpoint; the list endpoint may still work.
+ if (xhr && xhr.status !== 404 && xhr.status !== 405 && fail) fail(xhr)
+ else finish()
+ }
+ )
+
+ requestGet(
+ info.apiUrl + 'api/v1/tag',
+ headers,
+ function (data) {
+ listList = normalizeTagList(data)
+ finish()
},
function (xhr) {
- // Some forks might only expose list.
if (isNotFoundLikeXhr(xhr)) {
- getTagList(info, success, fail)
+ finish()
return
}
if (fail) fail(xhr)
diff --git a/firefox/js/oper.js b/firefox/js/oper.js
index 1f5dee5..764537e 100644
--- a/firefox/js/oper.js
+++ b/firefox/js/oper.js
@@ -346,6 +346,8 @@ function getMemoUid(memo) {
if (!memo) return ''
if (memo.uid != null && memo.uid !== '') return String(memo.uid)
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 ''
}
@@ -507,12 +509,10 @@ function buildV1ResourceStreamUrl(info, resource) {
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 : ''
- 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
@@ -522,6 +522,22 @@ function buildV1ResourceStreamUrl(info, resource) {
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) {
if (typeof s !== 'string') return false
const v = s.trim()
@@ -945,11 +961,17 @@ $('#opensite').click(function () {
// 0.23.1版本 GET api/v1/{parent}/tags 接口已移除,参考 https://github.com/usememos/memos/issues/4161
$('#tags').click(function () {
get_info(function (info) {
+ console.log('[memos-bber] tags click info:', info)
if (info.apiUrl) {
var tagDom = "";
const adapter = getApiAdapter(info)
+ if (!adapter) {
+ console.warn('[memos-bber] no adapter for tags')
+ return
+ }
const renderTags = function (tags) {
+ console.log('[memos-bber] tags result:', tags)
const uniTags = [...new Set((Array.isArray(tags) ? tags : []).filter(Boolean))]
$.each(uniTags, function (_, tag) {
tagDom += '#' + tag + '';
@@ -958,7 +980,8 @@ $('#tags').click(function () {
$("#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') })
})
} else {
@@ -1076,17 +1099,24 @@ $('#search').click(function () {
$('#random').click(function () {
get_info(function (info) {
+ console.log('[memos-bber] random click info:', info)
if (info.status) {
$("#randomlist").html('').hide()
const adapter = getApiAdapter(info)
+ if (!adapter) {
+ console.warn('[memos-bber] no adapter for random')
+ return
+ }
adapter.listRandomMemos(
function (memos) {
+ console.log('[memos-bber] random result count:', memos && memos.length)
let randomNum = Math.floor(Math.random() * (memos.length));
var randomData = memos[randomNum]
randDom(randomData)
},
- function () {
+ function (err) {
+ console.error('[memos-bber] random failed:', err)
$.message({ message: msg('placeApiUrl') })
}
)