Add Memos API adapter and settings UI

Introduce a Memos API adapter to unify compatibility across API flavors (v020-v021, v023, modern) and probe/detect server flavor. Add js/compat/memosApi.adapter.js and rename existing compat modules (memosApi.v024.js -> memosApi.modern.js, memosApi.v1.js -> memosApi.v020-v021.js) and trim probe from v023. Add settings UI support: new i18n keys in en/ja/ko/zh_CN, settings panel styles in css/main.css, new settings input binding and save flow in js (attachment-only default text persisted). Refactor oper.js to use the adapter for uploads, deletes, tag listing, search and image preview auth handling; add helper functions for attachment-only default text and settings payload. Update i18n bindings, popup, manifest and README changelog accordingly.
closed #5
This commit is contained in:
jonny
2026-04-22 16:13:18 +08:00
parent 6c4801cb16
commit 3968b6896c
14 changed files with 840 additions and 681 deletions
+1 -104
View File
@@ -111,112 +111,9 @@
})
}
function probeApiFlavor(apiUrl, apiTokens, callback) {
const headers = { Authorization: 'Bearer ' + apiTokens }
function looksLikeMemosListPayload(data) {
if (!data) return false
if (Array.isArray(data)) return true
if (Array.isArray(data.memos)) return true
if (data.data && Array.isArray(data.data.memos)) return true
if (Array.isArray(data.list)) return true
// Common JSON error shapes should not be treated as success.
if (typeof data.error === 'string' || typeof data.message === 'string') return false
return false
}
function isNotFoundLike(xhr) {
const status = xhr && xhr.status
return status === 404 || status === 405
}
// Modern-style filter probe.
const modernQ =
'api/v1/memos?pageSize=1&filter=' +
encodeURIComponent('visibility in ["PUBLIC","PROTECTED"]')
// v0.23-style filter probe.
const v023Q =
'api/v1/memos?pageSize=1&filter=' +
encodeURIComponent('visibilities == ["PUBLIC","PROTECTED"]')
// v0.20/v0.21 unified API v1 probe.
const v1Q = 'api/v1/memo?limit=1&rowStatus=NORMAL'
global.$
.ajax({
url: apiUrl + modernQ,
method: 'GET',
headers: headers,
dataType: 'json'
})
.done(function (data) {
if (looksLikeMemosListPayload(data)) {
callback({ flavor: 'modern' })
return
}
// Treat unexpected success payload as unknown and continue probing.
global.$
.ajax({
url: apiUrl + v023Q,
method: 'GET',
headers: headers,
dataType: 'json'
})
.done(function (data2) {
if (looksLikeMemosListPayload(data2)) callback({ flavor: 'v023' })
else callback({ flavor: 'unknown' })
})
.fail(function () {
callback({ flavor: 'unknown' })
})
})
.fail(function (xhr) {
if (xhr && xhr.status === 400) {
global.$
.ajax({
url: apiUrl + v023Q,
method: 'GET',
headers: headers,
dataType: 'json'
})
.done(function (data2) {
if (looksLikeMemosListPayload(data2)) callback({ flavor: 'v023' })
else callback({ flavor: 'unknown' })
})
.fail(function () {
callback({ flavor: 'unknown' })
})
return
}
// If /api/v1/memos is missing, check /api/v1/memo (v0.20/v0.21 unified).
if (isNotFoundLike(xhr)) {
global.$
.ajax({
url: apiUrl + v1Q,
method: 'GET',
headers: headers,
dataType: 'json'
})
.done(function (data2) {
if (looksLikeMemosListPayload(data2)) callback({ flavor: 'v1' })
else callback({ flavor: 'unknown' })
})
.fail(function () {
callback({ flavor: 'unknown' })
})
return
}
callback({ flavor: 'unknown' })
})
}
global.MemosApiV023 = {
buildFilter: buildFilter,
listMemos: listMemos,
extractTagsFromMemo: extractTagsFromMemo,
probeApiFlavor: probeApiFlavor
extractTagsFromMemo: extractTagsFromMemo
}
})(window)