// ==UserScript== // @name Universal Image Uploader // @name:zh-CN 通用图片上传助手 // @name:zh-TW 通用圖片上傳助手 // @namespace https://github.com/utags // @homepageURL https://github.com/utags/userscripts#readme // @supportURL https://github.com/utags/userscripts/issues // @version 0.7.0 // @description Paste/drag/select images, batch upload to Imgur/Tikolu/MJJ.Today/Appinn; auto-copy Markdown/HTML/BBCode/link; site button integration with SPA observer; local history. // @description:zh-CN 通用图片上传与插入:支持粘贴/拖拽/选择,批量上传至 Imgur/Tikolu/MJJ.Today/Appinn;自动复制 Markdown/HTML/BBCode/链接;可为各站点插入按钮并适配 SPA;保存本地历史。 // @description:zh-TW 通用圖片上傳與插入:支援貼上/拖曳/選擇,批次上傳至 Imgur/Tikolu/MJJ.Today/Appinn;自動複製 Markdown/HTML/BBCode/連結;可為各站點插入按鈕並適配 SPA;保存本地歷史。 // @author Pipecraft // @license MIT // @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCIgZmlsbD0ibm9uZSI+PHJlY3QgeD0iOCIgeT0iOCIgd2lkdGg9IjQ4IiBoZWlnaHQ9IjQ4IiByeD0iMTAiIHN0cm9rZT0iIzFmMjkzNyIgc3Ryb2tlLXdpZHRoPSI0Ii8+PHBhdGggZD0iTTMyIDIwbC0xMiAxMmg3djE4aDEwVjMyaDdsLTEyLTEyeiIgZmlsbD0iIzFmMjkzNyIvPjwvc3ZnPg== // @match https://*.v2ex.com/* // @match https://*.v2ex.co/* // @match https://greasyfork.org/* // @match https://www.nodeseek.com/* // @match https://www.deepflood.com/* // @match https://2libra.com/* // @match *://*/* // @connect api.imgur.com // @connect tikolu.net // @connect mjj.today // @connect h1.appinn.me // @grant GM_registerMenuCommand // @grant GM.addValueChangeListener // @grant GM_addValueChangeListener // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue // @grant GM_addStyle // @grant GM.xmlHttpRequest // @grant GM_xmlhttpRequest // @grant GM_setClipboard // ==/UserScript== // ;(() => { 'use strict' var __defProp = Object.defineProperty var __getOwnPropSymbols = Object.getOwnPropertySymbols var __hasOwnProp = Object.prototype.hasOwnProperty var __propIsEnum = Object.prototype.propertyIsEnumerable var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value, }) : (obj[key] = value) var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]) if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]) } return a } var win = globalThis function isTopFrame() { return win.self === win.top } function registerMenu(caption, onClick) { if (typeof GM_registerMenuCommand === 'function') { return GM_registerMenuCommand(caption, onClick) } return 0 } async function addValueChangeListener(key, callback) { if ( typeof GM !== 'undefined' && typeof GM.addValueChangeListener === 'function' ) { return GM.addValueChangeListener(key, callback) } if (typeof GM_addValueChangeListener === 'function') { return GM_addValueChangeListener(key, callback) } return 0 } var CONFIG = { localhost: { enabled: true, pasteEnabled: true, dragAndDropEnabled: true, format: 'markdown', host: 'imgur', proxy: 'none', buttons: [ { selector: 'textarea', position: 'after', text: '\u63D2\u5165\u56FE\u7247', }, ], }, 'v2ex.com': { enabled: true, pasteEnabled: true, dragAndDropEnabled: true, format: 'link', host: 'imgur', proxy: 'none', buttons: [ { selector: '#reply-box > div.cell.flex-one-row > div:nth-child(1)', position: 'inside', text: ' + \u63D2\u5165\u56FE\u7247', }, { selector: '#tab-preview', position: 'after', text: ' + \u63D2\u5165\u56FE\u7247', }, { selector: 'button[onclick^="previewTopicContent"]', position: 'before', text: '', }, ], }, 'greasyfork.org': { enabled: true, pasteEnabled: true, dragAndDropEnabled: true, format: 'markdown', host: 'tikolu', proxy: 'wsrv.nl', buttons: [ { selector: '.comment-screenshot-control', position: 'before', }, ], }, 'nodeseek.com': { enabled: true, pasteEnabled: true, dragAndDropEnabled: true, format: 'markdown', host: 'tikolu', proxy: 'wsrv.nl', buttons: [ { selector: '#editor-body > div.mde-toolbar > .toolbar-item:last-of-type', position: 'after', text: '\u63D2\u5165\u56FE\u7247', }, ], }, 'deepflood.com': { enabled: true, pasteEnabled: true, dragAndDropEnabled: true, format: 'markdown', host: 'tikolu', proxy: 'wsrv.nl', buttons: [ { selector: '#editor-body > div.mde-toolbar > .toolbar-item:last-of-type', position: 'after', text: '\u63D2\u5165\u56FE\u7247', }, ], }, '2libra.com': { enabled: true, pasteEnabled: true, dragAndDropEnabled: true, format: 'markdown', host: 'tikolu', proxy: 'wsrv.nl', buttons: [ { selector: '.w-md-editor > div.w-md-editor-toolbar > ul:nth-child(1) > li:last-of-type', position: 'after', text: '\u63D2\u5165\u56FE\u7247', }, ], }, 'meta.appinn.net': { enabled: true, pasteEnabled: false, dragAndDropEnabled: false, format: 'markdown', host: 'appinn', proxy: 'none', buttons: [ { selector: '.toolbar__button.upload', position: 'after', text: '', }, ], }, 'github.com': { enabled: true, pasteEnabled: false, dragAndDropEnabled: false, format: 'markdown', host: 'tikolu', proxy: 'wsrv.nl', }, } var I18N = { en: { header_title: 'Universal Image Uploader', btn_history: 'History', btn_settings: 'Settings', btn_close: 'Close', format_markdown: 'Markdown', format_html: 'HTML', format_bbcode: 'BBCode', format_link: 'Link', host_imgur: 'Imgur', host_tikolu: 'Tikolu', host_mjj: 'MJJ.Today', host_appinn: 'Appinn', btn_select_images: 'Select Images', progress_initial: 'Done 0/0', progress_done: 'Done {done}/{total}', hint_text: 'Paste or drag images onto the page, or click Select to batch upload', settings_section_title: 'Settings', settings_site_buttons: 'Site Button Settings', placeholder_css_selector: 'CSS Selector', pos_before: 'Before', pos_after: 'After', pos_inside: 'Inside', placeholder_button_content: 'Button content (HTML allowed)', insert_image_button_default: 'Insert image', btn_save_and_insert: 'Save & Insert', btn_remove_button_temp: 'Remove button (temporary)', btn_clear_settings: 'Clear settings', drop_overlay: 'Release to upload images', log_uploading: 'Uploading: ', log_success: '\u2705 Success: ', log_failed: '\u274C Failed: ', btn_copy: 'Copy', btn_open: 'Open', btn_delete: 'Delete', btn_edit: 'Edit', btn_update: 'Update', btn_cancel: 'Cancel', menu_open_panel: 'Open upload panel', menu_select_images: 'Select images', menu_settings: 'Settings', menu_enable_site: 'Enable uploader for this site', menu_disable_site: 'Disable uploader for this site', toggle_paste_enabled: 'Enable paste upload', toggle_drag_enabled: 'Enable drag-and-drop upload', formats_section_title: 'Custom Formats', placeholder_format_name: 'Format name', placeholder_format_template: 'Format template', example_format_template: 'Example: {name} - {link}', btn_add_format: 'Add format', formats_col_name: 'Name', formats_col_template: 'Format', formats_col_ops: 'Actions', history_upload_page_prefix: 'Upload page: ', history_upload_page: 'Upload page: {host}', btn_history_count: 'History ({count})', btn_clear_history: 'Clear', default_image_name: 'image', proxy_none: 'No proxy', proxy_wsrv_nl: 'wsrv.nl', error_network: 'Network error', error_upload_failed: 'Upload failed', placeholder_uploading: 'Uploading "{name}"...', placeholder_upload_failed: 'Upload failed: {name}', }, 'zh-CN': { header_title: '\u901A\u7528\u56FE\u7247\u4E0A\u4F20\u52A9\u624B', btn_history: '\u5386\u53F2', btn_settings: '\u8BBE\u7F6E', btn_close: '\u5173\u95ED', format_markdown: 'Markdown', format_html: 'HTML', format_bbcode: 'BBCode', format_link: '\u94FE\u63A5', host_imgur: 'Imgur', host_tikolu: 'Tikolu', host_mjj: 'MJJ.Today', host_appinn: 'Appinn', btn_select_images: '\u9009\u62E9\u56FE\u7247', progress_initial: '\u5B8C\u6210 0/0', progress_done: '\u5B8C\u6210 {done}/{total}', hint_text: '\u652F\u6301\u7C98\u8D34\u56FE\u7247\u3001\u62D6\u62FD\u56FE\u7247\u5230\u9875\u9762\u6216\u70B9\u51FB\u9009\u62E9\u56FE\u7247\u8FDB\u884C\u6279\u91CF\u4E0A\u4F20', settings_section_title: '\u8BBE\u7F6E', settings_site_buttons: '\u7AD9\u70B9\u6309\u94AE\u8BBE\u7F6E', placeholder_css_selector: 'CSS \u9009\u62E9\u5668', pos_before: '\u4E4B\u524D', pos_after: '\u4E4B\u540E', pos_inside: '\u91CC\u9762', placeholder_button_content: '\u6309\u94AE\u5185\u5BB9\uFF08\u53EF\u4E3A HTML\uFF09', insert_image_button_default: '\u63D2\u5165\u56FE\u7247', btn_save_and_insert: '\u4FDD\u5B58\u5E76\u63D2\u5165', btn_remove_button_temp: '\u79FB\u9664\u6309\u94AE\uFF08\u4E34\u65F6\uFF09', btn_clear_settings: '\u6E05\u7A7A\u8BBE\u7F6E', drop_overlay: '\u91CA\u653E\u4EE5\u4E0A\u4F20\u56FE\u7247', log_uploading: '\u4E0A\u4F20\u4E2D\uFF1A', log_success: '\u2705 \u6210\u529F\uFF1A', log_failed: '\u274C \u5931\u8D25\uFF1A', btn_copy: '\u590D\u5236', btn_open: '\u6253\u5F00', btn_delete: '\u5220\u9664', btn_edit: '\u7F16\u8F91', btn_update: '\u66F4\u65B0', btn_cancel: '\u53D6\u6D88', menu_open_panel: '\u6253\u5F00\u56FE\u7247\u4E0A\u4F20\u9762\u677F', menu_select_images: '\u9009\u62E9\u56FE\u7247', menu_settings: '\u8BBE\u7F6E', menu_enable_site: '\u4E3A\u6B64\u7AD9\u70B9\u542F\u7528\u4E0A\u4F20', menu_disable_site: '\u4E3A\u6B64\u7AD9\u70B9\u7981\u7528\u4E0A\u4F20', toggle_paste_enabled: '\u542F\u7528\u7C98\u8D34\u4E0A\u4F20', toggle_drag_enabled: '\u542F\u7528\u62D6\u62FD\u4E0A\u4F20', formats_section_title: '\u81EA\u5B9A\u4E49\u683C\u5F0F', placeholder_format_name: '\u683C\u5F0F\u540D\u79F0', placeholder_format_template: '\u683C\u5F0F\u5185\u5BB9', example_format_template: '\u793A\u4F8B\uFF1A{name} - {link}', btn_add_format: '\u6DFB\u52A0\u683C\u5F0F', formats_col_name: '\u540D\u5B57', formats_col_template: '\u683C\u5F0F', formats_col_ops: '\u64CD\u4F5C', history_upload_page_prefix: '\u4E0A\u4F20\u9875\u9762\uFF1A', history_upload_page: '\u4E0A\u4F20\u9875\u9762\uFF1A{host}', btn_history_count: '\u5386\u53F2\uFF08{count}\uFF09', btn_clear_history: '\u6E05\u7A7A', default_image_name: '\u56FE\u7247', proxy_none: '\u65E0\u4EE3\u7406', proxy_wsrv_nl: 'wsrv.nl', error_network: '\u7F51\u7EDC\u9519\u8BEF', error_upload_failed: '\u4E0A\u4F20\u5931\u8D25', placeholder_uploading: '\u6B63\u5728\u4E0A\u4F20\u300C{name}\u300D...', placeholder_upload_failed: '\u4E0A\u4F20\u5931\u8D25\uFF1A{name}', }, 'zh-TW': { header_title: '\u901A\u7528\u5716\u7247\u4E0A\u50B3\u52A9\u624B', btn_history: '\u6B77\u53F2', btn_settings: '\u8A2D\u5B9A', btn_close: '\u95DC\u9589', format_markdown: 'Markdown', format_html: 'HTML', format_bbcode: 'BBCode', format_link: '\u9023\u7D50', host_imgur: 'Imgur', host_tikolu: 'Tikolu', host_mjj: 'MJJ.Today', host_appinn: 'Appinn', btn_select_images: '\u9078\u64C7\u5716\u7247', progress_initial: '\u5B8C\u6210 0/0', progress_done: '\u5B8C\u6210 {done}/{total}', hint_text: '\u652F\u63F4\u8CBC\u4E0A\u3001\u62D6\u66F3\u5716\u7247\u5230\u9801\u9762\u6216\u9EDE\u64CA\u9078\u64C7\u6A94\u6848\u9032\u884C\u6279\u6B21\u4E0A\u50B3', settings_section_title: '\u8A2D\u5B9A', settings_site_buttons: '\u7AD9\u9EDE\u6309\u9215\u8A2D\u5B9A', placeholder_css_selector: 'CSS \u9078\u64C7\u5668', pos_before: '\u4E4B\u524D', pos_after: '\u4E4B\u5F8C', pos_inside: '\u88E1\u9762', placeholder_button_content: '\u6309\u9215\u5167\u5BB9\uFF08\u53EF\u70BA HTML\uFF09', insert_image_button_default: '\u63D2\u5165\u5716\u7247', btn_save_and_insert: '\u4FDD\u5B58\u4E26\u63D2\u5165', btn_remove_button_temp: '\u79FB\u9664\u6309\u9215\uFF08\u66AB\u6642\uFF09', btn_clear_settings: '\u6E05\u7A7A\u8A2D\u5B9A', drop_overlay: '\u653E\u958B\u4EE5\u4E0A\u50B3\u5716\u7247', log_uploading: '\u4E0A\u50B3\u4E2D\uFF1A', log_success: '\u2705 \u6210\u529F\uFF1A', log_failed: '\u274C \u5931\u6557\uFF1A', btn_copy: '\u8907\u88FD', btn_open: '\u6253\u958B', btn_delete: '\u522A\u9664', btn_edit: '\u7DE8\u8F2F', btn_update: '\u66F4\u65B0', btn_cancel: '\u53D6\u6D88', menu_open_panel: '\u6253\u958B\u5716\u7247\u4E0A\u50B3\u9762\u677F', menu_select_images: '\u9078\u64C7\u5716\u7247', menu_settings: '\u8A2D\u5B9A', menu_enable_site: '\u70BA\u6B64\u7AD9\u9EDE\u555F\u7528\u4E0A\u50B3', menu_disable_site: '\u70BA\u6B64\u7AD9\u9EDE\u505C\u7528\u4E0A\u50B3', toggle_paste_enabled: '\u555F\u7528\u8CBC\u4E0A\u4E0A\u50B3', toggle_drag_enabled: '\u555F\u7528\u62D6\u66F3\u4E0A\u50B3', formats_section_title: '\u81EA\u8A02\u683C\u5F0F', placeholder_format_name: '\u683C\u5F0F\u540D\u7A31', placeholder_format_template: '\u683C\u5F0F\u5167\u5BB9', example_format_template: '\u7BC4\u4F8B\uFF1A{name} - {link}', btn_add_format: '\u65B0\u589E\u683C\u5F0F', formats_col_name: '\u540D\u7A31', formats_col_template: '\u683C\u5F0F', formats_col_ops: '\u64CD\u4F5C', history_upload_page_prefix: '\u4E0A\u50B3\u9801\u9762\uFF1A', history_upload_page: '\u4E0A\u50B3\u9801\u9762\uFF1A{host}', btn_history_count: '\u6B77\u53F2\uFF08{count}\uFF09', btn_clear_history: '\u6E05\u7A7A', default_image_name: '\u5716\u7247', proxy_none: '\u4E0D\u4F7F\u7528\u4EE3\u7406', proxy_wsrv_nl: 'wsrv.nl', error_network: '\u7DB2\u8DEF\u932F\u8AA4', error_upload_failed: '\u4E0A\u50B3\u5931\u6557', placeholder_uploading: '\u6B63\u5728\u4E0A\u50B3\u300C{name}\u300D...', placeholder_upload_failed: '\u4E0A\u50B3\u5931\u6557\uFF1A{name}', }, } function detectLanguage() { try { const browserLang = ( navigator.language || navigator.userLanguage || 'en' ).toLowerCase() const supported = Object.keys(I18N) if (supported.includes(browserLang)) return browserLang const base = browserLang.split('-')[0] const match = supported.find((l) => l.startsWith(base + '-')) return match || 'en' } catch (e) { return 'en' } } var USER_LANG = detectLanguage() function t(key) { return (I18N[USER_LANG] && I18N[USER_LANG][key]) || I18N.en[key] || key } function tpl(str, params) { return String(str).replaceAll(/{(\w+)}/g, (_, k) => { var _a return ''.concat( (_a = params == null ? void 0 : params[k]) != null ? _a : '' ) }) } var IMGUR_CLIENT_IDS = [ '3107b9ef8b316f3', '442b04f26eefc8a', '59cfebe717c09e4', '60605aad4a62882', '6c65ab1d3f5452a', '83e123737849aa9', '9311f6be1c10160', 'c4a4a563f698595', '81be04b9e4a08ce', ] var HISTORY_KEY = 'uiu_history' var FORMAT_MAP_KEY = 'uiu_format_map' var BTN_SETTINGS_MAP_KEY = 'uiu_site_btn_settings_map' var HOST_MAP_KEY = 'uiu_host_map' var PROXY_MAP_KEY = 'uiu_proxy_map' var SITE_SETTINGS_MAP_KEY = 'uiu_site_settings_map' var CUSTOM_FORMATS_KEY = 'uiu_custom_formats' var DEFAULT_FORMAT = 'markdown' var DEFAULT_HOST = 'tikolu' var DEFAULT_PROXY = 'wsrv.nl' var ALLOWED_FORMATS = ['markdown', 'html', 'bbcode', 'link'] var ALLOWED_HOSTS = ['imgur', 'tikolu', 'mjj', 'appinn'] var ALLOWED_PROXIES = ['none', 'wsrv.nl'] var ALLOWED_BUTTON_POSITIONS = ['before', 'inside', 'after'] var DEFAULT_BUTTON_POSITION = 'after' var APPINN_UPLOAD_ENDPOINT = 'https://h1.appinn.me/upload' var APPINN_UPLOAD_PARAMS = { authCode: 'appinn2', serverCompress: false, uploadChannel: 'telegram', uploadNameType: 'default', autoRetry: true, } function migrateLegacyStorage() { try { const maybeMove = (oldKey, newKey) => { const hasNew = GM_getValue(newKey, void 0) !== void 0 const oldVal = GM_getValue(oldKey, void 0) const hasOld = oldVal !== void 0 if (!hasNew && hasOld) { GM_setValue(newKey, oldVal) try { if (typeof GM_deleteValue === 'function') GM_deleteValue(oldKey) } catch (e) {} } } maybeMove('iu_history', HISTORY_KEY) maybeMove('iu_format_map', FORMAT_MAP_KEY) maybeMove('iu_site_btn_settings_map', BTN_SETTINGS_MAP_KEY) } catch (e) {} } migrateLegacyStorage() function normalizeHost(h) { try { h = String(h || '').trim() return h.startsWith('www.') ? h.slice(4) : h } catch (e) { return h } } function ensureAllowedValue(value, allowedValues, defaultValue) { if (!Array.isArray(allowedValues) || allowedValues.length === 0) { return defaultValue } if (allowedValues.length < 8) { return allowedValues.includes(value) ? value : defaultValue } const set = new Set(allowedValues) return set.has(value) ? value : defaultValue } function getCustomFormats() { try { const list = GM_getValue(CUSTOM_FORMATS_KEY, []) || [] if (!Array.isArray(list)) return [] return list .map((it) => ({ name: String((it == null ? void 0 : it.name) || '').trim(), template: String((it == null ? void 0 : it.template) || ''), })) .filter((it) => it.name && it.template) } catch (e) { return [] } } function setCustomFormats(list) { try { const arr = Array.isArray(list) ? list : [] const normalized = arr .map((it) => ({ name: String((it == null ? void 0 : it.name) || '').trim(), template: String((it == null ? void 0 : it.template) || ''), })) .filter((it) => it.name && it.template) const map = /* @__PURE__ */ new Map() for (const it of normalized) map.set(it.name, it.template) const out = Array.from(map.entries()).map(([name, template]) => ({ name, template, })) GM_setValue(CUSTOM_FORMATS_KEY, out) } catch (e) {} } function upsertCustomFormat(name, template) { try { name = String(name || '').trim() template = String(template || '') if (!name || !template) return const list = getCustomFormats() const idx = list.findIndex((it) => it.name === name) if (idx === -1) { list.push({ name, template }) } else { list[idx] = { name, template } } setCustomFormats(list) } catch (e) {} } function removeCustomFormat(name) { try { name = String(name || '').trim() if (!name) return const list = getCustomFormats().filter((it) => it.name !== name) setCustomFormats(list) } catch (e) {} } function getAllowedFormats() { try { return [...ALLOWED_FORMATS, ...getCustomFormats().map((f) => f.name)] } catch (e) { return [...ALLOWED_FORMATS] } } function ensureAllowedFormat(fmt) { return ensureAllowedValue(fmt, getAllowedFormats(), DEFAULT_FORMAT) } function migrateToUnifiedSiteMap() { var _a, _b, _c, _d, _e, _f try { const existing = GM_getValue(SITE_SETTINGS_MAP_KEY, void 0) const siteMap = existing && typeof existing === 'object' ? existing : {} const isEmpty = !siteMap || Object.keys(siteMap).length === 0 if (!isEmpty) return const formatMap = GM_getValue(FORMAT_MAP_KEY, {}) || {} const hostMap = GM_getValue(HOST_MAP_KEY, {}) || {} const proxyMap = GM_getValue(PROXY_MAP_KEY, {}) || {} const btnMap = GM_getValue(BTN_SETTINGS_MAP_KEY, {}) || {} const rawKeys = /* @__PURE__ */ new Set([ ...Object.keys(formatMap), ...Object.keys(hostMap), ...Object.keys(proxyMap), ...Object.keys(btnMap), ...Object.keys(CONFIG || {}), ]) const keys = /* @__PURE__ */ new Set() for (const k of rawKeys) keys.add(normalizeHost(k)) for (const key of keys) { if (!key) continue const preset = (CONFIG == null ? void 0 : CONFIG[key]) || {} const s = siteMap[key] || {} if (s.format === void 0) { const fmt = (_a = formatMap[key]) != null ? _a : preset.format const normalizedFormat = ensureAllowedFormat(fmt) if (normalizedFormat) s.format = normalizedFormat } if (s.host === void 0) { const h = (_b = hostMap[key]) != null ? _b : preset.host const normalizedHost = ensureAllowedValue(h, ALLOWED_HOSTS) if (normalizedHost) s.host = normalizedHost } if (s.proxy === void 0) { const px = (_c = proxyMap[key]) != null ? _c : preset.proxy const resolved = ensureAllowedValue(px, ALLOWED_PROXIES) if (resolved && resolved !== 'none') s.proxy = resolved } if (s.buttons === void 0) { const raw = (_f = (_e = (_d = btnMap[key]) != null ? _d : preset.buttons) != null ? _e : preset.button) != null ? _f : [] const arr = Array.isArray(raw) ? raw : raw ? [raw] : [] const list = arr .map((c) => { const selector = String( (c == null ? void 0 : c.selector) || '' ).trim() if (!selector) return null const p = String((c == null ? void 0 : c.position) || '').trim() const pos = ensureAllowedValue( p, ALLOWED_BUTTON_POSITIONS, DEFAULT_BUTTON_POSITION ) const text = String( (c == null ? void 0 : c.text) || t('insert_image_button_default') ).trim() return { selector, position: pos, text } }) .filter(Boolean) if (list.length > 0) s.buttons = list } if (Object.keys(s).length > 0) siteMap[key] = s } GM_setValue(SITE_SETTINGS_MAP_KEY, siteMap) try { if (typeof GM_deleteValue === 'function') { GM_deleteValue(FORMAT_MAP_KEY) GM_deleteValue(HOST_MAP_KEY) GM_deleteValue(PROXY_MAP_KEY) GM_deleteValue(BTN_SETTINGS_MAP_KEY) } } catch (e) {} } catch (e) {} } migrateToUnifiedSiteMap() function applyPresetConfig() { try { const siteMap = GM_getValue(SITE_SETTINGS_MAP_KEY, {}) || {} let changed = false for (const [host, preset] of Object.entries(CONFIG || {})) { const key = normalizeHost(host) if (!key || typeof preset !== 'object') continue const s = siteMap[key] || {} if (s.format === void 0 && preset.format) { const normalizedFormat = ensureAllowedValue( preset.format, ALLOWED_FORMATS ) if (normalizedFormat) { s.format = normalizedFormat changed = true } } if (s.host === void 0 && preset.host) { const normalizedHost = ensureAllowedValue(preset.host, ALLOWED_HOSTS) if (normalizedHost) { s.host = normalizedHost changed = true } } if (s.proxy === void 0 && preset.proxy) { const resolved = ensureAllowedValue(preset.proxy, ALLOWED_PROXIES) if (resolved) { s.proxy = resolved changed = true } } if (s.buttons === void 0) { const raw = preset.buttons || preset.button || [] const arr = Array.isArray(raw) ? raw : raw ? [raw] : [] const list = arr .map((c) => { const selector = String( (c == null ? void 0 : c.selector) || '' ).trim() if (!selector) return null const p = String((c == null ? void 0 : c.position) || '').trim() const pos = ensureAllowedValue( p, ALLOWED_BUTTON_POSITIONS, DEFAULT_BUTTON_POSITION ) const text = String( (c == null ? void 0 : c.text) || t('insert_image_button_default') ).trim() return { selector, position: pos, text } }) .filter(Boolean) if (list.length > 0) { s.buttons = list changed = true } } if (s.enabled === void 0 && typeof preset.enabled === 'boolean') { s.enabled = preset.enabled changed = true } if ( s.pasteEnabled === void 0 && typeof preset.pasteEnabled === 'boolean' ) { s.pasteEnabled = preset.pasteEnabled changed = true } if ( s.dragAndDropEnabled === void 0 && typeof preset.dragAndDropEnabled === 'boolean' ) { s.dragAndDropEnabled = preset.dragAndDropEnabled changed = true } if (changed) siteMap[key] = s } if (changed) GM_setValue(SITE_SETTINGS_MAP_KEY, siteMap) } catch (e) {} } applyPresetConfig() var SITE_KEY = normalizeHost(location.hostname || '') var getSiteSettingsMap = () => GM_getValue(SITE_SETTINGS_MAP_KEY, {}) var setSiteSettingsMap = (map) => { GM_setValue(SITE_SETTINGS_MAP_KEY, map) } var getCurrentSiteSettings = () => { const map = getSiteSettingsMap() return map[SITE_KEY] || {} } var updateCurrentSiteSettings = (updater) => { const map = getSiteSettingsMap() const key = SITE_KEY const current = map[key] || {} const partial = typeof updater === 'function' ? updater(__spreadValues({}, current)) : __spreadValues({}, updater) const next = __spreadValues(__spreadValues({}, current), partial) if (Object.hasOwn(next, 'format')) { const resolvedFormat = ensureAllowedFormat(next.format) if (resolvedFormat) next.format = resolvedFormat else delete next.format } if (Object.hasOwn(next, 'host')) { const resolvedHost = ensureAllowedValue(next.host, ALLOWED_HOSTS) if (resolvedHost) next.host = resolvedHost else delete next.host } if (Object.hasOwn(next, 'proxy')) { const resolved = ensureAllowedValue(next.proxy, ALLOWED_PROXIES) if (resolved) next.proxy = resolved else delete next.proxy } if (Object.hasOwn(next, 'buttons')) { const list = next.buttons if (!list || !Array.isArray(list) || list.length === 0) { delete next.buttons } } if (!next || Object.keys(next).length === 0) { if (map[key]) delete map[key] } else { map[key] = next } setSiteSettingsMap(map) } var getFormat = () => { const s = getCurrentSiteSettings() return s.format || DEFAULT_FORMAT } var setFormat = (format) => { updateCurrentSiteSettings({ format }) } var getHost = () => { const s = getCurrentSiteSettings() return s.host || DEFAULT_HOST } var setHost = (host) => { updateCurrentSiteSettings({ host }) } var getProxy = () => { const s = getCurrentSiteSettings() return s.proxy || DEFAULT_PROXY } var setProxy = (proxy) => { updateCurrentSiteSettings({ proxy }) } var getEnabled = () => { const s = getCurrentSiteSettings() return s.enabled === true } var setEnabled = (val) => { updateCurrentSiteSettings({ enabled: Boolean(val) }) } var getPasteEnabled = () => { const s = getCurrentSiteSettings() return s.pasteEnabled === true } var setPasteEnabled = (val) => { updateCurrentSiteSettings({ pasteEnabled: Boolean(val) }) } var getDragAndDropEnabled = () => { const s = getCurrentSiteSettings() return s.dragAndDropEnabled === true } var setDragAndDropEnabled = (val) => { updateCurrentSiteSettings({ dragAndDropEnabled: Boolean(val) }) } var getSiteBtnSettingsList = () => { const s = getCurrentSiteSettings() const val = s.buttons || [] return Array.isArray(val) ? val : (val == null ? void 0 : val.selector) ? [val] : [] } var setSiteBtnSettingsList = (list) => { updateCurrentSiteSettings({ buttons: list }) } var addSiteBtnSetting = (cfg) => { const selector = ((cfg == null ? void 0 : cfg.selector) || '').trim() if (!selector) return const p = ((cfg == null ? void 0 : cfg.position) || '').trim() const pos = ensureAllowedValue( p, ALLOWED_BUTTON_POSITIONS, DEFAULT_BUTTON_POSITION ) const text = ( (cfg == null ? void 0 : cfg.text) || t('insert_image_button_default') ).trim() const list = getSiteBtnSettingsList() list.push({ selector, position: pos, text }) setSiteBtnSettingsList(list) } var removeSiteBtnSetting = (index) => { const list = getSiteBtnSettingsList() if (index >= 0 && index < list.length) { list.splice(index, 1) setSiteBtnSettingsList(list) } } var updateSiteBtnSetting = (index, cfg) => { const list = getSiteBtnSettingsList() if (!list || index < 0 || index >= list.length) return const selector = ((cfg == null ? void 0 : cfg.selector) || '').trim() if (!selector) return const p = ((cfg == null ? void 0 : cfg.position) || '').trim() const pos = ensureAllowedValue( p, ALLOWED_BUTTON_POSITIONS, DEFAULT_BUTTON_POSITION ) const text = ( (cfg == null ? void 0 : cfg.text) || t('insert_image_button_default') ).trim() list[index] = { selector, position: pos, text } setSiteBtnSettingsList(list) } var MAX_HISTORY = 50 var createEl = (tag, attrs = {}, children = []) => { const el = document.createElement(tag) for (const [k, v] of Object.entries(attrs)) { if (k === 'text') el.textContent = v else if (k === 'class') el.className = v else el.setAttribute(k, v) } for (const c of children) el.append(c) return el } var buildPositionOptions = (selectEl, selectedValue) => { if (!selectEl) return selectEl.textContent = '' const selected = selectedValue ? ensureAllowedValue( selectedValue, ALLOWED_BUTTON_POSITIONS, DEFAULT_BUTTON_POSITION ) : DEFAULT_BUTTON_POSITION for (const value of ALLOWED_BUTTON_POSITIONS) { const opt = createEl('option', { value, text: t('pos_' + value) }) if (value === selected) opt.selected = true selectEl.append(opt) } } var buildFormatOptions = (selectEl, selectedValue) => { if (!selectEl) return selectEl.textContent = '' const selected = selectedValue ? ensureAllowedFormat(selectedValue) : DEFAULT_FORMAT const builtins = ALLOWED_FORMATS const customs = getCustomFormats() for (const val of builtins) { const opt = createEl('option', { value: val, text: t('format_' + val) }) if (val === selected) opt.selected = true selectEl.append(opt) } for (const cf of customs) { const opt = createEl('option', { value: cf.name, text: cf.name }) if (cf.name === selected) opt.selected = true selectEl.append(opt) } } var buildHostOptions = (selectEl, selectedValue) => { if (!selectEl) return selectEl.textContent = '' const selected = selectedValue ? ensureAllowedValue(selectedValue, ALLOWED_HOSTS, DEFAULT_HOST) : DEFAULT_HOST for (const val of ALLOWED_HOSTS) { const opt = createEl('option', { value: val, text: t('host_' + val) }) if (val === selected) opt.selected = true selectEl.append(opt) } } var buildProxyOptions = (selectEl, selectedValue) => { if (!selectEl) return selectEl.textContent = '' const selected = selectedValue ? ensureAllowedValue(selectedValue, ALLOWED_PROXIES, DEFAULT_PROXY) : DEFAULT_PROXY const proxyLabelKey = (val) => val === 'wsrv.nl' ? 'proxy_wsrv_nl' : 'proxy_none' for (const val of ALLOWED_PROXIES) { const opt = createEl('option', { value: val, text: t(proxyLabelKey(val)), }) if (val === selected) opt.selected = true selectEl.append(opt) } } var css = '\n #uiu-panel { position: fixed; right: 16px; bottom: 16px; z-index: 2147483647; width: 440px; max-height: calc(100vh - 32px); overflow: auto; background: #111827cc; color: #fff; backdrop-filter: blur(6px); border-radius: 10px; box-shadow: 0 8px 24px rgba(0,0,0,.25); font-family: system-ui, -apple-system, Segoe UI, Roboto; font-size: 13px; line-height: 1.5; }\n #uiu-panel header { display:flex; align-items:center; justify-content:space-between; padding: 10px 12px; font-weight: 600; font-size: 16px; background-color: unset; box-shadow: unset; transition: unset; }\n #uiu-panel header .uiu-actions { display:flex; gap:8px; }\n #uiu-panel header .uiu-actions button { font-size: 12px; }\n /* Active styles for toggles when sections are open */\n #uiu-panel header.uiu-show-history .uiu-actions .uiu-toggle-history { background:#2563eb; border-color:#1d4ed8; box-shadow: 0 0 0 1px #1d4ed8 inset; color:#fff; }\n #uiu-panel header.uiu-show-settings .uiu-actions .uiu-toggle-settings { background:#2563eb; border-color:#1d4ed8; box-shadow: 0 0 0 1px #1d4ed8 inset; color:#fff; }\n #uiu-panel .uiu-body { padding: 8px 12px; }\n #uiu-panel .uiu-controls { display:flex; align-items:center; gap:8px; flex-wrap: wrap; }\n #uiu-panel select, #uiu-panel button { font-size: 12px; padding: 6px 10px; border-radius: 6px; border: 1px solid #334155; background:#1f2937; color:#fff; }\n #uiu-panel button.uiu-primary { background:#2563eb; border-color:#1d4ed8; }\n #uiu-panel .uiu-list { margin-top:8px; max-height: 140px; overflow-y:auto; overflow-x:hidden; font-size: 12px; }\n #uiu-panel .uiu-list .uiu-item { padding:6px 0; border-bottom: 1px dashed #334155; white-space: normal; word-break: break-word; overflow-wrap: anywhere; }\n #uiu-panel .uiu-history { display:none; margin-top:12px; border-top: 2px solid #475569; padding-top: 8px; }\n #uiu-panel header.uiu-show-history + .uiu-body .uiu-history { display:block; }\n #uiu-panel .uiu-history .uiu-controls > span { font-size: 16px; font-weight: 600;}\n #uiu-panel .uiu-history .uiu-list { max-height: 240px; }\n #uiu-panel .uiu-history .uiu-row { display:flex; align-items:center; justify-content:space-between; gap:8px; padding:6px 0; border-bottom: 1px dashed #334155; }\n #uiu-panel .uiu-history .uiu-row .uiu-ops { display:flex; gap:6px; }\n #uiu-panel .uiu-history .uiu-row .uiu-name { display:block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n #uiu-panel .uiu-hint { font-size: 11px; opacity:.85; margin-top:6px; }\n /* Settings container toggling */\n #uiu-panel .uiu-settings-container { display:none; margin-top:12px; border-top: 2px solid #475569; padding-top: 8px; }\n #uiu-panel header.uiu-show-settings + .uiu-body .uiu-settings-container { display:block; }\n #uiu-panel .uiu-settings .uiu-controls > span { font-size: 16px; font-weight: 600;}\n #uiu-panel .uiu-settings .uiu-controls > .uiu-subtitle { font-size: 13px; font-weight: 600; }\n #uiu-panel .uiu-settings .uiu-settings-list { margin-top:6px; max-height: 240px; overflow-y:auto; overflow-x:hidden; }\n #uiu-panel .uiu-settings .uiu-settings-row { display:flex; align-items:center; justify-content:space-between; gap:8px; padding:6px 0; border-bottom: 1px dashed #334155; font-size: 12px; flex-wrap: nowrap; }\n #uiu-panel .uiu-settings .uiu-settings-row .uiu-settings-item { flex:1; display:flex; align-items:center; gap:6px; min-width:0; }\n #uiu-panel .uiu-settings .uiu-settings-row .uiu-settings-item input[type="text"] { flex:1; min-width:0; }\n #uiu-panel .uiu-settings .uiu-settings-row .uiu-settings-item select { flex:0 0 auto; }\n #uiu-panel .uiu-settings .uiu-settings-row .uiu-ops { display:flex; gap:6px; flex-shrink:0; white-space:nowrap; }\n #uiu-drop { position: fixed; inset: 0; background: rgba(37,99,235,.12); border: 2px dashed #2563eb; display:none; align-items:center; justify-content:center; z-index: 999998; color:#2563eb; font-size: 18px; font-weight: 600; }\n #uiu-drop.show { display:flex; }\n .uiu-insert-btn { cursor:pointer; }\n .uiu-insert-btn.uiu-default { font-size: 12px; padding: 4px 8px; border-radius: 6px; border: 1px solid #334155; background:#1f2937; color:#fff; cursor:pointer; }\n /* Hover effects for all buttons */\n #uiu-panel button { transition: background-color .12s ease, box-shadow .12s ease, transform .06s ease, opacity .12s ease, border-color .12s ease; }\n #uiu-panel button:hover { background:#334155; border-color:#475569; box-shadow: 0 0 0 1px #475569 inset; transform: translateY(-0.5px); }\n #uiu-panel button.uiu-primary:hover { background:#1d4ed8; border-color:#1e40af; }\n #uiu-panel button:active { transform: translateY(0); }\n /* Disabled style for proxy selector */\n #uiu-panel select:disabled { opacity:.55; cursor:not-allowed; filter: grayscale(80%); background:#111827; color:#9ca3af; border-color:#475569; }\n /* Custom Formats layout */\n #uiu-panel .uiu-formats { margin-top:12px; border-top: 2px solid #475569; padding-top: 8px; }\n #uiu-panel .uiu-formats .uiu-controls > span { font-size: 16px; font-weight: 600; }\n #uiu-panel .uiu-formats .uiu-controls > .uiu-subtitle { font-size: 13px; font-weight: 600; }\n #uiu-panel .uiu-formats .uiu-formats-list { margin-top:6px; max-height: 200px; overflow-y:auto; overflow-x:hidden; }\n #uiu-panel .uiu-formats .uiu-formats-row { display:grid; grid-template-columns: 1fr 2fr 180px; align-items:center; gap:8px; padding:6px 0; border-bottom: 1px dashed #334155; }\n #uiu-panel .uiu-formats .uiu-formats-row .uiu-ops { display:flex; gap:6px; justify-content:flex-end; }\n #uiu-panel .uiu-formats .uiu-formats-row:not(.uiu-editing) .uiu-fmt-name, #uiu-panel .uiu-formats .uiu-formats-row:not(.uiu-editing) .uiu-fmt-template { display:block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n #uiu-panel .uiu-formats .uiu-formats-row.uiu-editing .uiu-fmt-name, #uiu-panel .uiu-formats .uiu-formats-row.uiu-editing .uiu-fmt-template { overflow: visible; text-overflow: clip; white-space: normal; }\n #uiu-panel .uiu-formats .uiu-form-add { display:grid; grid-template-columns: 1fr 2fr 180px; align-items:center; gap:8px; }\n #uiu-panel .uiu-formats .uiu-formats-row input[type="text"] { width:100%; }\n #uiu-panel .uiu-formats .uiu-form-add input[type="text"] { width:100%; }\n #uiu-panel .uiu-formats .uiu-form-add button { justify-self: end; }\n #uiu-panel .uiu-formats .uiu-formats-header { font-weight: 600; color:#e5e7eb; }\n #uiu-panel .uiu-formats .uiu-form-add .uiu-fmt-name, #uiu-panel .uiu-formats .uiu-form-add .uiu-fmt-template { display:block; min-width:0; }\n #uiu-panel .uiu-formats .uiu-format-example-row { padding-top:4px; border-bottom: none; }\n #uiu-panel .uiu-formats .uiu-format-example-row .uiu-fmt-template { font-size:12px; color:#cbd5e1; white-space: normal; overflow: visible; text-overflow: clip; }\n ' GM_addStyle(css) function loadHistory() { return GM_getValue(HISTORY_KEY, []) } function saveHistory(list) { GM_setValue(HISTORY_KEY, list.slice(0, MAX_HISTORY)) } function addToHistory(entry) { const list = loadHistory() list.unshift(entry) saveHistory(list) } function basename(name) { const n = (name || '').trim() if (!n) return t('default_image_name') return n.replace(/\.[^.]+$/, '') } function formatText(link, name, fmt) { const alt = basename(name) try { const custom = getCustomFormats().find((cf) => cf.name === fmt) if (custom) { return tpl(custom.template, { link, name: alt }) } } catch (e) {} switch (fmt) { case 'html': { return '').concat(alt, '') } case 'bbcode': { return '[img]'.concat(link, '[/img]') } case 'link': { return link } default: { return '!['.concat(alt, '](').concat(link, ')') } } } function isImgurUrl(url) { try { const u = new URL(url) const h = u.hostname.toLowerCase() return h.includes('imgur.com') } catch (e) { return false } } function applyProxy(url, providerKey) { try { const px = getProxy() if (px === 'none') return url const provider = providerKey || getHost() if (provider === 'imgur' || isImgurUrl(url)) return url if (px === 'wsrv.nl') { return 'https://wsrv.nl/?url='.concat(encodeURIComponent(url)) } return url } catch (e) { return url } } async function gmRequest(opts) { const req = typeof GM !== 'undefined' && (GM == null ? void 0 : GM.xmlHttpRequest) ? GM.xmlHttpRequest : typeof GM_xmlhttpRequest === 'undefined' ? null : GM_xmlhttpRequest if (!req) throw new Error('GM.xmlHttpRequest unavailable') return new Promise((resolve, reject) => { try { req({ method: opts.method || 'GET', url: opts.url, headers: opts.headers, data: opts.data, responseType: opts.responseType || 'text', onload(res) { var _a try { if ((opts.responseType || 'text') === 'json') { resolve( (_a = res.response) != null ? _a : JSON.parse(res.responseText || '{}') ) } else { resolve(res.responseText) } } catch (error) { reject(error) } }, onerror() { reject(new Error(t('error_network'))) }, ontimeout() { reject(new Error(t('error_network'))) }, }) } catch (error) { reject(error) } }) } async function getMjjAuthToken() { const html = await gmRequest({ url: 'https://mjj.today/upload' }) const m = /PF\.obj\.config\.auth_token\s*=\s*["']([A-Za-z\d]+)["']/.exec( String(html || '') ) if (!m || !m[1]) throw new Error(t('error_network')) return m[1] } async function uploadToMjj(file) { var _a if (Math.floor(file.size / 1e3) > 1e4) { throw new Error('10mb limit') } const token = await getMjjAuthToken() const formData = new FormData() formData.append('source', file) formData.append('type', 'file') formData.append('action', 'upload') formData.append('timestamp', String(Date.now())) formData.append('auth_token', token) formData.append('expiration', '') formData.append('nsfw', '0') const data = await gmRequest({ method: 'POST', url: 'https://mjj.today/json', data: formData, responseType: 'json', }) if ( (data == null ? void 0 : data.status_code) === 200 && ((_a = data == null ? void 0 : data.image) == null ? void 0 : _a.url) ) { const url = String(data.image.url) return url.includes('i.mji.rip') ? url.replace('i.mji.rip', 'i.mij.rip') : url } throw new Error(t('error_upload_failed')) } async function uploadToAppinn(file) { var _a if (Math.floor(file.size / 1e3) > 2e4) { throw new Error('20mb limit') } const filename = (file == null ? void 0 : file.name) || 'file_'.concat(Date.now()) const formData = new FormData() formData.append('filename', filename) formData.append('file', file) const qs = new URLSearchParams() for (const [k, v] of Object.entries(APPINN_UPLOAD_PARAMS)) qs.append(k, String(v)) const uploadUrl = '' .concat(APPINN_UPLOAD_ENDPOINT, '?') .concat(qs.toString()) const data = await gmRequest({ method: 'POST', url: uploadUrl, data: formData, responseType: 'json', }) if (Array.isArray(data) && ((_a = data[0]) == null ? void 0 : _a.src)) { const src = String(data[0].src) const abs = /^https?:\/\//i.test(src) ? src : new URL(src, APPINN_UPLOAD_ENDPOINT).href return abs } throw new Error(t('error_upload_failed')) } async function uploadToImgur(file) { var _a const ids = [...IMGUR_CLIENT_IDS] for (let i = ids.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)) ;[ids[i], ids[j]] = [ids[j], ids[i]] } let lastError for (const id of ids) { const formData = new FormData() formData.append('image', file) try { const data = await gmRequest({ method: 'POST', url: 'https://api.imgur.com/3/upload', headers: { Authorization: 'Client-ID '.concat(id) }, data: formData, responseType: 'json', }) if ( (data == null ? void 0 : data.success) && ((_a = data == null ? void 0 : data.data) == null ? void 0 : _a.link) ) { return data.data.link } lastError = new Error(t('error_upload_failed')) } catch (error) { lastError = error } } throw lastError || new Error(t('error_upload_failed')) } async function uploadToTikolu(file) { if (Math.floor(file.size / 1e3) > 8e3) { throw new Error('8mb limit') } const formData = new FormData() formData.append('upload', true) formData.append('file', file) const data = await gmRequest({ method: 'POST', url: 'https://tikolu.net/i/', data: formData, responseType: 'json', }) if ( (data == null ? void 0 : data.status) === 'uploaded' && (data == null ? void 0 : data.id) ) { return 'https://tikolu.net/i/'.concat(data.id) } throw new Error(t('error_upload_failed')) } async function uploadImage(file) { const host = getHost() if (host === 'tikolu') return uploadToTikolu(file) if (host === 'mjj') return uploadToMjj(file) if (host === 'appinn') return uploadToAppinn(file) return uploadToImgur(file) } var lastEditableEl = null function getDeepActiveElement() { let el = document.activeElement try { while (el && el.shadowRoot && el.shadowRoot.activeElement) { el = el.shadowRoot.activeElement } while ( el && el.tagName === 'IFRAME' && el.contentDocument && el.contentDocument.activeElement ) { el = el.contentDocument.activeElement } } catch (e) {} return el } function isInsideUIPanel(node) { try { const host = document.querySelector('#uiu-panel') if (!host || !node) return false if (host === node) return true if (host.contains(node)) return true const root = host.shadowRoot return root ? root.contains(node) : false } catch (e) {} return false } function isTextInput(el) { if (!(el instanceof HTMLInputElement)) return false const type = (el.type || '').toLowerCase() return ( type === 'text' || type === 'search' || type === 'url' || type === 'email' || type === 'tel' ) } function isEditable(el) { return ( el instanceof HTMLTextAreaElement || isTextInput(el) || (el instanceof HTMLElement && el.isContentEditable) ) } document.addEventListener( 'focusin', (e) => { const deepTarget = getDeepActiveElement() || (typeof e.composedPath === 'function' ? e.composedPath()[0] : e.target) if ( deepTarget && isEditable(deepTarget) && !isInsideUIPanel(deepTarget) ) { lastEditableEl = deepTarget } }, true ) function insertIntoFocused(text) { var _a, _b let el = getDeepActiveElement() if (!isEditable(el) || isInsideUIPanel(el)) { el = lastEditableEl try { if (el && typeof el.focus === 'function') el.focus() } catch (e) {} } if (!isEditable(el) || isInsideUIPanel(el)) return false try { if (el instanceof HTMLTextAreaElement || isTextInput(el)) { const start = (_a = el.selectionStart) != null ? _a : el.value.length const end = (_b = el.selectionEnd) != null ? _b : el.value.length const v = el.value el.value = v.slice(0, start) + text + v.slice(end) el.dispatchEvent(new Event('input', { bubbles: true })) return true } if (el instanceof HTMLElement && el.isContentEditable) { try { const sel = globalThis.getSelection() if (sel) { const range = document.createRange() range.selectNodeContents(el) range.collapse(false) sel.removeAllRanges() sel.addRange(range) } } catch (e) {} document.execCommand('insertText', false, text) return true } } catch (e) {} return false } function copyAndInsert(text) { try { GM_setClipboard(text) } catch (e) {} insertIntoFocused('\n'.concat(text, '\n')) } function getActiveEditableTarget() { let el = getDeepActiveElement() if (!isEditable(el) || isInsideUIPanel(el)) el = lastEditableEl return isEditable(el) && !isInsideUIPanel(el) ? el : null } function createUploadPlaceholder(name) { const safe = String(name || t('default_image_name')) return '' ) } function replacePlaceholder(el, placeholder, replacement) { if (!el || !placeholder) return false try { if (el instanceof HTMLTextAreaElement || isTextInput(el)) { const v = el.value const idx = v.indexOf(placeholder) if (idx !== -1) { el.value = v.slice(0, idx) + replacement + v.slice(idx + placeholder.length) el.dispatchEvent(new Event('input', { bubbles: true })) return true } return false } if (el instanceof HTMLElement && el.isContentEditable) { const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT) let node while ((node = walker.nextNode())) { const pos = node.data.indexOf(placeholder) if (pos !== -1) { node.replaceData(pos, placeholder.length, replacement) return true } } return false } } catch (e) {} return false } function createPanel() { if (!isTopFrame()) { return } const panel = createEl('div', { id: 'uiu-panel' }) const root = panel.attachShadow({ mode: 'open' }) try { const styleEl = document.createElement('style') styleEl.textContent = css.replaceAll(/#uiu-panel\b/g, ':host') root.append(styleEl) } catch (e) {} const header = createEl('header') header.append(createEl('span', { text: t('header_title') })) const actions = createEl('div', { class: 'uiu-actions' }) const toggleHistoryBtn = createEl('button', { text: t('btn_history'), class: 'uiu-toggle-history', }) toggleHistoryBtn.addEventListener('click', () => { header.classList.toggle('uiu-show-history') renderHistory() try { toggleHistoryBtn.setAttribute( 'aria-pressed', header.classList.contains('uiu-show-history') ? 'true' : 'false' ) } catch (e) {} }) const settingsBtn = createEl('button', { text: t('btn_settings'), class: 'uiu-toggle-settings', }) settingsBtn.addEventListener('click', () => { header.classList.toggle('uiu-show-settings') try { refreshSettingsUI() } catch (e) {} try { settingsBtn.setAttribute( 'aria-pressed', header.classList.contains('uiu-show-settings') ? 'true' : 'false' ) } catch (e) {} }) const closeBtn = createEl('button', { text: t('btn_close') }) closeBtn.addEventListener('click', () => { panel.style.display = 'none' }) actions.append(toggleHistoryBtn) actions.append(settingsBtn) actions.append(closeBtn) header.append(actions) const body = createEl('div', { class: 'uiu-body' }) const controls = createEl('div', { class: 'uiu-controls' }) const format = getFormat() const formatSel = createEl('select') buildFormatOptions(formatSel, format) formatSel.addEventListener('change', () => { setFormat(formatSel.value) }) const host = getHost() const hostSel = createEl('select') buildHostOptions(hostSel, host) hostSel.addEventListener('change', () => { setHost(hostSel.value) updateProxyState() }) const proxy = getProxy() const proxySel = createEl('select') buildProxyOptions(proxySel, proxy) function updateProxyState() { const currentHost = hostSel.value if (currentHost === 'imgur') { proxySel.value = 'none' proxySel.disabled = true setProxy('none') try { renderHistory() } catch (e) {} } else { proxySel.disabled = false } } updateProxyState() proxySel.addEventListener('change', () => { setProxy(proxySel.value) try { renderHistory() } catch (e) {} }) function openFilePicker() { const input = createEl('input', { type: 'file', accept: 'image/*', multiple: 'true', style: 'display:none', }) input.addEventListener('change', () => { var _a if ((_a = input.files) == null ? void 0 : _a.length) handleFiles(Array.from(input.files)) }) input.click() } const selectBtn = createEl('button', { class: 'uiu-primary', text: t('btn_select_images'), }) selectBtn.addEventListener('click', openFilePicker) const progressEl = createEl('span', { class: 'uiu-progress', text: t('progress_initial'), }) controls.append(formatSel) controls.append(hostSel) controls.append(proxySel) controls.append(selectBtn) controls.append(progressEl) body.append(controls) const list = createEl('div', { class: 'uiu-list' }) body.append(list) const hint = createEl('div', { class: 'uiu-hint', text: t('hint_text'), }) body.append(hint) const history = createEl('div', { class: 'uiu-history' }) body.append(history) const settingsContainer = createEl('div', { class: 'uiu-settings-container', }) body.append(settingsContainer) const settings = createEl('div', { class: 'uiu-settings' }) const settingsHeader = createEl('div', { class: 'uiu-controls', style: 'margin-bottom:8px;', }) settingsHeader.append( createEl('span', { text: t('settings_section_title') }) ) settings.append(settingsHeader) const togglesRow = createEl('div', { class: 'uiu-controls' }) const pasteLabel = createEl('label') const pasteChk = createEl('input', { type: 'checkbox' }) try { pasteChk.checked = getPasteEnabled() } catch (e) {} pasteChk.addEventListener('change', () => { setPasteEnabled(Boolean(pasteChk.checked)) if (pasteChk.checked) enablePaste() else disablePaste() }) pasteLabel.append(pasteChk) pasteLabel.append( createEl('span', { text: t('toggle_paste_enabled'), style: 'margin-left:6px;', }) ) const dragLabel = createEl('label', { style: 'margin-left:12px;' }) const dragChk = createEl('input', { type: 'checkbox' }) try { dragChk.checked = getDragAndDropEnabled() } catch (e) {} dragChk.addEventListener('change', () => { setDragAndDropEnabled(Boolean(dragChk.checked)) if (dragChk.checked) enableDrag() else disableDrag() }) dragLabel.append(dragChk) dragLabel.append( createEl('span', { text: t('toggle_drag_enabled'), style: 'margin-left:6px;', }) ) togglesRow.append(pasteLabel) togglesRow.append(dragLabel) settings.append(togglesRow) const btnsSubHeader = createEl('div', { class: 'uiu-controls', style: 'margin-top:12px;border-top:2px solid #475569;padding-top:8px;', }) btnsSubHeader.append( createEl('span', { class: 'uiu-subtitle', text: t('settings_site_buttons'), }) ) settings.append(btnsSubHeader) const settingsForm = createEl('div', { class: 'uiu-controls' }) const selInput = createEl('input', { type: 'text', placeholder: t('placeholder_css_selector'), }) const posSel = createEl('select') buildPositionOptions(posSel) const textInput = createEl('input', { type: 'text', placeholder: t('placeholder_button_content'), }) textInput.value = t('insert_image_button_default') const saveBtn = createEl('button', { text: t('btn_save_and_insert') }) saveBtn.addEventListener('click', () => { addSiteBtnSetting({ selector: selInput.value, position: posSel.value, text: textInput.value, }) selInput.value = '' buildPositionOptions(posSel) textInput.value = t('insert_image_button_default') renderSettingsList() for (const el of document.querySelectorAll('.uiu-insert-btn')) el.remove() applySiteButtons() try { restartSiteButtonObserver() } catch (e) {} }) const removeBtn = createEl('button', { text: t('btn_remove_button_temp') }) removeBtn.addEventListener('click', () => { for (const el of document.querySelectorAll('.uiu-insert-btn')) el.remove() try { if (siteBtnObserver) siteBtnObserver.disconnect() } catch (e) {} }) const clearBtn = createEl('button', { text: t('btn_clear_settings') }) clearBtn.addEventListener('click', () => { setSiteBtnSettingsList([]) renderSettingsList() for (const el of document.querySelectorAll('.uiu-insert-btn')) el.remove() try { if (siteBtnObserver) siteBtnObserver.disconnect() } catch (e) {} }) const settingsList = createEl('div', { class: 'uiu-settings-list' }) settings.append(settingsList) settingsForm.append(selInput) settingsForm.append(posSel) settingsForm.append(textInput) settingsForm.append(saveBtn) settingsForm.append(removeBtn) settingsForm.append(clearBtn) settings.append(settingsForm) settingsContainer.append(settings) const formats = createEl('div', { class: 'uiu-formats' }) const formatsHeader = createEl('div', { class: 'uiu-controls' }) formatsHeader.append( createEl('span', { class: 'uiu-subtitle', text: t('formats_section_title'), }) ) formats.append(formatsHeader) const formatsColsHeader = createEl('div', { class: 'uiu-formats-row uiu-formats-header', }) formatsColsHeader.append( createEl('span', { class: 'uiu-fmt-name', text: t('formats_col_name') }) ) formatsColsHeader.append( createEl('span', { class: 'uiu-fmt-template', text: t('formats_col_template'), }) ) formatsColsHeader.append( createEl('span', { class: 'uiu-ops', text: t('formats_col_ops') }) ) formats.append(formatsColsHeader) const formatsForm = createEl('div', { class: 'uiu-controls uiu-form-add' }) const fnameInput = createEl('input', { type: 'text', placeholder: t('placeholder_format_name'), }) const ftemplateInput = createEl('input', { type: 'text', placeholder: t('placeholder_format_template'), }) const addFmtBtn = createEl('button', { text: t('btn_add_format') }) addFmtBtn.addEventListener('click', () => { const name = (fnameInput.value || '').trim() const tplStr = String(ftemplateInput.value || '') if (!name || !tplStr) return upsertCustomFormat(name, tplStr) fnameInput.value = '' ftemplateInput.value = '' renderFormatsList() try { buildFormatOptions(formatSel, getFormat()) } catch (e) {} }) const addNameCol = createEl('span', { class: 'uiu-fmt-name' }) addNameCol.append(fnameInput) const addTplCol = createEl('span', { class: 'uiu-fmt-template' }) addTplCol.append(ftemplateInput) formatsForm.append(addNameCol) formatsForm.append(addTplCol) formatsForm.append(addFmtBtn) const formatsList = createEl('div', { class: 'uiu-formats-list' }) formats.append(formatsList) formats.append(formatsForm) const formatsExampleRow = createEl('div', { class: 'uiu-formats-row uiu-format-example-row', }) formatsExampleRow.append( createEl('span', { class: 'uiu-fmt-name', text: '' }) ) formatsExampleRow.append( createEl('span', { class: 'uiu-fmt-template', text: t('example_format_template'), }) ) formatsExampleRow.append(createEl('span', { class: 'uiu-ops', text: '' })) formats.append(formatsExampleRow) settingsContainer.append(formats) function renderFormatsList() { formatsList.textContent = '' const list2 = getCustomFormats() for (const cf of list2) { const row = createEl('div', { class: 'uiu-formats-row' }) const nameEl = createEl('span', { class: 'uiu-fmt-name', text: cf.name, }) const tplEl = createEl('span', { class: 'uiu-fmt-template', text: cf.template, }) const editBtn = createEl('button', { text: t('btn_edit') }) editBtn.addEventListener('click', () => { row.textContent = '' row.classList.add('uiu-editing') const colName = createEl('span', { class: 'uiu-settings-item uiu-fmt-name', }) const eName = createEl('input', { type: 'text' }) eName.value = cf.name const colTpl = createEl('span', { class: 'uiu-settings-item uiu-fmt-template', }) const eTpl = createEl('input', { type: 'text' }) eTpl.value = cf.template colName.append(eName) colTpl.append(eTpl) const ops2 = createEl('span', { class: 'uiu-ops' }) const updateBtn = createEl('button', { text: t('btn_update') }) updateBtn.addEventListener('click', () => { const newName = (eName.value || '').trim() const newTpl = String(eTpl.value || '') if (!newName || !newTpl) return if (newName !== cf.name) removeCustomFormat(cf.name) upsertCustomFormat(newName, newTpl) try { if (getFormat() === cf.name) setFormat(newName) } catch (e) {} renderFormatsList() try { buildFormatOptions(formatSel, getFormat()) } catch (e) {} }) const cancelBtn = createEl('button', { text: t('btn_cancel') }) cancelBtn.addEventListener('click', () => { renderFormatsList() }) ops2.append(updateBtn) ops2.append(cancelBtn) row.append(colName) row.append(colTpl) row.append(ops2) }) const delBtn = createEl('button', { text: t('btn_delete') }) delBtn.addEventListener('click', () => { removeCustomFormat(cf.name) try { if (getFormat() === cf.name) setFormat(DEFAULT_FORMAT) } catch (e) {} renderFormatsList() try { buildFormatOptions(formatSel, getFormat()) } catch (e) {} }) const ops = createEl('span', { class: 'uiu-ops' }) ops.append(editBtn) ops.append(delBtn) row.append(nameEl) row.append(tplEl) row.append(ops) formatsList.append(row) } } function renderSettingsList() { settingsList.textContent = '' const listData = getSiteBtnSettingsList() for (const [idx, cfg] of listData.entries()) { const row = createEl('div', { class: 'uiu-settings-row' }) const info = createEl('span', { class: 'uiu-settings-item', text: '' .concat(cfg.selector, ' [') .concat(cfg.position || DEFAULT_BUTTON_POSITION, '] - ') .concat(cfg.text || t('insert_image_button_default')), }) const editBtn = createEl('button', { text: t('btn_edit') }) editBtn.addEventListener('click', () => { row.textContent = '' row.classList.add('uiu-editing') const fields = createEl('span', { class: 'uiu-settings-item' }) const eSel = createEl('input', { type: 'text' }) eSel.value = cfg.selector || '' const ePos = createEl('select') buildPositionOptions(ePos, cfg.position) const eText = createEl('input', { type: 'text' }) eText.value = cfg.text || t('insert_image_button_default') fields.append(eSel) fields.append(ePos) fields.append(eText) const ops2 = createEl('span', { class: 'uiu-ops' }) const updateBtn = createEl('button', { text: t('btn_update') }) updateBtn.addEventListener('click', () => { updateSiteBtnSetting(idx, { selector: eSel.value, position: ePos.value, text: eText.value, }) renderSettingsList() for (const el of document.querySelectorAll('.uiu-insert-btn')) el.remove() applySiteButtons() try { restartSiteButtonObserver() } catch (e) {} }) const cancelBtn = createEl('button', { text: t('btn_cancel') }) cancelBtn.addEventListener('click', () => { renderSettingsList() }) ops2.append(updateBtn) ops2.append(cancelBtn) row.append(fields) row.append(ops2) }) const delBtn = createEl('button', { text: t('btn_delete') }) delBtn.addEventListener('click', () => { removeSiteBtnSetting(idx) renderSettingsList() for (const el of document.querySelectorAll('.uiu-insert-btn')) el.remove() applySiteButtons() try { restartSiteButtonObserver() } catch (e) {} }) row.append(info) const ops = createEl('span', { class: 'uiu-ops' }) ops.append(editBtn) ops.append(delBtn) row.append(ops) settingsList.append(row) } } function refreshSettingsUI() { selInput.value = '' buildPositionOptions(posSel) textInput.value = t('insert_image_button_default') renderSettingsList() try { fnameInput.value = '' ftemplateInput.value = '' renderFormatsList() } catch (e) {} } root.append(header) root.append(body) panel.style.display = 'none' document.documentElement.append(panel) try { toggleHistoryBtn.setAttribute('aria-pressed', 'false') settingsBtn.setAttribute('aria-pressed', 'false') } catch (e) {} const showPanel = () => { panel.style.display = 'block' document.documentElement.append(panel) } function applySingle(cfg) { var _a, _b if (!(cfg == null ? void 0 : cfg.selector)) return let targets try { targets = document.querySelectorAll(cfg.selector) } catch (e) { return } if (!targets || targets.length === 0) return const posRaw = (cfg.position || '').trim() const pos = posRaw === 'before' ? 'before' : posRaw === 'inside' ? 'inside' : 'after' const content = (cfg.text || t('insert_image_button_default')).trim() for (const target of Array.from(targets)) { const exists = pos === 'inside' ? Boolean(target.querySelector('.uiu-insert-btn')) : pos === 'before' ? Boolean( target.previousElementSibling && ((_a = target.previousElementSibling.classList) == null ? void 0 : _a.contains('uiu-insert-btn')) ) : Boolean( target.nextElementSibling && ((_b = target.nextElementSibling.classList) == null ? void 0 : _b.contains('uiu-insert-btn')) ) if (exists) continue let btn try { const range = document.createRange() const ctx = document.createElement('div') range.selectNodeContents(ctx) const frag = range.createContextualFragment(content) if (frag && frag.childElementCount === 1) { btn = frag.firstElementChild } } catch (e) {} if (btn) { btn.classList.add('uiu-insert-btn') } else { btn = createEl('button', { class: 'uiu-insert-btn uiu-default', text: content, }) } btn.addEventListener('click', (event) => { showPanel() event.preventDefault() try { openFilePicker() } catch (e) {} }) if (pos === 'before') { target.before(btn) } else if (pos === 'inside') { target.append(btn) } else { target.after(btn) } } } function applySiteButtons() { const list2 = getSiteBtnSettingsList() for (const cfg of list2) { try { applySingle(cfg) } catch (e) {} } } applySiteButtons() let siteBtnObserver function restartSiteButtonObserver() { try { if (siteBtnObserver) siteBtnObserver.disconnect() } catch (e) {} const list2 = getSiteBtnSettingsList() if (list2.length === 0) { siteBtnObserver = null return } const checkAndInsertAll = () => { for (const cfg of list2) { try { applySingle(cfg) } catch (e) {} } } checkAndInsertAll() siteBtnObserver = new MutationObserver(() => { checkAndInsertAll() }) siteBtnObserver.observe(document.body || document.documentElement, { childList: true, subtree: true, }) } restartSiteButtonObserver() let drop = null let pasteHandler = null let dragoverHandler = null let dragleaveHandler = null let dropHandler = null function enablePaste() { if (pasteHandler) return pasteHandler = (event) => { var _a const cd = event.clipboardData if (!cd) return const list2 = [] const seen = /* @__PURE__ */ new Set() const addIfNew = (f) => { const sig = '' .concat(f.name, '|') .concat(f.size, '|') .concat(f.type, '|') .concat(f.lastModified || 0) if (!seen.has(sig)) { seen.add(sig) list2.push(f) } } const items = cd.items ? Array.from(cd.items) : [] for (const i of items) { if (i && i.type && i.type.includes('image')) { const f = (_a = i.getAsFile) == null ? void 0 : _a.call(i) if (f) addIfNew(f) } } const files = cd.files ? Array.from(cd.files) : [] for (const f of files) { if (f && f.type && f.type.includes('image')) addIfNew(f) } if (list2.length > 0) { event.preventDefault() event.stopPropagation() handleFiles(list2) } } document.addEventListener('paste', pasteHandler, true) } function disablePaste() { if (!pasteHandler) return document.removeEventListener('paste', pasteHandler, true) pasteHandler = null } function enableDrag() { if (!drop) { drop = createEl('div', { id: 'uiu-drop', text: t('drop_overlay') }) document.body.append(drop) } if (!dragoverHandler) { dragoverHandler = (e) => { var _a, _b const dt = e.dataTransfer const types = (dt == null ? void 0 : dt.types) ? Array.from(dt.types) : [] const hasFileType = types.includes('Files') || ((_b = (_a = dt == null ? void 0 : dt.types) == null ? void 0 : _a.contains) == null ? void 0 : _b.call(_a, 'Files')) const hasFileItem = (dt == null ? void 0 : dt.items) ? Array.from(dt.items).some((it) => it.kind === 'file') : false if (hasFileType || hasFileItem) { if (drop) drop.classList.add('show') e.preventDefault() } else if (drop) drop.classList.remove('show') } document.addEventListener('dragover', dragoverHandler) } if (!dragleaveHandler) { dragleaveHandler = () => { if (drop) drop.classList.remove('show') } document.addEventListener('dragleave', dragleaveHandler) } if (!dropHandler) { dropHandler = (event) => { var _a if (drop) drop.classList.remove('show') event.preventDefault() const files = (_a = event.dataTransfer) == null ? void 0 : _a.files if (files == null ? void 0 : files.length) handleFiles(Array.from(files)) } document.addEventListener('drop', dropHandler) } } function disableDrag() { if (dragoverHandler) { document.removeEventListener('dragover', dragoverHandler) dragoverHandler = null } if (dragleaveHandler) { document.removeEventListener('dragleave', dragleaveHandler) dragleaveHandler = null } if (dropHandler) { document.removeEventListener('drop', dropHandler) dropHandler = null } if (drop) { try { drop.remove() } catch (e) {} drop = null } } const queue = [] let running = 0 let done = 0 let total = 0 const CONCURRENCY = 3 function updateProgress() { progressEl.textContent = tpl(t('progress_done'), { done, total }) } function addLog(text) { list.prepend(createEl('div', { class: 'item', text })) } async function processQueue() { while (running < CONCURRENCY && queue.length > 0) { const item = queue.shift() running++ addLog(''.concat(t('log_uploading')).concat(item.file.name)) try { const link = await uploadImage(item.file) const fmt = getFormat() const out = formatText( applyProxy(link, getHost()), item.file.name, fmt ) if (item.placeholder && item.targetEl) { const ok = replacePlaceholder( item.targetEl, item.placeholder, ''.concat(out) ) if (!ok) copyAndInsert(out) } else { copyAndInsert(out) } addToHistory({ link, name: item.file.name, ts: Date.now(), pageUrl: location.href, provider: getHost(), }) addLog( '' .concat(t('log_success')) .concat(item.file.name, ' \u2192 ') .concat(link) ) } catch (error) { if (item.placeholder && item.targetEl) { const failNote = '' ) try { replacePlaceholder(item.targetEl, item.placeholder, failNote) } catch (e) {} } addLog( '' .concat(t('log_failed')) .concat(item.file.name, '\uFF08') .concat( (error == null ? void 0 : error.message) || error, '\uFF09' ) ) } finally { running-- done++ updateProgress() } } } function handleFiles(files) { const imgs = files.filter((f) => f.type.includes('image')) if (imgs.length === 0) return total += imgs.length updateProgress() const targetEl = getActiveEditableTarget() for (const file of imgs) { let placeholder if (targetEl) { try { targetEl.focus() } catch (e) {} placeholder = createUploadPlaceholder(file.name) insertIntoFocused('\n'.concat(placeholder, '\n')) } queue.push({ file, placeholder, targetEl }) } void processQueue() } const pasteEnabled = getPasteEnabled() if (pasteEnabled) enablePaste() const dragEnabled = getDragAndDropEnabled() if (dragEnabled) enableDrag() function renderHistory() { history.textContent = '' const header2 = createEl('div', { class: 'uiu-controls' }) header2.append( createEl('span', { text: tpl(t('btn_history_count'), { count: loadHistory().length }), }) ) const clearBtn2 = createEl('button', { text: t('btn_clear_history') }) clearBtn2.addEventListener('click', () => { saveHistory([]) renderHistory() }) header2.append(clearBtn2) history.append(header2) const listWrap = createEl('div', { class: 'uiu-list' }) const items = loadHistory() for (const it of items) { const row = createEl('div', { class: 'uiu-row' }) const preview = createEl('img', { src: applyProxy( it.link, it.provider || (isImgurUrl(it.link) ? 'imgur' : 'other') ), style: 'width:48px;height:48px;object-fit:cover;border-radius:4px;border:1px solid #334155;', }) row.append(preview) const info = createEl('div', { style: 'flex:1;min-width:0;display:flex;flex-direction:column;gap:4px;padding:0 8px;', }) info.append( createEl('span', { class: 'uiu-name', text: it.name || it.link, title: it.name || it.link, }) ) try { const providerKey = it.provider || 'imgur' const providerText = t('host_' + providerKey) info.append( createEl('span', { text: providerText, style: 'font-size:11px;color:#cbd5e1;border:1px solid #334155;border-radius:4px;padding:1px 6px;width:fit-content;', }) ) } catch (e) {} if (it.pageUrl) { let host2 = it.pageUrl try { host2 = new URL(it.pageUrl).hostname } catch (e) {} const pageLink = createEl('a', { href: it.pageUrl, text: tpl(t('history_upload_page'), { host: host2 }), target: '_blank', rel: 'noopener noreferrer', style: 'color:#93c5fd;text-decoration:none;font-size:11px;', }) info.append(pageLink) } row.append(info) const ops = createEl('div', { class: 'uiu-ops' }) const copyBtn = createEl('button', { text: t('btn_copy') }) copyBtn.addEventListener('click', () => { const fmt = getFormat() const proxied = applyProxy( it.link, it.provider || (isImgurUrl(it.link) ? 'imgur' : 'other') ) const out = formatText( proxied, it.name || t('default_image_name'), fmt ) copyAndInsert(out) }) const openBtn = createEl('button', { text: t('btn_open') }) openBtn.addEventListener('click', () => { const url = applyProxy( it.link, it.provider || (isImgurUrl(it.link) ? 'imgur' : 'other') ) window.open(url, '_blank') }) ops.append(copyBtn) ops.append(openBtn) row.append(ops) listWrap.append(row) } history.append(listWrap) } void addValueChangeListener( HISTORY_KEY, (name, oldValue, newValue, remote) => { try { renderHistory() } catch (e) {} } ) registerMenu(t('menu_open_panel'), () => { showPanel() try { toggleHistoryBtn.setAttribute( 'aria-pressed', header.classList.contains('uiu-show-history') ? 'true' : 'false' ) settingsBtn.setAttribute( 'aria-pressed', header.classList.contains('uiu-show-settings') ? 'true' : 'false' ) } catch (e) {} }) registerMenu(t('menu_select_images'), () => { showPanel() openFilePicker() }) registerMenu(t('menu_settings'), () => { showPanel() header.classList.add('uiu-show-settings') try { refreshSettingsUI() } catch (e) {} try { settingsBtn.setAttribute('aria-pressed', 'true') toggleHistoryBtn.setAttribute( 'aria-pressed', header.classList.contains('uiu-show-history') ? 'true' : 'false' ) } catch (e) {} }) return { handleFiles } } try { const enabled = getEnabled() if (enabled && !document.querySelector('#uiu-panel')) { const { handleFiles } = createPanel() globalThis.addEventListener('iu:uploadFiles', (e) => { var _a const files = (_a = e.detail) == null ? void 0 : _a.files if (files == null ? void 0 : files.length) handleFiles(files) }) } registerMenu( enabled ? t('menu_disable_site') : t('menu_enable_site'), () => { setEnabled(!enabled) try { location.reload() } catch (e) {} } ) } catch (e) {} })()