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 += '
'+timeText+'
'+(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 = '
'+timeText+'
'+(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') }) } }) }