3 Commits

Author SHA1 Message Date
jonny ae9b37d87a Add changelog section to README
Reorganized README by removing the version compatibility info and adding a new Changelog section with a link to the change log file. Moved the Ko-fi donation button to be more prominent.
2026-08-05 17:26:29 +08:00
jonny c4da5f790a Update Memos version support text
Refresh the changelog and localized compatibility strings to reflect support for Memos 0.28.x-0.30.x, and correct the older image preview note to 0.19.x-0.23.x.
2026-08-05 17:20:16 +08:00
jonny 1361cc15db Fix tags display and Memos API compatibility issues
Fix the tags not displaying issue by querying both /tag/suggestion and /tag endpoints and merging results. Simplify v0.23 filter to avoid 400 errors from numeric ID requirements. Add v0.18.2 support via numeric ID fallback in getMemoUid. Improve v0.23+ resource URL handling and add better error handling with debug logging for tags and random features.
2026-08-05 17:07:59 +08:00
32 changed files with 326 additions and 84 deletions
+4 -2
View File
@@ -1,9 +1,11 @@
一款通过浏览器插件发布 [Memos](https://usememos.com/) 的插件。基于 iSpeak-bber 修改,原作者为 [DreamyTZK](https://www.antmoe.com/)。
兼容 Memos v0.15.0 - v0.29.x,支持新版 Attachment API。
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/jonnyan404)
## 更新日志
- 当前支持memos `v0.15.0 - v0.30.x` [点击查看详细更新日志](https://raw.githubusercontent.com/Jonnyan404/memos-bber/refs/heads/main/change.log)
## 在线商店安装
- Chrome 应用商店:https://chrome.google.com/webstore/detail/memos-bber/cbhjebjfccgchgbmfbobjmebjjckgofe/
+1 -1
View File
@@ -1,5 +1,5 @@
## 更新日志
- 20260805 适配 Memos 0.29.x Attachment API,修复附件上传、绑定和预览地址;修复 0.20.x 图片预览 URL 错误使用 numeric id 的问题
- 20260805 适配 Memos 0.28.x-0.30.x Attachment API,修复附件上传、绑定和预览地址;修复 0.19.x-0.23.x 图片预览 URL 错误使用 numeric id 的问题以及标签不显示问题
- 20260425 新增德语/法语/西班牙语支持
- 20260423 优化 firefox 抖动问题和支持手机版,支持 edge 浏览器扩展
- 20260422 调整发送设置,支持仅发送附件
+1 -1
View File
@@ -21,7 +21,7 @@
"message": "Speichern"
},
"supportedMemosVersion": {
"message": "Kompatibel mit Memos v0.15.0 - 0.29.x"
"message": "Kompatibel mit Memos v0.15.0 - 0.30.x"
},
"settingsConnectionTitle": {
"message": "Verbindung"
+1 -1
View File
@@ -21,7 +21,7 @@
"message": "Save"
},
"supportedMemosVersion": {
"message": "Compatible with Memos v0.15.0 - 0.29.x"
"message": "Compatible with Memos v0.15.0 - 0.30.x"
},
"settingsConnectionTitle": {
"message": "Connection"
+1 -1
View File
@@ -21,7 +21,7 @@
"message": "Guardar"
},
"supportedMemosVersion": {
"message": "Compatible con Memos v0.15.0 - 0.29.x"
"message": "Compatible con Memos v0.15.0 - 0.30.x"
},
"settingsConnectionTitle": {
"message": "Conexión"
+1 -1
View File
@@ -21,7 +21,7 @@
"message": "Enregistrer"
},
"supportedMemosVersion": {
"message": "Compatible avec Memos v0.15.0 - 0.29.x"
"message": "Compatible avec Memos v0.15.0 - 0.30.x"
},
"settingsConnectionTitle": {
"message": "Connexion"
+1 -1
View File
@@ -21,7 +21,7 @@
"message": "保存"
},
"supportedMemosVersion": {
"message": "Memos v0.15.0 - 0.29.x に対応"
"message": "Memos v0.15.0 - 0.30.x に対応"
},
"settingsConnectionTitle": {
"message": "接続設定"
+1 -1
View File
@@ -21,7 +21,7 @@
"message": "저장"
},
"supportedMemosVersion": {
"message": "Memos v0.15.0 - 0.29.x 호환"
"message": "Memos v0.15.0 - 0.30.x 호환"
},
"settingsConnectionTitle": {
"message": "연결 설정"
+1 -1
View File
@@ -21,7 +21,7 @@
"message": "保存"
},
"supportedMemosVersion": {
"message": "兼容 Memos v0.15.0 - 0.29.x"
"message": "兼容 Memos v0.15.0 - 0.30.x"
},
"settingsConnectionTitle": {
"message": "连接设置"
+8 -5
View File
@@ -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
}
+56 -9
View File
@@ -201,22 +201,69 @@
function getTagSuggestion(info, success, fail) {
const headers = { Authorization: 'Bearer ' + info.apiTokens }
requestGet(
info.apiUrl + 'api/v1/tag/suggestion',
headers,
function (data) {
const list = Array.isArray(data) ? data : []
const out = list
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)
if (success) success(out)
}
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) {
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)
+36 -6
View File
@@ -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 += '<span class="item-container">#' + tag + '</span>';
@@ -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') })
}
)
+1 -1
View File
@@ -21,7 +21,7 @@
"message": "Speichern"
},
"supportedMemosVersion": {
"message": "Kompatibel mit Memos v0.15.0 - 0.29.x"
"message": "Kompatibel mit Memos v0.15.0 - 0.30.x"
},
"settingsConnectionTitle": {
"message": "Verbindung"
+1 -1
View File
@@ -21,7 +21,7 @@
"message": "Save"
},
"supportedMemosVersion": {
"message": "Compatible with Memos v0.15.0 - 0.29.x"
"message": "Compatible with Memos v0.15.0 - 0.30.x"
},
"settingsConnectionTitle": {
"message": "Connection"
+1 -1
View File
@@ -21,7 +21,7 @@
"message": "Guardar"
},
"supportedMemosVersion": {
"message": "Compatible con Memos v0.15.0 - 0.29.x"
"message": "Compatible con Memos v0.15.0 - 0.30.x"
},
"settingsConnectionTitle": {
"message": "Conexión"
+1 -1
View File
@@ -21,7 +21,7 @@
"message": "Enregistrer"
},
"supportedMemosVersion": {
"message": "Compatible avec Memos v0.15.0 - 0.29.x"
"message": "Compatible avec Memos v0.15.0 - 0.30.x"
},
"settingsConnectionTitle": {
"message": "Connexion"
+1 -1
View File
@@ -21,7 +21,7 @@
"message": "保存"
},
"supportedMemosVersion": {
"message": "Memos v0.15.0 - 0.29.x に対応"
"message": "Memos v0.15.0 - 0.30.x に対応"
},
"settingsConnectionTitle": {
"message": "接続設定"
+1 -1
View File
@@ -21,7 +21,7 @@
"message": "저장"
},
"supportedMemosVersion": {
"message": "Memos v0.15.0 - 0.29.x 호환"
"message": "Memos v0.15.0 - 0.30.x 호환"
},
"settingsConnectionTitle": {
"message": "연결 설정"
+1 -1
View File
@@ -21,7 +21,7 @@
"message": "保存"
},
"supportedMemosVersion": {
"message": "兼容 Memos v0.15.0 - 0.29.x"
"message": "兼容 Memos v0.15.0 - 0.30.x"
},
"settingsConnectionTitle": {
"message": "连接设置"
+8 -5
View File
@@ -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
}
+56 -9
View File
@@ -201,22 +201,69 @@
function getTagSuggestion(info, success, fail) {
const headers = { Authorization: 'Bearer ' + info.apiTokens }
requestGet(
info.apiUrl + 'api/v1/tag/suggestion',
headers,
function (data) {
const list = Array.isArray(data) ? data : []
const out = list
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)
if (success) success(out)
}
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) {
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)
+36 -6
View File
@@ -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 += '<span class="item-container">#' + tag + '</span>';
@@ -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') })
}
)
+1 -1
View File
@@ -21,7 +21,7 @@
"message": "Speichern"
},
"supportedMemosVersion": {
"message": "Kompatibel mit Memos v0.15.0 - 0.29.x"
"message": "Kompatibel mit Memos v0.15.0 - 0.30.x"
},
"settingsConnectionTitle": {
"message": "Verbindung"
+1 -1
View File
@@ -21,7 +21,7 @@
"message": "Save"
},
"supportedMemosVersion": {
"message": "Compatible with Memos v0.15.0 - 0.29.x"
"message": "Compatible with Memos v0.15.0 - 0.30.x"
},
"settingsConnectionTitle": {
"message": "Connection"
+1 -1
View File
@@ -21,7 +21,7 @@
"message": "Guardar"
},
"supportedMemosVersion": {
"message": "Compatible con Memos v0.15.0 - 0.29.x"
"message": "Compatible con Memos v0.15.0 - 0.30.x"
},
"settingsConnectionTitle": {
"message": "Conexión"
+1 -1
View File
@@ -21,7 +21,7 @@
"message": "Enregistrer"
},
"supportedMemosVersion": {
"message": "Compatible avec Memos v0.15.0 - 0.29.x"
"message": "Compatible avec Memos v0.15.0 - 0.30.x"
},
"settingsConnectionTitle": {
"message": "Connexion"
+1 -1
View File
@@ -21,7 +21,7 @@
"message": "保存"
},
"supportedMemosVersion": {
"message": "Memos v0.15.0 - 0.29.x に対応"
"message": "Memos v0.15.0 - 0.30.x に対応"
},
"settingsConnectionTitle": {
"message": "接続設定"
+1 -1
View File
@@ -21,7 +21,7 @@
"message": "저장"
},
"supportedMemosVersion": {
"message": "Memos v0.15.0 - 0.29.x 호환"
"message": "Memos v0.15.0 - 0.30.x 호환"
},
"settingsConnectionTitle": {
"message": "연결 설정"
+1 -1
View File
@@ -21,7 +21,7 @@
"message": "保存"
},
"supportedMemosVersion": {
"message": "兼容 Memos v0.15.0 - 0.29.x"
"message": "兼容 Memos v0.15.0 - 0.30.x"
},
"settingsConnectionTitle": {
"message": "连接设置"
+8 -5
View File
@@ -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
}
+56 -9
View File
@@ -201,22 +201,69 @@
function getTagSuggestion(info, success, fail) {
const headers = { Authorization: 'Bearer ' + info.apiTokens }
requestGet(
info.apiUrl + 'api/v1/tag/suggestion',
headers,
function (data) {
const list = Array.isArray(data) ? data : []
const out = list
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)
if (success) success(out)
}
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) {
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)
+36 -6
View File
@@ -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 += '<span class="item-container">#' + tag + '</span>';
@@ -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') })
}
)