dayjs.extend(window.dayjs_plugin_relativeTime)
let currentMemoLock = ''
function msg(key) {
if (typeof window.t === 'function') return window.t(key)
return chrome.i18n.getMessage(key) || ''
}
function applyDayjsLocaleByUiLanguage(uiLang) {
const lang = String(uiLang || 'auto')
if (lang === 'zh_CN') {
dayjs.locale('zh-cn')
return
}
if (lang === 'ja') {
dayjs.locale('ja')
return
}
if (lang === 'ko') {
dayjs.locale('ko')
return
}
if (lang === 'en') {
dayjs.locale('en')
return
}
// auto: best-effort infer from browser UI language
const ui = String(chrome.i18n.getUILanguage ? chrome.i18n.getUILanguage() : '').toLowerCase()
if (ui.startsWith('zh')) {
dayjs.locale('zh-cn')
return
}
if (ui.startsWith('ja')) {
dayjs.locale('ja')
return
}
if (ui.startsWith('ko')) {
dayjs.locale('ko')
return
}
dayjs.locale('en')
}
function updateLockNowText(lockType) {
if (lockType === 'PUBLIC') {
$('#lock-now').text(msg('lockPublic'))
} else if (lockType === 'PRIVATE') {
$('#lock-now').text(msg('lockPrivate'))
} else if (lockType === 'PROTECTED') {
$('#lock-now').text(msg('lockProtected'))
}
}
applyDayjsLocaleByUiLanguage(typeof window.getUiLanguage === 'function' ? window.getUiLanguage() : 'auto')
window.addEventListener('i18n:changed', (ev) => {
applyDayjsLocaleByUiLanguage(ev && ev.detail ? ev.detail.lang : 'auto')
updateLockNowText(currentMemoLock)
renderUploadList(relistNow)
})
let relistNow = []
function get_info(callback) {
chrome.storage.sync.get(
{
apiUrl: '',
apiTokens: '',
apiFlavor: '',
hidetag: '',
showtag: '',
memo_lock: '',
open_action: '',
open_content: '',
userid: '',
memoUiPath: 'memos',
resourceIdList: []
},
function (items) {
var flag = false
var returnObject = {}
if (items.apiUrl === '' || items.apiTokens === '') {
flag = false
} else {
flag = true
}
returnObject.status = flag
returnObject.apiUrl = items.apiUrl
returnObject.apiTokens = items.apiTokens
returnObject.apiFlavor = items.apiFlavor
returnObject.hidetag = items.hidetag
returnObject.showtag = items.showtag
returnObject.memo_lock = items.memo_lock
returnObject.open_content = items.open_content
returnObject.open_action = items.open_action
returnObject.userid = items.userid
returnObject.memoUiPath = items.memoUiPath
returnObject.resourceIdList = items.resourceIdList
if (callback) callback(returnObject)
}
)
}
function isV023Flavor(info) {
return info && info.apiFlavor === 'v023' && window.MemosApiV023
}
function isV1Flavor(info) {
return info && info.apiFlavor === 'v1' && window.MemosApiV1
}
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()
return ''
}
get_info(function (info) {
if (info.status) {
//已经有绑定信息了,折叠
$('#blog_info').hide()
}
var memoNow = info.memo_lock
if (memoNow == '') {
chrome.storage.sync.set(
{ memo_lock: 'PUBLIC' }
)
memoNow = 'PUBLIC'
}
currentMemoLock = memoNow
updateLockNowText(memoNow)
$('#apiUrl').val(info.apiUrl)
$('#apiTokens').val(info.apiTokens)
$('#hideInput').val(info.hidetag)
$('#showInput').val(info.showtag)
if (info.open_action === 'upload_image') {
//打开的时候就是上传图片
uploadImage(info.open_content)
} else {
$("textarea[name=text]").val(info.open_content)
}
relistNow = Array.isArray(info.resourceIdList) ? info.resourceIdList : []
renderUploadList(relistNow)
//从localstorage 里面读取数据
setTimeout(get_info, 1)
})
chrome.storage.onChanged.addListener(function (changes, areaName) {
if (areaName !== 'sync') return
if (!changes.resourceIdList) return
relistNow = Array.isArray(changes.resourceIdList.newValue)
? changes.resourceIdList.newValue
: []
renderUploadList(relistNow)
})
$("textarea[name=text]").focus()
//监听输入结束,保存未发送内容到本地
$("textarea[name=text]").blur(function () {
chrome.storage.sync.set(
{ open_action: 'save_text', open_content: $("textarea[name=text]").val() }
)
})
$("textarea[name=text]").on('keydown', function (ev) {
if (ev.code === 'Enter' && (ev.ctrlKey || ev.metaKey)) {
$('#content_submit_text').click()
}
})
//监听拖拽事件,实现拖拽到窗口上传图片
initDrag()
//监听复制粘贴事件,实现粘贴上传图片
document.addEventListener('paste', function (e) {
let photo = null
if (e.clipboardData.files[0]) {
photo = e.clipboardData.files[0]
} else if (e.clipboardData.items[0] && e.clipboardData.items[0].getAsFile()) {
photo = e.clipboardData.items[0].getAsFile()
}
if (photo != null) {
uploadImage(photo)
}
})
function initDrag() {
var file = null
var obj = $("textarea[name=text]")[0]
obj.ondragenter = function (ev) {
if (ev.target.className === 'common-editor-inputer') {
$.message({
message: msg('picDrag'),
autoClose: false
})
$('body').css('opacity', 0.3)
}
ev.dataTransfer.dropEffect = 'copy'
}
obj.ondragover = function (ev) {
ev.preventDefault()
ev.dataTransfer.dropEffect = 'copy'
}
obj.ondrop = function (ev) {
$('body').css('opacity', 1)
ev.preventDefault()
var files = ev.dataTransfer.files || ev.target.files
for (var i = 0; i < files.length; i++) {
file = files[i]
}
uploadImage(file)
}
obj.ondragleave = function (ev) {
ev.preventDefault()
if (ev.target.className === 'common-editor-inputer') {
$.message({
message: msg('picCancelDrag')
})
$('body').css('opacity', 1)
}
}
}
function escapeHtml(input) {
return String(input)
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
}
function buildV1ResourceStreamUrl(info, resource) {
if (!info || !info.apiUrl || !resource) return ''
// Use the configured apiUrl as the base (may include a reverse-proxy subpath).
// Do NOT reduce to origin-only, otherwise deployments like https://host/memos/ will break.
let root = String(info.apiUrl)
try {
const u = new URL(root)
u.hash = ''
u.search = ''
root = u.toString()
} catch (_) {
// keep as-is
}
if (root && !root.endsWith('/')) root += '/'
function isImageResource(r) {
if (!r) return false
const t = typeof r.type === 'string' ? r.type.toLowerCase() : ''
if (t.startsWith('image/')) return true
const fn = typeof r.filename === 'string' ? r.filename.toLowerCase() : ''
return /\.(png|jpe?g|gif|webp|bmp|svg|avif|heic)$/.test(fn)
}
function isProbablyUid(s) {
if (typeof s !== 'string') return false
const v = s.trim()
if (!v) return false
if (v.indexOf('/') !== -1) return false
if (/^\d+$/.test(v)) return false
// shortuuid v4 typically uses URL-safe base57-ish; allow a conservative charset.
return /^[A-Za-z0-9_-]{8,}$/.test(v)
}
function buildStreamUrl(uid) {
const base = root + 'o/r/' + encodeURIComponent(uid)
return isImageResource(resource) ? base + '?thumbnail=1' : base
}
const uidRaw = resource.uid != null ? resource.uid : resource.UID != null ? resource.UID : resource.Uid
const uid = typeof uidRaw === 'string' ? uidRaw : uidRaw != null ? String(uidRaw) : ''
if (uid.trim() !== '') return buildStreamUrl(uid.trim())
// 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 id = typeof idRaw === 'number' && Number.isFinite(idRaw)
? String(Math.floor(idRaw))
: typeof idRaw === 'string' && idRaw.trim() !== '' && !Number.isNaN(Number(idRaw))
? String(Math.floor(Number(idRaw)))
: ''
if (id) return buildStreamUrl(id)
// Fallback for older resource shapes.
const name = typeof resource.name === 'string' ? resource.name : ''
// In some memo payloads, the uid may appear as `name` directly.
// Example: name="ETU6hjuR..." should map to /o/r/:uid, not /file/:name/:filename.
if (isProbablyUid(name)) return buildStreamUrl(name.trim())
const fileId = resource.publicId || resource.filename
if (name && fileId) return root + 'file/' + name + '/' + fileId
return ''
}
function normalizeUnixTimeToMs(input) {
if (input == null) return null
if (typeof input === 'number' && Number.isFinite(input)) {
// Heuristic: seconds are typically 10 digits; milliseconds are 13 digits.
if (input > 0 && input < 1e12) return input * 1000
return input
}
if (typeof input === 'string') {
const s = input.trim()
if (/^\d+$/.test(s)) {
const n = Number(s)
if (!Number.isFinite(n)) return null
if (n > 0 && n < 1e12) return n * 1000
return n
}
// ISO/RFC3339 etc.
return s
}
return null
}
function memoFromNow(memo) {
if (!memo) return ''
const raw = memo.createTime || memo.createdAt || memo.createdTs
const normalized = normalizeUnixTimeToMs(raw)
if (!normalized) return ''
return dayjs(normalized).fromNow()
}
function hydrateV1PreviewImages(info) {
if (!isV1Flavor(info)) return
if (!info || !info.apiUrl || !info.apiTokens) return
const token = String(info.apiTokens)
let root = String(info.apiUrl)
let apiOrigin = ''
try {
const u = new URL(root)
u.hash = ''
u.search = ''
root = u.toString()
apiOrigin = u.origin
} catch (_) {
// keep as-is
}
if (root && !root.endsWith('/')) root += '/'
const nodes = document.querySelectorAll('img.random-image')
if (!nodes || nodes.length === 0) return
// Revoke blob URLs on popup unload to avoid leaking memory.
if (!window.__memosBberObjectUrls) {
window.__memosBberObjectUrls = []
window.addEventListener('unload', function () {
const list = window.__memosBberObjectUrls || []
for (let i = 0; i < list.length; i++) {
try { URL.revokeObjectURL(list[i]) } catch (_) {}
}
window.__memosBberObjectUrls = []
})
}
const transparentPixel = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=='
function resolveToAbsoluteUrl(url) {
const u = String(url || '').trim()
if (!u) return ''
if (u.startsWith('data:') || u.startsWith('blob:') || u.startsWith('chrome-extension:')) return ''
if (u.startsWith('#')) return ''
try {
return new URL(u, root).toString()
} catch (_) {
return ''
}
}
function isSameOrigin(url) {
if (!apiOrigin) return false
try {
return new URL(url).origin === apiOrigin
} catch (_) {
return false
}
}
function looksLikeMemosResourceUrl(absUrl) {
const s = String(absUrl || '')
return s.indexOf('/o/r/') !== -1 || s.indexOf('/file/') !== -1
}
nodes.forEach(function (img) {
const hasAuthAttr = img.hasAttribute('data-auth-src')
const url = img.getAttribute('data-auth-src') || img.getAttribute('src')
if (!url) return
if (img.getAttribute('data-auth-loaded') === '1') return
const abs = resolveToAbsoluteUrl(url)
if (!abs) return
// Only hydrate same-origin resources that require Authorization.
if (!isSameOrigin(abs)) return
// Reduce unnecessary fetches: only hydrate known resource endpoints,
// or images explicitly marked as auth-required.
if (!hasAuthAttr && !looksLikeMemosResourceUrl(abs)) return
img.setAttribute('data-auth-loaded', '1')
// Prevent a broken-image icon before hydration completes.
// Only do this for images explicitly marked as auth-required.
if (hasAuthAttr) {
const currentSrc = img.getAttribute('src')
if (!currentSrc || currentSrc === abs) {
img.setAttribute('src', transparentPixel)
}
}
fetch(abs, {
method: 'GET',
headers: {
Authorization: 'Bearer ' + token
}
})
.then(function (res) {
if (!res || !res.ok) throw new Error('HTTP ' + (res ? res.status : '0'))
const ct = (res.headers && typeof res.headers.get === 'function') ? (res.headers.get('content-type') || '') : ''
if (ct && !ct.toLowerCase().startsWith('image/')) throw new Error('Not an image')
return res.blob()
})
.then(function (blob) {
const objectUrl = URL.createObjectURL(blob)
window.__memosBberObjectUrls.push(objectUrl)
img.src = objectUrl
})
.catch(function () {
// Don't break previews for modern versions where plain
may already work.
if (hasAuthAttr) {
try { img.removeAttribute('src') } catch (_) {}
}
})
})
}
function renderUploadList(list) {
const $wrapper = $('.upload-list-wrapper')
const $list = $('#uploadlist')
if ($list.length === 0) return
const items = Array.isArray(list) ? list : []
if (items.length === 0) {
if ($wrapper.length) $wrapper.hide()
$list.html('')
return
}
if ($wrapper.length) $wrapper.show()
const tipReorder = escapeHtml(msg('tipReorder'))
const tipDelete = escapeHtml(msg('tipDeleteAttachment'))
let html = ''
for (let i = 0; i < items.length; i++) {
const att = items[i] || {}
const name = att.name || ''
const id = att.id != null ? String(att.id) : ''
const filename = att.filename || name
html +=
'
' +
'
' +
'≡' +
'' +
escapeHtml(filename) +
'' +
'
' +
'
' +
'
'
}
$list.html(html)
}
function saveUploadList(nextList, callback) {
relistNow = Array.isArray(nextList) ? nextList : []
chrome.storage.sync.set({ resourceIdList: relistNow }, callback)
}
let uploadDragIndex = null
$(document).on('dragstart', '.upload-item', function (e) {
uploadDragIndex = Number($(this).data('index'))
const dt = e.originalEvent && e.originalEvent.dataTransfer
if (dt) {
dt.effectAllowed = 'move'
dt.setData('text/plain', String(uploadDragIndex))
}
})
$(document).on('dragover', '.upload-item', function (e) {
e.preventDefault()
$(this).addClass('drag-over')
const dt = e.originalEvent && e.originalEvent.dataTransfer
if (dt) dt.dropEffect = 'move'
})
$(document).on('dragleave', '.upload-item', function () {
$(this).removeClass('drag-over')
})
$(document).on('drop', '.upload-item', function (e) {
e.preventDefault()
$('.upload-item.drag-over').removeClass('drag-over')
const fromIndex =
uploadDragIndex != null
? uploadDragIndex
: Number(
(e.originalEvent && e.originalEvent.dataTransfer
? e.originalEvent.dataTransfer.getData('text/plain')
: '') || -1
)
const toIndex = Number($(this).data('index'))
uploadDragIndex = null
if (!Number.isFinite(fromIndex) || !Number.isFinite(toIndex)) return
if (fromIndex < 0 || toIndex < 0) return
if (fromIndex === toIndex) return
const next = (Array.isArray(relistNow) ? relistNow : []).slice()
if (fromIndex >= next.length || toIndex >= next.length) return
const moved = next.splice(fromIndex, 1)[0]
next.splice(toIndex, 0, moved)
saveUploadList(next, function () {
renderUploadList(relistNow)
})
})
$(document).on('click', '.upload-del', function () {
const name = $(this).data('name')
const rid = $(this).data('id')
if (!name) return
get_info(function (info) {
if (!info.status) {
$.message({ message: msg('placeApiUrl') })
return
}
const inferredId = (function () {
if (rid != null && String(rid).trim() !== '' && !Number.isNaN(Number(rid))) return Math.floor(Number(rid))
const tail = String(name).split('/').pop()
if (tail && !Number.isNaN(Number(tail))) return Math.floor(Number(tail))
return null
})()
const doDelete = isV1Flavor(info) && window.MemosApiV1 && typeof window.MemosApiV1.deleteResource === 'function' && inferredId != null
? function (onOk, onFail) {
window.MemosApiV1.deleteResource(info, inferredId, onOk, onFail)
}
: function (onOk, onFail) {
$.ajax({
url: info.apiUrl + 'api/v1/' + name,
type: 'DELETE',
headers: { Authorization: 'Bearer ' + info.apiTokens },
success: function (data) {
onOk(data)
},
error: function (xhr) {
onFail(xhr)
}
})
}
doDelete(
function () {
const next = (Array.isArray(relistNow) ? relistNow : []).filter(function (x) {
return x && x.name !== name
})
saveUploadList(next, function () {
$.message({ message: msg('attachmentDeleteSuccess') })
renderUploadList(relistNow)
})
},
function () {
$.message({ message: msg('attachmentDeleteFailed') })
}
)
})
})
function uploadImage(file) {
$.message({
message: msg('picUploading'),
autoClose: false
});
get_info(function (info) {
if (isV1Flavor(info)) {
uploadImageNowV1(file)
return
}
const reader = new FileReader();
reader.onload = function(e) {
const base64String = e.target.result.split(',')[1];
uploadImageNow(base64String, file);
};
reader.onerror = function(error) {
console.error('Error reading file:', error);
};
reader.readAsDataURL(file);
})
};
function uploadImageNowV1(file) {
get_info(function (info) {
if (!info.status) {
$.message({ message: msg('placeApiUrl') })
return
}
let old_name = file.name.split('.')
let file_ext = file.name.split('.').pop()
let now = dayjs().format('YYYYMMDDHHmmss')
let new_name = old_name[0] + '_' + now + '.' + file_ext
window.MemosApiV1.uploadResourceBlob(
info,
file,
{ filename: new_name, type: file.type },
function (entity) {
const inferredId = (function () {
if (!entity) return null
const v = entity.id != null ? entity.id : entity.ID != null ? entity.ID : entity.Id
if (typeof v === 'number' && Number.isFinite(v)) return Math.floor(v)
if (typeof v === 'string' && v.trim() !== '' && !Number.isNaN(Number(v))) return Math.floor(Number(v))
return null
})()
// v0.18: resource entity has no `name`, only `id/filename/type/...`.
// Treat having an id as a successful upload.
if (entity && (entity.name || inferredId != null)) {
const name = entity.name || (inferredId != null ? 'resources/' + String(inferredId) : '')
relistNow.push({
id: inferredId != null ? inferredId : entity.id,
name: name,
filename: entity.filename || new_name,
createTime: entity.createTime || entity.createdTs || entity.createdAt,
type: entity.type
})
chrome.storage.sync.set({ open_action: '', open_content: '', resourceIdList: relistNow }, function () {
$.message({ message: msg('picSuccess') })
})
return
}
chrome.storage.sync.set({ open_action: '', open_content: '' }, function () {
$.message({ message: msg('picFailed') })
})
},
function () {
$.message({ message: msg('picFailed') })
}
)
})
}
function uploadImageNow(base64String, file) {
get_info(function(info) {
if (info.status) {
let old_name = file.name.split('.');
let file_ext = file.name.split('.').pop();
let now = dayjs().format('YYYYMMDDHHmmss');
let new_name = old_name[0] + '_' + now + '.' + file_ext;
var hideTag = info.hidetag
var showTag = info.showtag
var nowTag = $("textarea[name=text]").val().match(/(#[^\s#]+)/)
var sendvisi = info.memo_lock || ''
if(nowTag){
if(nowTag[1] == showTag){
sendvisi = 'PUBLIC'
}else if(nowTag[1] == hideTag){
sendvisi = 'PRIVATE'
}
}
const data = {
content: base64String,
visibility: sendvisi,
filename: new_name,
type: file.type
};
window.MemosApi.uploadAttachmentOrResource(
info,
data,
function (resp) {
const entity = (resp && resp.resource) || resp
if (entity && entity.name) {
relistNow.push({
name: entity.name,
filename: entity.filename || new_name,
createTime: entity.createTime,
type: entity.type
})
chrome.storage.sync.set(
{
open_action: '',
open_content: '',
resourceIdList: relistNow
},
function () {
$.message({ message: msg('picSuccess') })
}
)
return
}
chrome.storage.sync.set(
{
open_action: '',
open_content: '',
resourceIdList: []
},
function () {
$.message({ message: msg('picFailed') })
}
)
},
function () {
$.message({ message: msg('picFailed') })
}
)
}else {
$.message({
message: msg('placeApiUrl')
})
}
});
}
$('#saveKey').click(function () {
var apiUrl = $('#apiUrl').val()
if (apiUrl.length > 0 && !apiUrl.endsWith('/')) {
apiUrl += '/';
}
var apiTokens = $('#apiTokens').val()
window.MemosApi.authWithFallback(apiUrl, apiTokens, function (auth) {
if (!auth || auth.userId == null) {
$.message({ message: msg('invalidToken') })
return
}
chrome.storage.sync.set(
{
apiUrl: apiUrl,
apiTokens: apiTokens,
userid: auth.userId,
memoUiPath: auth.uiPath || 'memos',
apiFlavor: ''
},
function () {
$.message({ message: msg('saveSuccess') })
$('#blog_info').hide()
// Auto-detect API flavor once; keep default behavior when unknown.
if (window.MemosApiV023 && typeof window.MemosApiV023.probeApiFlavor === 'function') {
window.MemosApiV023.probeApiFlavor(apiUrl, apiTokens, function (res) {
const flavor = res && res.flavor ? res.flavor : ''
const normalized = flavor === 'v020' || flavor === 'v021' ? 'v1' : flavor
if (normalized === 'v1' || normalized === 'v023' || normalized === 'modern') {
chrome.storage.sync.set({ apiFlavor: normalized })
}
})
}
}
)
})
});
$('#opensite').click(function () {
get_info(function (info) {
chrome.tabs.create({url:info.apiUrl})
})
})
// 0.23.1版本 GET api/v1/{parent}/tags 接口已移除,参考 https://github.com/usememos/memos/issues/4161
$('#tags').click(function () {
get_info(function (info) {
if (info.apiUrl) {
var parent = `users/${info.userid}`;
// 从最近的1000条memo中获取tags,因此不保证获取能全部的
var tagDom = "";
const renderTags = function (tags) {
const uniTags = [...new Set((Array.isArray(tags) ? tags : []).filter(Boolean))]
$.each(uniTags, function (_, tag) {
tagDom += '#' + tag + '';
});
tagDom += ''
$("#taglist").html(tagDom).slideToggle(500)
}
const onTagsData = function (data) {
const memos = window.MemosApi.extractMemosListFromResponse(data)
const allTags = memos.flatMap(function (memo) {
if (!memo) return []
// v0.23 response may include `tags: []` while actual tags live in `memo.property.tags`.
// So when v0.23 flavor is detected, always use the compat extractor first.
if (isV023Flavor(info)) return window.MemosApiV023.extractTagsFromMemo(memo)
if (Array.isArray(memo.tags) && memo.tags.length > 0) return memo.tags
if (Array.isArray(memo.tagList) && memo.tagList.length > 0) return memo.tagList
if (memo.property && Array.isArray(memo.property.tags) && memo.property.tags.length > 0) {
return memo.property.tags
}
return []
})
const uniTags = [...new Set(allTags.filter(Boolean))]
renderTags(uniTags)
}
if (isV1Flavor(info)) {
window.MemosApiV1.getTagSuggestion(
info,
function (tags) {
renderTags(Array.isArray(tags) ? tags : [])
},
function () {
$.message({ message: msg('placeApiUrl') })
}
)
} else if (isV023Flavor(info)) {
const filterExpr = window.MemosApiV023.buildFilter({
rowStatus: 'NORMAL',
creator: 'users/' + info.userid
})
window.MemosApiV023.listMemos(
info,
{
pageSize: 1000,
filterExpr: filterExpr
},
onTagsData,
function () {
$.message({ message: msg('placeApiUrl') })
}
)
} else {
window.MemosApi.fetchMemosWithFallback(
info,
'?pageSize=1000',
onTagsData,
function () {
$.message({ message: msg('placeApiUrl') })
}
)
}
} else {
$.message({
message: msg('placeApiUrl')
})
}
})
})
$(document).on("click","#hideTag",function () {
$('#taghide').slideToggle(500)
})
$('#saveTag').click(function () {
// 保存数据
chrome.storage.sync.set(
{
hidetag: $('#hideInput').val(),
showtag: $('#showInput').val()
},
function () {
$.message({
message: msg('saveSuccess')
})
$('#taghide').hide()
}
)
})
$('#lock').click(function () {
$("#lock-wrapper").toggleClass( "!hidden", 1000 );
})
$(document).on("click",".item-lock",function () {
$("#lock-wrapper").toggleClass( "!hidden", 1000 );
$("#lock-now").text($(this).text())
_this = $(this)[0].dataset.type;
currentMemoLock = _this
chrome.storage.sync.set(
{memo_lock: _this}
)
})
$('#search').click(function () {
get_info(function (info) {
const pattern = $("textarea[name=text]").val()
var parent = `users/${info.userid}`;
const patternLiteral = JSON.stringify(String(pattern || ''))
var filter = "?filter=" + encodeURIComponent(`visibility in ["PUBLIC","PROTECTED"] && content.contains(${patternLiteral})`);
if (info.status) {
$("#randomlist").html('').hide()
var searchDom = ""
if(pattern){
const runSearch = isV023Flavor(info)
? function (onOk, onFail) {
const filterExpr = window.MemosApiV023.buildFilter({
visibilities: ['PUBLIC', 'PROTECTED'],
contentSearch: String(pattern)
})
window.MemosApiV023.listMemos(info, { pageSize: 1000, filterExpr: filterExpr }, onOk, onFail)
}
: isV1Flavor(info)
? function (onOk, onFail) {
window.MemosApiV1.listMemos(info, { limit: 1000, rowStatus: 'NORMAL', contentSearch: String(pattern) }, onOk, onFail)
}
: function (onOk, onFail) {
window.MemosApi.fetchMemosWithFallback(info, filter, onOk, onFail)
}
runSearch(
function (data) {
let searchData = window.MemosApi.extractMemosListFromResponse(data)
if(searchData.length == 0){
$.message({
message: msg('searchNone')
})
}else{
for(var i=0;i < searchData.length;i++){
var memosID = getMemoUid(searchData[i])
var timeText = memoFromNow(searchData[i])
searchDom += ''+(searchData[i].content || '').replace(/!\[.*?\]\((.*?)\)/g,'

').replace(/\[(.*?)\]\((.*?)\)/g,'
$1 ')+'
'
var resources = (searchData[i].attachments && searchData[i].attachments.length > 0) ? searchData[i].attachments : ((searchData[i].resources && searchData[i].resources.length > 0) ? searchData[i].resources : (searchData[i].resourceList || []));
if(resources && resources.length > 0){
for(var j=0;j < resources.length;j++){
var restype = (resources[j].type || '').slice(0,5);
var resexlink = resources[j].externalLink
var resLink = '',fileId=''
if(resexlink){
resLink = resexlink
}else{
resLink = buildV1ResourceStreamUrl(info, resources[j])
}
if (!resLink) {
continue
}
if(restype == 'image'){
if (isV1Flavor(info)) {
searchDom += '
![]()
'
} else {
searchDom += '

'
}
}
if(restype !== 'image'){
searchDom += '
'+resources[j].filename+''
}
}
}
searchDom += '
'
}
window.ViewImage && ViewImage.init('.random-image')
$("#randomlist").html(searchDom).slideDown(500);
hydrateV1PreviewImages(info)
}
},
function (xhr) {
$.message({ message: msg('searchNone') })
}
)
}else{
$.message({
message: msg('searchNow')
})
}
} else {
$.message({
message: msg('placeApiUrl')
})
}
})
})
$('#random').click(function () {
get_info(function (info) {
var parent = `users/${info.userid}`;
if (info.status) {
$("#randomlist").html('').hide()
const runRandom = isV023Flavor(info)
? function (onOk, onFail) {
const filterExpr = window.MemosApiV023.buildFilter({ visibilities: ['PUBLIC', 'PROTECTED'] })
window.MemosApiV023.listMemos(info, { pageSize: 1000, filterExpr: filterExpr }, onOk, onFail)
}
: isV1Flavor(info)
? function (onOk, onFail) {
window.MemosApiV1.listMemos(info, { limit: 1000, rowStatus: 'NORMAL' }, onOk, onFail)
}
: function (onOk, onFail) {
const filter = "?filter=" + encodeURIComponent(`visibility in ["PUBLIC","PROTECTED"]`);
window.MemosApi.fetchMemosWithFallback(info, filter, onOk, onFail)
}
runRandom(
function (data) {
const memos = window.MemosApi.extractMemosListFromResponse(data)
let randomNum = Math.floor(Math.random() * (memos.length));
var randomData = memos[randomNum]
randDom(randomData)
},
function () {
$.message({ message: msg('placeApiUrl') })
}
)
} else {
$.message({
message: msg('placeApiUrl')
})
}
})
})
function randDom(randomData){
get_info(function (info) {
var memosID = getMemoUid(randomData)
var timeText = memoFromNow(randomData)
var randomDom = ''+(randomData && randomData.content ? randomData.content : '').replace(/!\[.*?\]\((.*?)\)/g,'

').replace(/\[(.*?)\]\((.*?)\)/g,'
$1 ')+'
'
var resources = (randomData.attachments && randomData.attachments.length > 0) ? randomData.attachments : ((randomData.resources && randomData.resources.length > 0) ? randomData.resources : (randomData.resourceList || []));
if(resources && resources.length > 0){
for(var j=0;j < resources.length;j++){
var restype = (resources[j].type || '').slice(0,5);
var resexlink = resources[j].externalLink
var resLink = '',fileId=''
if(resexlink){
resLink = resexlink
}else{
resLink = buildV1ResourceStreamUrl(info, resources[j])
}
if (!resLink) {
continue
}
if(restype == 'image'){
if (isV1Flavor(info)) {
randomDom += '
![]()
'
} else {
randomDom += '

'
}
}
if(restype !== 'image'){
randomDom += '
'+resources[j].filename+''
}
}
}
randomDom += '
'
window.ViewImage && ViewImage.init('.random-image')
$("#randomlist").html(randomDom).slideDown(500);
hydrateV1PreviewImages(info)
})
}
$(document).on("click","#random-link",function () {
var memoUid = $("#random-link").data('uid');
get_info(function (info) {
const path = (info.memoUiPath || 'memos').replace(/^\/+|\/+$/g, '')
chrome.tabs.create({url:info.apiUrl + path + "/" + memoUid})
})
})
$(document).on("click","#random-delete",function () {
get_info(function (info) {
// var memoUid = $("#random-delete").data('uid');
var memosName = $("#random-delete").data('name');
var memoId = $("#random-delete").data('id');
// v0.20/v0.21: archive memo via API v1 PATCH /api/v1/memo/:id
if (isV1Flavor(info) && memoId) {
window.MemosApiV1.patchMemo(
info,
memoId,
{ rowStatus: 'ARCHIVED' },
function () {
$("#randomlist").html('').hide()
$.message({ message: msg('archiveSuccess') })
},
function () {
$.message({ message: msg('archiveFailed') })
}
)
return
}
var deleteUrl = info.apiUrl+'api/v1/'+memosName
$.ajax({
url:deleteUrl,
type:"PATCH",
data:JSON.stringify({
// 'uid': memoUid,
'state': "ARCHIVED"
}),
contentType:"application/json",
dataType:"json",
headers : {'Authorization':'Bearer ' + info.apiTokens},
success: function(result){
$("#randomlist").html('').hide()
$.message({
message: msg('archiveSuccess')
})
},error:function(err){//清空open_action(打开时候进行的操作),同时清空open_content
$.message({
message: msg('archiveFailed')
})
}
})
})
})
$(document).on("click",".item-container",function () {
var tagHtml = $(this).text()+" "
add(tagHtml);
})
$('#newtodo').click(function () {
var tagHtml = "\n- [ ] "
add(tagHtml);
})
$('#getlink').click(function () {
chrome.tabs.query({ active: true, currentWindow: true }, ([tab]) => {
var linkHtml = " ["+tab.title+"]("+tab.url+") "
if(tab.url){
add(linkHtml);
}else{
$.message({
message: msg('getTabFailed')
})
}
})
})
$('#upres').click(async function () {
$('#inFile').click()
})
$('#inFile').on('change', function(data){
var fileVal = $('#inFile').val();
var file = null
if(fileVal == '') {
return;
}
file= this.files[0];
uploadImage(file)
});
function add(str) {
var tc = document.getElementById("content");
var tclen = tc.value.length;
tc.focus();
if(typeof document.selection != "undefined"){
document.selection.createRange().text = str;
}else{
tc.value =
tc.value.substr(0, tc.selectionStart) +
str +
tc.value.substring(tc.selectionStart, tclen);
}
}
$('#blog_info_edit').click(function () {
$('#blog_info').slideToggle()
})
$('#content_submit_text').click(function () {
var contentVal = $("textarea[name=text]").val()
if(contentVal){
sendText()
}else{
$.message({
message: msg('placeContent')
})
}
})
function getOne(memosId){
get_info(function (info) {
if (info.apiUrl) {
$("#randomlist").html('').hide()
var getUrl = isV1Flavor(info) ? info.apiUrl+'api/v1/memo/'+memosId : info.apiUrl+'api/v1/'+memosId
$.ajax({
url:getUrl,
type:"GET",
contentType:"application/json",
dataType:"json",
headers : {'Authorization':'Bearer ' + info.apiTokens},
success: function(data){
randDom(data)
}
})
} else {
$.message({
message: msg('placeApiUrl')
})
}
})
}
function sendText() {
get_info(function (info) {
if (info.status) {
$.message({
message: msg('memoUploading')
})
//$("#content_submit_text").attr('disabled','disabled');
let content = $("textarea[name=text]").val()
var hideTag = info.hidetag
var showTag = info.showtag
var nowTag = $("textarea[name=text]").val().match(/(#[^\s#]+)/)
var sendvisi = info.memo_lock || ''
if(nowTag){
if(nowTag[1] == showTag){
sendvisi = 'PUBLIC'
}else if(nowTag[1] == hideTag){
sendvisi = 'PRIVATE'
}
}
// Memos v0.20/v0.21: use /api/v1/memo and bind resources by numeric IDs.
if (isV1Flavor(info)) {
const items = Array.isArray(info.resourceIdList) ? info.resourceIdList : []
const resourceIdList = items
.map(function (r) {
if (!r) return null
if (typeof r.id === 'number' && Number.isFinite(r.id)) return Math.floor(r.id)
if (typeof r.id === 'string' && r.id.trim() !== '' && !Number.isNaN(Number(r.id))) {
return Math.floor(Number(r.id))
}
// Some versions store name as resources/{id}.
const n = typeof r.name === 'string' ? r.name : ''
const tail = n ? n.split('/').pop() : ''
if (tail && !Number.isNaN(Number(tail))) return Math.floor(Number(tail))
return null
})
.filter(function (x) {
return x != null && Number.isFinite(x)
})
window.MemosApiV1.createMemo(
info,
{
content: content,
visibility: sendvisi,
resourceIdList: resourceIdList
},
function (data) {
chrome.storage.sync.set(
{ open_action: '', open_content: '', resourceIdList: [] },
function () {
$.message({ message: msg('memoSuccess') })
$("textarea[name=text]").val('')
relistNow = []
renderUploadList(relistNow)
randDom(data)
}
)
},
function () {
chrome.storage.sync.set(
{ open_action: '', open_content: '' },
function () {
$.message({ message: msg('memoFailed') })
}
)
}
)
return
}
$.ajax({
url:info.apiUrl+'api/v1/memos',
type:"POST",
data:JSON.stringify({
'content': content,
'visibility': sendvisi
}),
contentType:"application/json",
dataType:"json",
headers : {'Authorization':'Bearer ' + info.apiTokens},
success: function(data){
if(info.resourceIdList.length > 0 ){
//匹配图片
window.MemosApi.patchMemoWithAttachmentsOrResources(
info,
data.name,
info.resourceIdList,
function () {
getOne(data.name)
},
function () {
getOne(data.name)
}
)
}else{
getOne(data.name)
}
chrome.storage.sync.set(
{ open_action: '', open_content: '',resourceIdList:[]},
function () {
$.message({
message: msg('memoSuccess')
})
//$("#content_submit_text").removeAttr('disabled');
$("textarea[name=text]").val('')
relistNow = []
renderUploadList(relistNow)
}
)
},error:function(err){//清空open_action(打开时候进行的操作),同时清空open_content
chrome.storage.sync.set(
{ open_action: '', open_content: '',resourceIdList:[] },
function () {
$.message({
message: msg('memoFailed')
})
}
)},
})
} else {
$.message({
message: msg('placeApiUrl')
})
}
})
}