Linux do 表情扩展 (Emoji Extension)

为论坛网站添加表情选择器功能 (Add emoji picker functionality to forum websites)

目前為 2025-09-15 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Linux do 表情扩展 (Emoji Extension)
// @namespace    https://github.com/stevessr/bug-v3
// @version      1.0.1
// @description  为论坛网站添加表情选择器功能 (Add emoji picker functionality to forum websites)
// @author       stevessr
// @match        https://linux.do/*
// @match        https://meta.discourse.org/*
// @match        https://*.discourse.org/*
// @match        http://localhost:5173/*
// @grant        none
// @license      MIT
// @homepageURL  https://github.com/stevessr/bug-v3
// @supportURL   https://github.com/stevessr/bug-v3/issues
// @run-at       document-end
// ==/UserScript==

(function() {
'use strict';

(function() {
	var __defProp = Object.defineProperty;
	var __esmMin = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
	var __export = (all) => {
		let target = {};
		for (var name in all) __defProp(target, name, {
			get: all[name],
			enumerable: true
		});
		return target;
	};
	async function fetchPackagedJSON() {
		try {
			if (typeof fetch === "undefined") return null;
			const res = await fetch("/assets/defaultEmojiGroups.json", { cache: "no-cache" });
			if (!res.ok) return null;
			return await res.json();
		} catch (err) {
			return null;
		}
	}
	async function loadDefaultEmojiGroups() {
		const packaged = await fetchPackagedJSON();
		if (packaged && Array.isArray(packaged.groups)) return packaged.groups;
		return [];
	}
	var init_defaultEmojiGroups_loader = __esmMin((() => {}));
	function loadDataFromLocalStorage() {
		try {
			const groupsData = localStorage.getItem(STORAGE_KEY);
			let emojiGroups = [];
			if (groupsData) try {
				const parsed = JSON.parse(groupsData);
				if (Array.isArray(parsed) && parsed.length > 0) emojiGroups = parsed;
			} catch (e) {
				console.warn("[Userscript] Failed to parse stored emoji groups:", e);
			}
			if (emojiGroups.length === 0) emojiGroups = [];
			const settingsData = localStorage.getItem(SETTINGS_KEY);
			let settings = {
				imageScale: 30,
				gridColumns: 4,
				outputFormat: "markdown",
				forceMobileMode: false,
				defaultGroup: "nachoneko",
				showSearchBar: true
			};
			if (settingsData) try {
				const parsed = JSON.parse(settingsData);
				if (parsed && typeof parsed === "object") settings = {
					...settings,
					...parsed
				};
			} catch (e) {
				console.warn("[Userscript] Failed to parse stored settings:", e);
			}
			emojiGroups = emojiGroups.filter((g) => g.id !== "favorites");
			console.log("[Userscript] Loaded data from localStorage:", {
				groupsCount: emojiGroups.length,
				emojisCount: emojiGroups.reduce((acc, g) => acc + (g.emojis?.length || 0), 0),
				settings
			});
			return {
				emojiGroups,
				settings
			};
		} catch (error) {
			console.error("[Userscript] Failed to load from localStorage:", error);
			console.error("[Userscript] Failed to load from localStorage:", error);
			return {
				emojiGroups: [],
				settings: {
					imageScale: 30,
					gridColumns: 4,
					outputFormat: "markdown",
					forceMobileMode: false,
					defaultGroup: "nachoneko",
					showSearchBar: true
				}
			};
		}
	}
	async function loadDataFromLocalStorageAsync() {
		try {
			const local = loadDataFromLocalStorage();
			if (local.emojiGroups && local.emojiGroups.length > 0) return local;
			const remoteUrl = localStorage.getItem("emoji_extension_remote_config_url");
			if (remoteUrl && typeof remoteUrl === "string" && remoteUrl.trim().length > 0) try {
				const controller = new AbortController();
				const timeout = setTimeout(() => controller.abort(), 5e3);
				const res = await fetch(remoteUrl, { signal: controller.signal });
				clearTimeout(timeout);
				if (res && res.ok) {
					const json = await res.json();
					const groups = Array.isArray(json.emojiGroups) ? json.emojiGroups : Array.isArray(json.groups) ? json.groups : null;
					const settings = json.settings && typeof json.settings === "object" ? json.settings : local.settings;
					if (groups && groups.length > 0) {
						try {
							localStorage.setItem(STORAGE_KEY, JSON.stringify(groups));
						} catch (e) {
							console.warn("[Userscript] Failed to persist fetched remote groups to localStorage", e);
						}
						return {
							emojiGroups: groups.filter((g) => g.id !== "favorites"),
							settings
						};
					}
				}
			} catch (err) {
				console.warn("[Userscript] Failed to fetch remote default config:", err);
			}
			try {
				const runtime = await loadDefaultEmojiGroups();
				const source = runtime && runtime.length ? runtime : [];
				const filtered = JSON.parse(JSON.stringify(source)).filter((g) => g.id !== "favorites");
				try {
					localStorage.setItem(STORAGE_KEY, JSON.stringify(filtered));
				} catch (e) {}
				return {
					emojiGroups: filtered,
					settings: local.settings
				};
			} catch (e) {
				console.error("[Userscript] Failed to load default groups in async fallback:", e);
				return {
					emojiGroups: [],
					settings: local.settings
				};
			}
		} catch (error) {
			console.error("[Userscript] loadDataFromLocalStorageAsync failed:", error);
			return {
				emojiGroups: [],
				settings: {
					imageScale: 30,
					gridColumns: 4,
					outputFormat: "markdown",
					forceMobileMode: false,
					defaultGroup: "nachoneko",
					showSearchBar: true
				}
			};
		}
	}
	function saveDataToLocalStorage(data) {
		try {
			if (data.emojiGroups) localStorage.setItem(STORAGE_KEY, JSON.stringify(data.emojiGroups));
			if (data.settings) localStorage.setItem(SETTINGS_KEY, JSON.stringify(data.settings));
		} catch (error) {
			console.error("[Userscript] Failed to save to localStorage:", error);
		}
	}
	function addEmojiToUserscript(emojiData) {
		try {
			const data = loadDataFromLocalStorage();
			let userGroup = data.emojiGroups.find((g) => g.id === "user_added");
			if (!userGroup) {
				userGroup = {
					id: "user_added",
					name: "用户添加",
					icon: "⭐",
					order: 999,
					emojis: []
				};
				data.emojiGroups.push(userGroup);
			}
			if (!userGroup.emojis.some((e) => e.url === emojiData.url || e.name === emojiData.name)) {
				userGroup.emojis.push({
					packet: Date.now(),
					name: emojiData.name,
					url: emojiData.url
				});
				saveDataToLocalStorage({ emojiGroups: data.emojiGroups });
				console.log("[Userscript] Added emoji to user group:", emojiData.name);
			} else console.log("[Userscript] Emoji already exists:", emojiData.name);
		} catch (error) {
			console.error("[Userscript] Failed to add emoji:", error);
		}
	}
	function exportUserscriptData() {
		try {
			const data = loadDataFromLocalStorage();
			return JSON.stringify(data, null, 2);
		} catch (error) {
			console.error("[Userscript] Failed to export data:", error);
			return "";
		}
	}
	function importUserscriptData(jsonData) {
		try {
			const data = JSON.parse(jsonData);
			if (data.emojiGroups && Array.isArray(data.emojiGroups)) saveDataToLocalStorage({ emojiGroups: data.emojiGroups });
			if (data.settings && typeof data.settings === "object") saveDataToLocalStorage({ settings: data.settings });
			console.log("[Userscript] Data imported successfully");
			return true;
		} catch (error) {
			console.error("[Userscript] Failed to import data:", error);
			return false;
		}
	}
	function syncFromManager() {
		try {
			const managerGroups = localStorage.getItem("emoji_extension_manager_groups");
			const managerSettings = localStorage.getItem("emoji_extension_manager_settings");
			let updated = false;
			if (managerGroups) {
				const groups = JSON.parse(managerGroups);
				if (Array.isArray(groups)) {
					localStorage.setItem(STORAGE_KEY, JSON.stringify(groups));
					updated = true;
				}
			}
			if (managerSettings) {
				const settings = JSON.parse(managerSettings);
				if (typeof settings === "object") {
					localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings));
					updated = true;
				}
			}
			if (updated) console.log("[Userscript] Synced data from manager");
			return updated;
		} catch (error) {
			console.error("[Userscript] Failed to sync from manager:", error);
			return false;
		}
	}
	var STORAGE_KEY, SETTINGS_KEY;
	var init_userscript_storage = __esmMin((() => {
		init_defaultEmojiGroups_loader();
		STORAGE_KEY = "emoji_extension_userscript_data";
		SETTINGS_KEY = "emoji_extension_userscript_settings";
	}));
	var userscriptState;
	var init_state = __esmMin((() => {
		userscriptState = {
			emojiGroups: [],
			settings: {
				imageScale: 30,
				gridColumns: 4,
				outputFormat: "markdown",
				forceMobileMode: false,
				defaultGroup: "nachoneko",
				showSearchBar: true
			}
		};
	}));
	function createEl(tag, opts) {
		const el = document.createElement(tag);
		if (opts) {
			if (opts.width) el.style.width = opts.width;
			if (opts.height) el.style.height = opts.height;
			if (opts.className) el.className = opts.className;
			if (opts.text) el.textContent = opts.text;
			if (opts.placeholder && "placeholder" in el) el.placeholder = opts.placeholder;
			if (opts.type && "type" in el) el.type = opts.type;
			if (opts.value !== void 0 && "value" in el) el.value = opts.value;
			if (opts.style) el.style.cssText = opts.style;
			if (opts.src && "src" in el) el.src = opts.src;
			if (opts.attrs) for (const k in opts.attrs) el.setAttribute(k, opts.attrs[k]);
			if (opts.dataset) for (const k in opts.dataset) el.dataset[k] = opts.dataset[k];
			if (opts.innerHTML) el.innerHTML = opts.innerHTML;
			if (opts.title) el.title = opts.title;
			if (opts.alt && "alt" in el) el.alt = opts.alt;
		}
		return el;
	}
	var init_createEl = __esmMin((() => {}));
	init_createEl();
	init_state();
	init_userscript_storage();
	function insertIntoEditor(text) {
		const textArea = document.querySelector("textarea.d-editor-input");
		const richEle = document.querySelector(".ProseMirror.d-editor-input");
		if (!textArea && !richEle) {
			console.error("找不到输入框");
			return;
		}
		if (textArea) {
			const start = textArea.selectionStart;
			const end = textArea.selectionEnd;
			const value = textArea.value;
			textArea.value = value.substring(0, start) + text + value.substring(end);
			textArea.setSelectionRange(start + text.length, start + text.length);
			textArea.focus();
			const event = new Event("input", { bubbles: true });
			textArea.dispatchEvent(event);
		} else if (richEle) {
			const selection = window.getSelection();
			if (selection && selection.rangeCount > 0) {
				const range = selection.getRangeAt(0);
				const textNode = document.createTextNode(text);
				range.insertNode(textNode);
				range.setStartAfter(textNode);
				range.setEndAfter(textNode);
				selection.removeAllRanges();
				selection.addRange(range);
			}
			richEle.focus();
		}
	}
	var ImageUploader = class {
		waitingQueue = [];
		uploadingQueue = [];
		failedQueue = [];
		successQueue = [];
		isProcessing = false;
		maxRetries = 2;
		progressDialog = null;
		async uploadImage(file) {
			return new Promise((resolve, reject) => {
				const item = {
					id: `upload_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
					file,
					resolve,
					reject,
					retryCount: 0,
					status: "waiting",
					timestamp: Date.now()
				};
				this.waitingQueue.push(item);
				this.updateProgressDialog();
				this.processQueue();
			});
		}
		moveToQueue(item, targetStatus) {
			this.waitingQueue = this.waitingQueue.filter((i) => i.id !== item.id);
			this.uploadingQueue = this.uploadingQueue.filter((i) => i.id !== item.id);
			this.failedQueue = this.failedQueue.filter((i) => i.id !== item.id);
			this.successQueue = this.successQueue.filter((i) => i.id !== item.id);
			item.status = targetStatus;
			switch (targetStatus) {
				case "waiting":
					this.waitingQueue.push(item);
					break;
				case "uploading":
					this.uploadingQueue.push(item);
					break;
				case "failed":
					this.failedQueue.push(item);
					break;
				case "success":
					this.successQueue.push(item);
					break;
			}
			this.updateProgressDialog();
		}
		async processQueue() {
			if (this.isProcessing || this.waitingQueue.length === 0) return;
			this.isProcessing = true;
			while (this.waitingQueue.length > 0) {
				const item = this.waitingQueue.shift();
				if (!item) continue;
				this.moveToQueue(item, "uploading");
				try {
					const result = await this.performUpload(item.file);
					item.result = result;
					this.moveToQueue(item, "success");
					item.resolve(result);
					const markdown = `![${result.original_filename}](${result.url})`;
					insertIntoEditor(markdown);
				} catch (error) {
					item.error = error;
					if (this.shouldRetry(error, item)) {
						item.retryCount++;
						if (error.error_type === "rate_limit" && error.extras?.wait_seconds) await this.sleep(error.extras.wait_seconds * 1e3);
						else await this.sleep(Math.pow(2, item.retryCount) * 1e3);
						this.moveToQueue(item, "waiting");
					} else {
						this.moveToQueue(item, "failed");
						item.reject(error);
					}
				}
			}
			this.isProcessing = false;
		}
		shouldRetry(error, item) {
			if (item.retryCount >= this.maxRetries) return false;
			return error.error_type === "rate_limit";
		}
		retryFailedItem(itemId) {
			const item = this.failedQueue.find((i) => i.id === itemId);
			if (item && item.retryCount < this.maxRetries) {
				item.retryCount++;
				this.moveToQueue(item, "waiting");
				this.processQueue();
			}
		}
		showProgressDialog() {
			if (this.progressDialog) return;
			this.progressDialog = this.createProgressDialog();
			document.body.appendChild(this.progressDialog);
		}
		hideProgressDialog() {
			if (this.progressDialog) {
				this.progressDialog.remove();
				this.progressDialog = null;
			}
		}
		updateProgressDialog() {
			if (!this.progressDialog) return;
			const allItems = [
				...this.waitingQueue,
				...this.uploadingQueue,
				...this.failedQueue,
				...this.successQueue
			];
			this.renderQueueItems(this.progressDialog, allItems);
		}
		async sleep(ms) {
			return new Promise((resolve) => setTimeout(resolve, ms));
		}
		createProgressDialog() {
			const dialog = document.createElement("div");
			dialog.style.cssText = `
      position: fixed;
      top: 20px;
      right: 20px;
      width: 350px;
      max-height: 400px;
      background: white;
      border-radius: 8px;
      box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
      z-index: 10000;
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
      border: 1px solid #e5e7eb;
      overflow: hidden;
    `;
			const header = document.createElement("div");
			header.style.cssText = `
      padding: 16px 20px;
      background: #f9fafb;
      border-bottom: 1px solid #e5e7eb;
      font-weight: 600;
      font-size: 14px;
      color: #374151;
      display: flex;
      justify-content: space-between;
      align-items: center;
    `;
			header.textContent = "图片上传队列";
			const closeButton = document.createElement("button");
			closeButton.innerHTML = "✕";
			closeButton.style.cssText = `
      background: none;
      border: none;
      font-size: 16px;
      cursor: pointer;
      color: #6b7280;
      padding: 4px;
      border-radius: 4px;
      transition: background-color 0.2s;
    `;
			closeButton.addEventListener("click", () => {
				this.hideProgressDialog();
			});
			closeButton.addEventListener("mouseenter", () => {
				closeButton.style.backgroundColor = "#e5e7eb";
			});
			closeButton.addEventListener("mouseleave", () => {
				closeButton.style.backgroundColor = "transparent";
			});
			header.appendChild(closeButton);
			const content = document.createElement("div");
			content.className = "upload-queue-content";
			content.style.cssText = `
      max-height: 320px;
      overflow-y: auto;
      padding: 12px;
    `;
			dialog.appendChild(header);
			dialog.appendChild(content);
			return dialog;
		}
		renderQueueItems(dialog, allItems) {
			const content = dialog.querySelector(".upload-queue-content");
			if (!content) return;
			content.innerHTML = "";
			if (allItems.length === 0) {
				const emptyState = document.createElement("div");
				emptyState.style.cssText = `
        text-align: center;
        color: #6b7280;
        font-size: 14px;
        padding: 20px;
      `;
				emptyState.textContent = "暂无上传任务";
				content.appendChild(emptyState);
				return;
			}
			allItems.forEach((item) => {
				const itemEl = document.createElement("div");
				itemEl.style.cssText = `
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding: 8px 12px;
        margin-bottom: 8px;
        background: #f9fafb;
        border-radius: 6px;
        border-left: 4px solid ${this.getStatusColor(item.status)};
      `;
				const leftSide = document.createElement("div");
				leftSide.style.cssText = `
        flex: 1;
        min-width: 0;
      `;
				const fileName = document.createElement("div");
				fileName.style.cssText = `
        font-size: 13px;
        font-weight: 500;
        color: #374151;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
      `;
				fileName.textContent = item.file.name;
				const status = document.createElement("div");
				status.style.cssText = `
        font-size: 12px;
        color: #6b7280;
        margin-top: 2px;
      `;
				status.textContent = this.getStatusText(item);
				leftSide.appendChild(fileName);
				leftSide.appendChild(status);
				const rightSide = document.createElement("div");
				rightSide.style.cssText = `
        display: flex;
        align-items: center;
        gap: 8px;
      `;
				if (item.status === "failed" && item.retryCount < this.maxRetries) {
					const retryButton = document.createElement("button");
					retryButton.innerHTML = "🔄";
					retryButton.style.cssText = `
          background: none;
          border: none;
          cursor: pointer;
          font-size: 14px;
          padding: 4px;
          border-radius: 4px;
          transition: background-color 0.2s;
        `;
					retryButton.title = "重试上传";
					retryButton.addEventListener("click", () => {
						this.retryFailedItem(item.id);
					});
					retryButton.addEventListener("mouseenter", () => {
						retryButton.style.backgroundColor = "#e5e7eb";
					});
					retryButton.addEventListener("mouseleave", () => {
						retryButton.style.backgroundColor = "transparent";
					});
					rightSide.appendChild(retryButton);
				}
				const statusIcon = document.createElement("div");
				statusIcon.style.cssText = `
        font-size: 16px;
      `;
				statusIcon.textContent = this.getStatusIcon(item.status);
				rightSide.appendChild(statusIcon);
				itemEl.appendChild(leftSide);
				itemEl.appendChild(rightSide);
				content.appendChild(itemEl);
			});
		}
		getStatusColor(status) {
			switch (status) {
				case "waiting": return "#f59e0b";
				case "uploading": return "#3b82f6";
				case "success": return "#10b981";
				case "failed": return "#ef4444";
				default: return "#6b7280";
			}
		}
		getStatusText(item) {
			switch (item.status) {
				case "waiting": return "等待上传";
				case "uploading": return "正在上传...";
				case "success": return "上传成功";
				case "failed":
					if (item.error?.error_type === "rate_limit") return `上传失败 - 请求过于频繁 (重试 ${item.retryCount}/${this.maxRetries})`;
					return `上传失败 (重试 ${item.retryCount}/${this.maxRetries})`;
				default: return "未知状态";
			}
		}
		getStatusIcon(status) {
			switch (status) {
				case "waiting": return "⏳";
				case "uploading": return "📤";
				case "success": return "✅";
				case "failed": return "❌";
				default: return "❓";
			}
		}
		async performUpload(file) {
			const sha1 = await this.calculateSHA1(file);
			const formData = new FormData();
			formData.append("upload_type", "composer");
			formData.append("relativePath", "null");
			formData.append("name", file.name);
			formData.append("type", file.type);
			formData.append("sha1_checksum", sha1);
			formData.append("file", file, file.name);
			const csrfToken = this.getCSRFToken();
			const headers = { "X-Csrf-Token": csrfToken };
			if (document.cookie) headers["Cookie"] = document.cookie;
			const response = await fetch(`https://linux.do/uploads.json?client_id=f06cb5577ba9410d94b9faf94e48c2d8`, {
				method: "POST",
				headers,
				body: formData
			});
			if (!response.ok) throw await response.json();
			return await response.json();
		}
		getCSRFToken() {
			const metaToken = document.querySelector("meta[name=\"csrf-token\"]");
			if (metaToken) return metaToken.content;
			const match = document.cookie.match(/csrf_token=([^;]+)/);
			if (match) return decodeURIComponent(match[1]);
			const hiddenInput = document.querySelector("input[name=\"authenticity_token\"]");
			if (hiddenInput) return hiddenInput.value;
			console.warn("[Image Uploader] No CSRF token found");
			return "";
		}
		async calculateSHA1(file) {
			const text = `${file.name}-${file.size}-${file.lastModified}`;
			const data = new TextEncoder().encode(text);
			if (crypto.subtle) try {
				const hashBuffer = await crypto.subtle.digest("SHA-1", data);
				return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
			} catch (e) {
				console.warn("[Image Uploader] Could not calculate SHA1, using fallback");
			}
			let hash = 0;
			for (let i = 0; i < text.length; i++) {
				const char = text.charCodeAt(i);
				hash = (hash << 5) - hash + char;
				hash = hash & hash;
			}
			return Math.abs(hash).toString(16).padStart(40, "0");
		}
	};
	var uploader = new ImageUploader();
	function extractEmojiFromImage(img, titleElement) {
		const url = img.src;
		if (!url || !url.startsWith("http")) return null;
		let name = "";
		const parts = (titleElement.textContent || "").split("·");
		if (parts.length > 0) name = parts[0].trim();
		if (!name || name.length < 2) name = img.alt || img.title || extractNameFromUrl(url);
		name = name.trim();
		if (name.length === 0) name = "表情";
		return {
			name,
			url
		};
	}
	function extractNameFromUrl(url) {
		try {
			const nameWithoutExt = (new URL(url).pathname.split("/").pop() || "").replace(/\.[^/.]+$/, "");
			const decoded = decodeURIComponent(nameWithoutExt);
			if (/^[0-9a-f]{8,}$/i.test(decoded) || decoded.length < 2) return "表情";
			return decoded || "表情";
		} catch {
			return "表情";
		}
	}
	function createAddButton(emojiData) {
		const link = createEl("a", {
			className: "image-source-link emoji-add-link",
			style: `
    color: #ffffff;
    text-decoration: none;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    font-size: inherit;
    font-family: inherit;
    background: linear-gradient(135deg, #4f46e5, #7c3aed);
    border: 2px solid #ffffff;
    border-radius: 6px;
    padding: 4px 8px;
    margin: 0 2px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
    transition: all 0.2s ease;
    font-weight: 600;
  `
		});
		link.addEventListener("mouseenter", () => {
			if (!link.innerHTML.includes("已添加") && !link.innerHTML.includes("失败")) {
				link.style.background = "linear-gradient(135deg, #3730a3, #5b21b6)";
				link.style.transform = "scale(1.05)";
				link.style.boxShadow = "0 4px 8px rgba(0, 0, 0, 0.3)";
			}
		});
		link.addEventListener("mouseleave", () => {
			if (!link.innerHTML.includes("已添加") && !link.innerHTML.includes("失败")) {
				link.style.background = "linear-gradient(135deg, #4f46e5, #7c3aed)";
				link.style.transform = "scale(1)";
				link.style.boxShadow = "0 2px 4px rgba(0, 0, 0, 0.2)";
			}
		});
		link.innerHTML = `
    <svg class="fa d-icon d-icon-plus svg-icon svg-string" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" style="width: 1em; height: 1em; fill: currentColor; margin-right: 4px;">
      <path d="M12 4c.55 0 1 .45 1 1v6h6c.55 0 1 .45 1 1s-.45 1-1 1h-6v6c0 .55-.45 1-1 1s-1-.45-1-1v-6H5c-.55 0-1-.45-1-1s.45-1 1-1h6V5c0-.55.45-1 1-1z"/>
    </svg>添加表情
  `;
		link.title = "添加到用户表情";
		link.addEventListener("click", async (e) => {
			e.preventDefault();
			e.stopPropagation();
			const originalHTML = link.innerHTML;
			const originalStyle = link.style.cssText;
			try {
				addEmojiToUserscript(emojiData);
				try {
					uploader.showProgressDialog();
				} catch (e$1) {
					console.warn("[Userscript] uploader.showProgressDialog failed:", e$1);
				}
				link.innerHTML = `
        <svg class="fa d-icon d-icon-check svg-icon svg-string" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" style="width: 1em; height: 1em; fill: currentColor; margin-right: 4px;">
          <path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
        </svg>已添加
      `;
				link.style.background = "linear-gradient(135deg, #10b981, #059669)";
				link.style.color = "#ffffff";
				link.style.border = "2px solid #ffffff";
				link.style.boxShadow = "0 2px 4px rgba(16, 185, 129, 0.3)";
				setTimeout(() => {
					link.innerHTML = originalHTML;
					link.style.cssText = originalStyle;
				}, 2e3);
			} catch (error) {
				console.error("[Emoji Extension Userscript] Failed to add emoji:", error);
				link.innerHTML = `
        <svg class="fa d-icon d-icon-times svg-icon svg-string" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" style="width: 1em; height: 1em; fill: currentColor; margin-right: 4px;">
          <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
        </svg>失败
      `;
				link.style.background = "linear-gradient(135deg, #ef4444, #dc2626)";
				link.style.color = "#ffffff";
				link.style.border = "2px solid #ffffff";
				link.style.boxShadow = "0 2px 4px rgba(239, 68, 68, 0.3)";
				setTimeout(() => {
					link.innerHTML = originalHTML;
					link.style.cssText = originalStyle;
				}, 2e3);
			}
		});
		return link;
	}
	function processLightbox(lightbox) {
		if (lightbox.querySelector(".emoji-add-link")) return;
		const img = lightbox.querySelector(".mfp-img");
		const title = lightbox.querySelector(".mfp-title");
		if (!img || !title) return;
		const emojiData = extractEmojiFromImage(img, title);
		if (!emojiData) return;
		const addButton = createAddButton(emojiData);
		const sourceLink = title.querySelector("a.image-source-link");
		if (sourceLink) {
			const separator = document.createTextNode(" · ");
			title.insertBefore(separator, sourceLink);
			title.insertBefore(addButton, sourceLink);
		} else {
			title.appendChild(document.createTextNode(" · "));
			title.appendChild(addButton);
		}
	}
	function processAllLightboxes() {
		document.querySelectorAll(".mfp-wrap.mfp-gallery").forEach((lightbox) => {
			if (lightbox.classList.contains("mfp-wrap") && lightbox.classList.contains("mfp-gallery") && lightbox.querySelector(".mfp-img")) processLightbox(lightbox);
		});
	}
	function initOneClickAdd() {
		console.log("[Emoji Extension Userscript] Initializing one-click add functionality");
		setTimeout(processAllLightboxes, 500);
		new MutationObserver((mutations) => {
			let hasNewLightbox = false;
			mutations.forEach((mutation) => {
				if (mutation.type === "childList") mutation.addedNodes.forEach((node) => {
					if (node.nodeType === Node.ELEMENT_NODE) {
						const element = node;
						if (element.classList && element.classList.contains("mfp-wrap")) hasNewLightbox = true;
					}
				});
			});
			if (hasNewLightbox) setTimeout(processAllLightboxes, 100);
		}).observe(document.body, {
			childList: true,
			subtree: true
		});
		document.addEventListener("visibilitychange", () => {
			if (!document.hidden) setTimeout(processAllLightboxes, 200);
		});
	}
	function isImageUrl(value) {
		if (!value) return false;
		let v = value.trim();
		if (/^url\(/i.test(v)) {
			const inner = v.replace(/^url\(/i, "").replace(/\)$/, "").trim();
			if (inner.startsWith("\"") && inner.endsWith("\"") || inner.startsWith("'") && inner.endsWith("'")) v = inner.slice(1, -1).trim();
			else v = inner;
		}
		if (v.startsWith("data:image/")) return true;
		if (v.startsWith("blob:")) return true;
		if (v.startsWith("//")) v = "https:" + v;
		if (/\.(png|jpe?g|gif|webp|svg|avif|bmp|ico)(\?.*)?$/i.test(v)) return true;
		try {
			const url = new URL(v);
			const protocol = url.protocol;
			if (protocol === "http:" || protocol === "https:" || protocol.endsWith(":")) {
				if (/\.(png|jpe?g|gif|webp|svg|avif|bmp|ico)(\?.*)?$/i.test(url.pathname)) return true;
				if (/format=|ext=|type=image|image_type=/i.test(url.search)) return true;
			}
		} catch {}
		return false;
	}
	function injectEmojiPickerStyles() {
		if (typeof document === "undefined") return;
		if (document.getElementById("emoji-picker-styles")) return;
		const css = `
.emoji-picker-hover-preview{
  position:fixed;
  pointer-events:none;
  display:none;
  z-index:1000002;
  max-width:320px;
  max-height:320px;
  overflow:hidden;
  border-radius:8px;
  box-shadow:0 6px 20px rgba(0,0,0,0.32);
  background:#ffffff;
  padding:8px;
  transition:opacity .12s ease, transform .12s ease;
}
.emoji-picker-hover-preview img.emoji-picker-hover-img{
  display:block;
  max-width:100%;
  max-height:220px;
  object-fit:contain;
}
.emoji-picker-hover-preview .emoji-picker-hover-label{
  font-size:12px;
  color:#222;
  margin-top:8px;
  text-align:center;
  word-break:break-word;
}

/* Dark theme adaptation */
@media (prefers-color-scheme: dark) {
  .emoji-picker-hover-preview{
    background: rgba(32,33,36,0.94);
    box-shadow: 0 6px 20px rgba(0,0,0,0.6);
    border: 1px solid rgba(255,255,255,0.04);
  }
  .emoji-picker-hover-preview .emoji-picker-hover-label{
    color: #e6e6e6;
  }
}
`;
		const style = document.createElement("style");
		style.id = "emoji-picker-styles";
		style.textContent = css;
		document.head.appendChild(style);
	}
	const __vitePreload = function preload(baseModule, deps, importerUrl) {
		let promise = Promise.resolve();
		function handlePreloadError(err$2) {
			const e$1 = new Event("vite:preloadError", { cancelable: true });
			e$1.payload = err$2;
			window.dispatchEvent(e$1);
			if (!e$1.defaultPrevented) throw err$2;
		}
		return promise.then((res) => {
			for (const item of res || []) {
				if (item.status !== "rejected") continue;
				handlePreloadError(item.reason);
			}
			return baseModule().catch(handlePreloadError);
		});
	};
	function injectManagerStyles() {
		if (__managerStylesInjected) return;
		__managerStylesInjected = true;
		document.head.appendChild(createEl("style", {
			attrs: { "data-emoji-manager-styles": "1" },
			text: `
    /* Modal backdrop */
    .emoji-manager-wrapper { 
      position: fixed; 
      top: 0; 
      left: 0; 
      right: 0; 
      bottom: 0; 
      background: rgba(0,0,0,0.8); 
      z-index: 999999; 
      display: flex; 
      align-items: center; 
      justify-content: center; 
    }
    
    /* Main modal panel */
    .emoji-manager-panel { 
      background: white; 
      border-radius: 8px; 
      max-width: 90vw; 
      max-height: 90vh; 
      width: 1000px; 
      height: 600px; 
      display: grid; 
      grid-template-columns: 300px 1fr; 
      grid-template-rows: 1fr auto;
      overflow: hidden; 
      box-shadow: 0 10px 40px rgba(0,0,0,0.3); 
    }
    
    /* Left panel - groups list */
    .emoji-manager-left { 
      background: #f8f9fa; 
      border-right: 1px solid #e9ecef; 
      display: flex; 
      flex-direction: column; 
      overflow: hidden; 
    }
    
    .emoji-manager-left-header { 
      display: flex; 
      align-items: center; 
      padding: 16px; 
      border-bottom: 1px solid #e9ecef; 
      background: white; 
    }
    
    .emoji-manager-addgroup-row { 
      display: flex; 
      gap: 8px; 
      padding: 12px; 
      border-bottom: 1px solid #e9ecef; 
    }
    
    .emoji-manager-groups-list { 
      flex: 1; 
      overflow-y: auto; 
      padding: 8px; 
    }
    
    .emoji-manager-groups-list > div { 
      padding: 12px; 
      border-radius: 6px; 
      cursor: pointer; 
      margin-bottom: 4px; 
      transition: background-color 0.2s; 
    }
    
    .emoji-manager-groups-list > div:hover { 
      background: #e9ecef; 
    }
    
    .emoji-manager-groups-list > div:focus { 
      outline: none; 
      box-shadow: inset 0 0 0 2px #007bff; 
    }
    
    /* Right panel - emoji display and editing */
    .emoji-manager-right { 
      background: white; 
      display: flex; 
      flex-direction: column; 
      overflow: hidden; 
    }
    
    .emoji-manager-right-header { 
      display: flex; 
      align-items: center; 
      justify-content: space-between; 
      padding: 16px; 
      border-bottom: 1px solid #e9ecef; 
    }
    
    .emoji-manager-right-main { 
      flex: 1; 
      overflow-y: auto; 
      padding: 16px; 
    }
    
    .emoji-manager-emojis { 
      display: grid; 
      grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); 
      gap: 12px; 
      margin-bottom: 16px; 
    }
    
    .emoji-manager-card { 
      display: flex; 
      flex-direction: column; 
      gap: 8px; 
      align-items: center; 
      padding: 12px; 
      background: #f8f9fa; 
      border: 1px solid #e9ecef; 
      border-radius: 8px; 
      transition: transform 0.2s, box-shadow 0.2s; 
    }
    
    .emoji-manager-card:hover { 
      transform: translateY(-2px); 
      box-shadow: 0 4px 12px rgba(0,0,0,0.1); 
    }
    
    .emoji-manager-card-img { 
      width: 80px; 
      height: 80px; 
      object-fit: contain; 
      border-radius: 6px; 
      background: white; 
    }
    
    .emoji-manager-card-name { 
      font-size: 12px; 
      color: #495057; 
      text-align: center; 
      width: 100%; 
      overflow: hidden; 
      white-space: nowrap; 
      text-overflow: ellipsis; 
      font-weight: 500; 
    }
    
    .emoji-manager-card-actions { 
      display: flex; 
      gap: 6px; 
    }
    
    /* Add emoji form */
    .emoji-manager-add-emoji-form { 
      padding: 16px; 
      background: #f8f9fa; 
      border-top: 1px solid #e9ecef; 
      display: flex; 
      gap: 8px; 
      align-items: center; 
    }
    
    /* Footer */
    .emoji-manager-footer { 
      grid-column: 1 / -1;
      display: flex; 
      gap: 8px; 
      justify-content: space-between; 
      padding: 16px; 
      background: #f8f9fa; 
      border-top: 1px solid #e9ecef; 
    }
    
    /* Editor panel - popup modal */
    .emoji-manager-editor-panel { 
      position: fixed; 
      top: 50%; 
      left: 50%; 
      transform: translate(-50%, -50%); 
      background: white; 
      border: 1px solid #e9ecef; 
      border-radius: 8px; 
      padding: 24px; 
      box-shadow: 0 10px 40px rgba(0,0,0,0.3); 
      z-index: 1000000; 
      min-width: 400px; 
    }
    
    .emoji-manager-editor-preview { 
      width: 100px; 
      height: 100px; 
      object-fit: contain; 
      border-radius: 8px; 
      background: #f8f9fa; 
      margin: 0 auto 16px; 
      display: block; 
    }

    /* Hover preview (moved from inline styles) */
    .emoji-manager-hover-preview {
      position: fixed;
      pointer-events: none;
      z-index: 1000002;
      display: none;
      max-width: 300px;
      max-height: 300px;
      border: 1px solid rgba(0,0,0,0.1);
      background: #fff;
      padding: 4px;
      border-radius: 6px;
      box-shadow: 0 6px 18px rgba(0,0,0,0.12);
    }
    
    /* Form styling */
    .form-control { 
      width: 100%; 
      padding: 8px 12px; 
      border: 1px solid #ced4da; 
      border-radius: 4px; 
      font-size: 14px; 
      margin-bottom: 8px; 
    }
    
    .btn { 
      padding: 8px 16px; 
      border: 1px solid transparent; 
      border-radius: 4px; 
      font-size: 14px; 
      cursor: pointer; 
      transition: all 0.2s; 
    }
    
    .btn-primary { 
      background-color: #007bff; 
      color: white; 
    }
    
    .btn-primary:hover { 
      background-color: #0056b3; 
    }
    
    .btn-sm { 
      padding: 4px 8px; 
      font-size: 12px; 
    }
  `
		}));
	}
	var __managerStylesInjected;
	var init_styles = __esmMin((() => {
		init_createEl();
		__managerStylesInjected = false;
	}));
	var manager_exports = __export({ openManagementInterface: () => openManagementInterface });
	function createEditorPopup(groupId, index, renderGroups, renderSelectedGroup) {
		const group = userscriptState.emojiGroups.find((g) => g.id === groupId);
		if (!group) return;
		const emo = group.emojis[index];
		if (!emo) return;
		const backdrop = createEl("div", { style: `
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(0,0,0,0.5);
    z-index: 1000000;
    display: flex;
    align-items: center;
    justify-content: center;
  ` });
		const editorPanel = createEl("div", { className: "emoji-manager-editor-panel" });
		const editorTitle = createEl("h3", {
			text: "编辑表情",
			className: "emoji-manager-editor-title",
			style: "margin: 0 0 16px 0; text-align: center;"
		});
		const editorPreview = createEl("img", { className: "emoji-manager-editor-preview" });
		editorPreview.src = emo.url;
		const editorWidthInput = createEl("input", {
			className: "form-control",
			placeholder: "宽度 (px) 可选",
			value: emo.width ? String(emo.width) : ""
		});
		const editorHeightInput = createEl("input", {
			className: "form-control",
			placeholder: "高度 (px) 可选",
			value: emo.height ? String(emo.height) : ""
		});
		const editorNameInput = createEl("input", {
			className: "form-control",
			placeholder: "名称 (alias)",
			value: emo.name || ""
		});
		const editorUrlInput = createEl("input", {
			className: "form-control",
			placeholder: "表情图片 URL",
			value: emo.url || ""
		});
		const buttonContainer = createEl("div", { style: "display: flex; gap: 8px; justify-content: flex-end; margin-top: 16px;" });
		const editorSaveBtn = createEl("button", {
			text: "保存修改",
			className: "btn btn-primary"
		});
		const editorCancelBtn = createEl("button", {
			text: "取消",
			className: "btn"
		});
		buttonContainer.appendChild(editorCancelBtn);
		buttonContainer.appendChild(editorSaveBtn);
		editorPanel.appendChild(editorTitle);
		editorPanel.appendChild(editorPreview);
		editorPanel.appendChild(editorWidthInput);
		editorPanel.appendChild(editorHeightInput);
		editorPanel.appendChild(editorNameInput);
		editorPanel.appendChild(editorUrlInput);
		editorPanel.appendChild(buttonContainer);
		backdrop.appendChild(editorPanel);
		document.body.appendChild(backdrop);
		editorUrlInput.addEventListener("input", () => {
			editorPreview.src = editorUrlInput.value;
		});
		editorSaveBtn.addEventListener("click", () => {
			const newName = (editorNameInput.value || "").trim();
			const newUrl = (editorUrlInput.value || "").trim();
			const newWidth = parseInt((editorWidthInput.value || "").trim(), 10);
			const newHeight = parseInt((editorHeightInput.value || "").trim(), 10);
			if (!newName || !newUrl) {
				alert("名称和 URL 均不能为空");
				return;
			}
			emo.name = newName;
			emo.url = newUrl;
			if (!isNaN(newWidth) && newWidth > 0) emo.width = newWidth;
			else delete emo.width;
			if (!isNaN(newHeight) && newHeight > 0) emo.height = newHeight;
			else delete emo.height;
			renderGroups();
			renderSelectedGroup();
			backdrop.remove();
		});
		editorCancelBtn.addEventListener("click", () => {
			backdrop.remove();
		});
		backdrop.addEventListener("click", (e) => {
			if (e.target === backdrop) backdrop.remove();
		});
	}
	function openManagementInterface() {
		injectManagerStyles();
		const modal = createEl("div", {
			className: "emoji-manager-wrapper",
			attrs: {
				role: "dialog",
				"aria-modal": "true"
			}
		});
		const panel = createEl("div", { className: "emoji-manager-panel" });
		const left = createEl("div", { className: "emoji-manager-left" });
		const leftHeader = createEl("div", { className: "emoji-manager-left-header" });
		const title = createEl("h3", { text: "表情管理器" });
		const closeBtn = createEl("button", {
			text: "×",
			className: "btn",
			style: "font-size:20px; background:none; border:none; cursor:pointer;"
		});
		leftHeader.appendChild(title);
		leftHeader.appendChild(closeBtn);
		left.appendChild(leftHeader);
		const addGroupRow = createEl("div", { className: "emoji-manager-addgroup-row" });
		const addGroupInput = createEl("input", {
			placeholder: "新分组 id",
			className: "form-control"
		});
		const addGroupBtn = createEl("button", {
			text: "添加",
			className: "btn"
		});
		addGroupRow.appendChild(addGroupInput);
		addGroupRow.appendChild(addGroupBtn);
		left.appendChild(addGroupRow);
		const groupsList = createEl("div", { className: "emoji-manager-groups-list" });
		left.appendChild(groupsList);
		const right = createEl("div", { className: "emoji-manager-right" });
		const rightHeader = createEl("div", { className: "emoji-manager-right-header" });
		const groupTitle = createEl("h4");
		groupTitle.textContent = "";
		const deleteGroupBtn = createEl("button", {
			text: "删除分组",
			className: "btn",
			style: "background:#ef4444; color:#fff;"
		});
		rightHeader.appendChild(groupTitle);
		rightHeader.appendChild(deleteGroupBtn);
		right.appendChild(rightHeader);
		const managerRightMain = createEl("div", { className: "emoji-manager-right-main" });
		const emojisContainer = createEl("div", { className: "emoji-manager-emojis" });
		managerRightMain.appendChild(emojisContainer);
		const addEmojiForm = createEl("div", { className: "emoji-manager-add-emoji-form" });
		const emojiUrlInput = createEl("input", {
			placeholder: "表情图片 URL",
			className: "form-control"
		});
		const emojiNameInput = createEl("input", {
			placeholder: "名称 (alias)",
			className: "form-control"
		});
		const emojiWidthInput = createEl("input", {
			placeholder: "宽度 (px) 可选",
			className: "form-control"
		});
		const emojiHeightInput = createEl("input", {
			placeholder: "高度 (px) 可选",
			className: "form-control"
		});
		const addEmojiBtn = createEl("button", {
			text: "添加表情",
			className: "btn btn-primary"
		});
		addEmojiForm.appendChild(emojiUrlInput);
		addEmojiForm.appendChild(emojiNameInput);
		addEmojiForm.appendChild(emojiWidthInput);
		addEmojiForm.appendChild(emojiHeightInput);
		addEmojiForm.appendChild(addEmojiBtn);
		managerRightMain.appendChild(addEmojiForm);
		right.appendChild(managerRightMain);
		const footer = createEl("div", { className: "emoji-manager-footer" });
		const exportBtn = createEl("button", {
			text: "导出",
			className: "btn"
		});
		const importBtn = createEl("button", {
			text: "导入",
			className: "btn"
		});
		const exitBtn = createEl("button", {
			text: "退出",
			className: "btn"
		});
		exitBtn.addEventListener("click", () => modal.remove());
		const saveBtn = createEl("button", {
			text: "保存",
			className: "btn btn-primary"
		});
		const syncBtn = createEl("button", {
			text: "同步管理器",
			className: "btn"
		});
		footer.appendChild(syncBtn);
		footer.appendChild(exportBtn);
		footer.appendChild(importBtn);
		footer.appendChild(exitBtn);
		footer.appendChild(saveBtn);
		panel.appendChild(left);
		panel.appendChild(right);
		panel.appendChild(footer);
		modal.appendChild(panel);
		document.body.appendChild(modal);
		let selectedGroupId = null;
		function renderGroups() {
			groupsList.innerHTML = "";
			if (!selectedGroupId && userscriptState.emojiGroups.length > 0) selectedGroupId = userscriptState.emojiGroups[0].id;
			userscriptState.emojiGroups.forEach((g) => {
				const row = createEl("div", {
					style: "display:flex; justify-content:space-between; align-items:center; padding:6px; border-radius:4px; cursor:pointer;",
					text: `${g.name || g.id} (${(g.emojis || []).length})`,
					attrs: {
						tabindex: "0",
						"data-group-id": g.id
					}
				});
				const selectGroup = () => {
					selectedGroupId = g.id;
					renderGroups();
					renderSelectedGroup();
				};
				row.addEventListener("click", selectGroup);
				row.addEventListener("keydown", (e) => {
					if (e.key === "Enter" || e.key === " ") {
						e.preventDefault();
						selectGroup();
					}
				});
				if (selectedGroupId === g.id) row.style.background = "#f0f8ff";
				groupsList.appendChild(row);
			});
		}
		function showEditorFor(groupId, index) {
			createEditorPopup(groupId, index, renderGroups, renderSelectedGroup);
		}
		function renderSelectedGroup() {
			const group = userscriptState.emojiGroups.find((g) => g.id === selectedGroupId) || null;
			groupTitle.textContent = group ? group.name || group.id : "";
			emojisContainer.innerHTML = "";
			if (!group) return;
			(Array.isArray(group.emojis) ? group.emojis : []).forEach((emo, idx) => {
				const card = createEl("div", { className: "emoji-manager-card" });
				const img = createEl("img", {
					src: emo.url,
					alt: emo.name,
					className: "emoji-manager-card-img"
				});
				if (emo.width) img.style.width = typeof emo.width === "number" ? emo.width + "px" : emo.width;
				if (emo.height) img.style.height = typeof emo.height === "number" ? emo.height + "px" : emo.height;
				const name = createEl("div", {
					text: emo.name,
					className: "emoji-manager-card-name"
				});
				const actions = createEl("div", { className: "emoji-manager-card-actions" });
				const edit = createEl("button", {
					text: "编辑",
					className: "btn btn-sm"
				});
				edit.addEventListener("click", () => {
					showEditorFor(group.id, idx);
				});
				const del = createEl("button", {
					text: "删除",
					className: "btn btn-sm"
				});
				del.addEventListener("click", () => {
					group.emojis.splice(idx, 1);
					renderGroups();
					renderSelectedGroup();
				});
				actions.appendChild(edit);
				actions.appendChild(del);
				card.appendChild(img);
				card.appendChild(name);
				card.appendChild(actions);
				emojisContainer.appendChild(card);
				bindHoverPreview(img, emo);
			});
		}
		let hoverPreviewEl = null;
		function ensureHoverPreview$1() {
			if (hoverPreviewEl && document.body.contains(hoverPreviewEl)) return hoverPreviewEl;
			hoverPreviewEl = createEl("img", { className: "emoji-manager-hover-preview" });
			document.body.appendChild(hoverPreviewEl);
			return hoverPreviewEl;
		}
		function bindHoverPreview(targetImg, emo) {
			const preview = ensureHoverPreview$1();
			function onEnter(e) {
				preview.src = emo.url;
				if (emo.width) preview.style.width = typeof emo.width === "number" ? emo.width + "px" : emo.width;
				else preview.style.width = "";
				if (emo.height) preview.style.height = typeof emo.height === "number" ? emo.height + "px" : emo.height;
				else preview.style.height = "";
				preview.style.display = "block";
				movePreview(e);
			}
			function movePreview(e) {
				const pad = 12;
				const vw = window.innerWidth;
				const vh = window.innerHeight;
				const rect = preview.getBoundingClientRect();
				let left$1 = e.clientX + pad;
				let top = e.clientY + pad;
				if (left$1 + rect.width > vw) left$1 = e.clientX - rect.width - pad;
				if (top + rect.height > vh) top = e.clientY - rect.height - pad;
				preview.style.left = left$1 + "px";
				preview.style.top = top + "px";
			}
			function onLeave() {
				if (preview) preview.style.display = "none";
			}
			targetImg.addEventListener("mouseenter", onEnter);
			targetImg.addEventListener("mousemove", movePreview);
			targetImg.addEventListener("mouseleave", onLeave);
		}
		addGroupBtn.addEventListener("click", () => {
			const id = (addGroupInput.value || "").trim();
			if (!id) return alert("请输入分组 id");
			if (userscriptState.emojiGroups.find((g) => g.id === id)) return alert("分组已存在");
			userscriptState.emojiGroups.push({
				id,
				name: id,
				emojis: []
			});
			addGroupInput.value = "";
			const newIdx = userscriptState.emojiGroups.findIndex((g) => g.id === id);
			if (newIdx >= 0) selectedGroupId = userscriptState.emojiGroups[newIdx].id;
			renderGroups();
			renderSelectedGroup();
		});
		addEmojiBtn.addEventListener("click", () => {
			if (!selectedGroupId) return alert("请先选择分组");
			const url = (emojiUrlInput.value || "").trim();
			const name = (emojiNameInput.value || "").trim();
			const widthVal = (emojiWidthInput.value || "").trim();
			const heightVal = (emojiHeightInput.value || "").trim();
			const width = widthVal ? parseInt(widthVal, 10) : NaN;
			const height = heightVal ? parseInt(heightVal, 10) : NaN;
			if (!url || !name) return alert("请输入 url 和 名称");
			const group = userscriptState.emojiGroups.find((g) => g.id === selectedGroupId);
			if (!group) return;
			group.emojis = group.emojis || [];
			const newEmo = {
				url,
				name
			};
			if (!isNaN(width) && width > 0) newEmo.width = width;
			if (!isNaN(height) && height > 0) newEmo.height = height;
			group.emojis.push(newEmo);
			emojiUrlInput.value = "";
			emojiNameInput.value = "";
			emojiWidthInput.value = "";
			emojiHeightInput.value = "";
			renderGroups();
			renderSelectedGroup();
		});
		deleteGroupBtn.addEventListener("click", () => {
			if (!selectedGroupId) return alert("请先选择分组");
			const idx = userscriptState.emojiGroups.findIndex((g) => g.id === selectedGroupId);
			if (idx >= 0) {
				if (!confirm("确认删除该分组?该操作不可撤销")) return;
				userscriptState.emojiGroups.splice(idx, 1);
				if (userscriptState.emojiGroups.length > 0) selectedGroupId = userscriptState.emojiGroups[Math.min(idx, userscriptState.emojiGroups.length - 1)].id;
				else selectedGroupId = null;
				renderGroups();
				renderSelectedGroup();
			}
		});
		exportBtn.addEventListener("click", () => {
			const data = exportUserscriptData();
			navigator.clipboard.writeText(data).then(() => alert("已复制到剪贴板")).catch(() => {
				const ta = createEl("textarea", { value: data });
				document.body.appendChild(ta);
				ta.select();
			});
		});
		importBtn.addEventListener("click", () => {
			const ta = createEl("textarea", {
				placeholder: "粘贴 JSON 后点击确认",
				style: "width:100%;height:200px;margin-top:8px;"
			});
			const ok = createEl("button", {
				text: "确认导入",
				style: "padding:6px 8px;margin-top:6px;"
			});
			const container = createEl("div");
			container.appendChild(ta);
			container.appendChild(ok);
			const importModal = createEl("div", { style: "position:fixed;left:0;top:0;right:0;bottom:0;background:rgba(0,0,0,0.6);display:flex;align-items:center;justify-content:center;z-index:1000001;" });
			const box = createEl("div", { style: "background:#fff;padding:12px;border-radius:6px;width:90%;max-width:700px;" });
			box.appendChild(container);
			importModal.appendChild(box);
			document.body.appendChild(importModal);
			ok.addEventListener("click", () => {
				try {
					const json = ta.value.trim();
					if (!json) return;
					if (importUserscriptData(json)) {
						alert("导入成功,请保存以持久化");
						loadDataFromLocalStorage$1();
						renderGroups();
						renderSelectedGroup();
					} else alert("导入失败:格式错误");
				} catch (e) {
					alert("导入异常:" + e);
				}
				importModal.remove();
			});
		});
		saveBtn.addEventListener("click", () => {
			try {
				saveDataToLocalStorage({ emojiGroups: userscriptState.emojiGroups });
				alert("已保存");
			} catch (e) {
				alert("保存失败:" + e);
			}
		});
		syncBtn.addEventListener("click", () => {
			try {
				if (syncFromManager()) {
					alert("同步成功,已导入管理器数据");
					loadDataFromLocalStorage$1();
					renderGroups();
					renderSelectedGroup();
				} else alert("同步未成功,未检测到管理器数据");
			} catch (e) {
				alert("同步异常:" + e);
			}
		});
		closeBtn.addEventListener("click", () => modal.remove());
		modal.addEventListener("click", (e) => {
			if (e.target === modal) modal.remove();
		});
		renderGroups();
		if (userscriptState.emojiGroups.length > 0) {
			selectedGroupId = userscriptState.emojiGroups[0].id;
			const first = groupsList.firstChild;
			if (first) first.style.background = "#f0f8ff";
			renderSelectedGroup();
		}
	}
	function loadDataFromLocalStorage$1() {
		console.log("Data reload requested");
	}
	var init_manager = __esmMin((() => {
		init_styles();
		init_createEl();
		init_userscript_storage();
	}));
	var settings_exports = __export({ showSettingsModal: () => showSettingsModal });
	function showSettingsModal() {
		const modal = createEl("div", { style: `
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.8);
    z-index: 999999;
    display: flex;
    align-items: center;
    justify-content: center;
  ` });
		const content = createEl("div", {
			style: `
      background: white;
      border-radius: 8px;
    padding: 24px;
    max-width: 500px;
    max-height: 80vh;
    overflow-y: auto;
    position: relative;
  `,
			innerHTML: `
    <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
      <h2 style="margin: 0; color: #333;">设置</h2>
      <button id="closeModal" style="background: none; border: none; font-size: 24px; cursor: pointer; color: #999;">×</button>
    </div>
    
    <div style="margin-bottom: 16px;">
      <label style="display: block; margin-bottom: 8px; color: #555; font-weight: 500;">图片缩放比例: <span id="scaleValue">${userscriptState.settings.imageScale}%</span></label>
      <input type="range" id="scaleSlider" min="5" max="150" step="5" value="${userscriptState.settings.imageScale}" 
             style="width: 100%; margin-bottom: 8px;">
    </div>
    
    <div style="margin-bottom: 16px;">
      <label style="display: block; margin-bottom: 8px; color: #555; font-weight: 500;">输出格式:</label>
      <div style="display: flex; gap: 16px;">
        <label style="display: flex; align-items: center; color: #666;">
          <input type="radio" name="outputFormat" value="markdown" ${userscriptState.settings.outputFormat === "markdown" ? "checked" : ""} style="margin-right: 4px;">
          Markdown
        </label>
        <label style="display: flex; align-items: center; color: #666;">
          <input type="radio" name="outputFormat" value="html" ${userscriptState.settings.outputFormat === "html" ? "checked" : ""} style="margin-right: 4px;">
          HTML
        </label>
      </div>
    </div>
    
    <div style="margin-bottom: 16px;">
      <label style="display: flex; align-items: center; color: #555; font-weight: 500;">
        <input type="checkbox" id="showSearchBar" ${userscriptState.settings.showSearchBar ? "checked" : ""} style="margin-right: 8px;">
        显示搜索栏
      </label>
    </div>
    
    <div style="margin-bottom: 16px;">
      <label style="display: flex; align-items: center; color: #555; font-weight: 500;">
        <input type="checkbox" id="forceMobileMode" ${userscriptState.settings.forceMobileMode ? "checked" : ""} style="margin-right: 8px;">
        强制移动模式 (在不兼容检测时也注入移动版布局)
      </label>
    </div>
    
    <div style="display: flex; gap: 8px; justify-content: flex-end;">
      <button id="resetSettings" style="padding: 8px 16px; background: #f5f5f5; border: 1px solid #ddd; border-radius: 4px; cursor: pointer;">重置</button>
      <button id="saveSettings" style="padding: 8px 16px; background: #1890ff; color: white; border: none; border-radius: 4px; cursor: pointer;">保存</button>
    </div>
  `
		});
		modal.appendChild(content);
		document.body.appendChild(modal);
		const scaleSlider = content.querySelector("#scaleSlider");
		const scaleValue = content.querySelector("#scaleValue");
		scaleSlider?.addEventListener("input", () => {
			if (scaleValue) scaleValue.textContent = scaleSlider.value + "%";
		});
		content.querySelector("#closeModal")?.addEventListener("click", () => {
			modal.remove();
		});
		content.querySelector("#resetSettings")?.addEventListener("click", async () => {
			if (confirm("确定要重置所有设置吗?")) {
				userscriptState.settings = {
					imageScale: 30,
					gridColumns: 4,
					outputFormat: "markdown",
					forceMobileMode: false,
					defaultGroup: "nachoneko",
					showSearchBar: true
				};
				modal.remove();
			}
		});
		content.querySelector("#saveSettings")?.addEventListener("click", () => {
			userscriptState.settings.imageScale = parseInt(scaleSlider?.value || "30");
			const outputFormat = content.querySelector("input[name=\"outputFormat\"]:checked");
			if (outputFormat) userscriptState.settings.outputFormat = outputFormat.value;
			const showSearchBar = content.querySelector("#showSearchBar");
			if (showSearchBar) userscriptState.settings.showSearchBar = showSearchBar.checked;
			const forceMobileEl = content.querySelector("#forceMobileMode");
			if (forceMobileEl) userscriptState.settings.forceMobileMode = !!forceMobileEl.checked;
			saveDataToLocalStorage({ settings: userscriptState.settings });
			try {
				const remoteInput = content.querySelector("#remoteConfigUrl");
				if (remoteInput && remoteInput.value.trim()) localStorage.setItem("emoji_extension_remote_config_url", remoteInput.value.trim());
			} catch (e) {}
			alert("设置已保存");
			modal.remove();
		});
		modal.addEventListener("click", (e) => {
			if (e.target === modal) modal.remove();
		});
	}
	var init_settings = __esmMin((() => {
		init_state();
		init_userscript_storage();
		init_createEl();
	}));
	init_state();
	init_createEl();
	function isMobileView() {
		try {
			return !!(userscriptState && userscriptState.settings && userscriptState.settings.forceMobileMode);
		} catch (e) {
			return false;
		}
	}
	function insertEmojiIntoEditor(emoji) {
		console.log("[Emoji Extension Userscript] Inserting emoji:", emoji);
		const textarea = document.querySelector("textarea.d-editor-input");
		const proseMirror = document.querySelector(".ProseMirror.d-editor-input");
		if (!textarea && !proseMirror) {
			console.error("找不到输入框");
			return;
		}
		const dimensionMatch = emoji.url?.match(/_(\d{3,})x(\d{3,})\./);
		let width = "500";
		let height = "500";
		if (dimensionMatch) {
			width = dimensionMatch[1];
			height = dimensionMatch[2];
		} else if (emoji.width && emoji.height) {
			width = emoji.width.toString();
			height = emoji.height.toString();
		}
		const scale = userscriptState.settings?.imageScale || 30;
		const outputFormat = userscriptState.settings?.outputFormat || "markdown";
		if (textarea) {
			let insertText = "";
			if (outputFormat === "html") {
				const scaledWidth = Math.max(1, Math.round(Number(width) * (scale / 100)));
				const scaledHeight = Math.max(1, Math.round(Number(height) * (scale / 100)));
				insertText = `<img src="${emoji.url}" title=":${emoji.name}:" class="emoji only-emoji" alt=":${emoji.name}:" loading="lazy" width="${scaledWidth}" height="${scaledHeight}" style="aspect-ratio: ${scaledWidth} / ${scaledHeight};"> `;
			} else insertText = `![${emoji.name}|${width}x${height},${scale}%](${emoji.url}) `;
			const selectionStart = textarea.selectionStart;
			const selectionEnd = textarea.selectionEnd;
			textarea.value = textarea.value.substring(0, selectionStart) + insertText + textarea.value.substring(selectionEnd, textarea.value.length);
			textarea.selectionStart = textarea.selectionEnd = selectionStart + insertText.length;
			textarea.focus();
			const inputEvent = new Event("input", {
				bubbles: true,
				cancelable: true
			});
			textarea.dispatchEvent(inputEvent);
		} else if (proseMirror) {
			const imgWidth = Number(width) || 500;
			const scaledWidth = Math.max(1, Math.round(imgWidth * (scale / 100)));
			const htmlContent = `<img src="${emoji.url}" alt="${emoji.name}" width="${width}" height="${height}" data-scale="${scale}" style="width: ${scaledWidth}px">`;
			try {
				const dataTransfer = new DataTransfer();
				dataTransfer.setData("text/html", htmlContent);
				const pasteEvent = new ClipboardEvent("paste", {
					clipboardData: dataTransfer,
					bubbles: true
				});
				proseMirror.dispatchEvent(pasteEvent);
			} catch (error) {
				try {
					document.execCommand("insertHTML", false, htmlContent);
				} catch (fallbackError) {
					console.error("无法向富文本编辑器中插入表情", fallbackError);
				}
			}
		}
	}
	var _hoverPreviewEl = null;
	function ensureHoverPreview() {
		if (_hoverPreviewEl && document.body.contains(_hoverPreviewEl)) return _hoverPreviewEl;
		_hoverPreviewEl = createEl("div", {
			className: "emoji-picker-hover-preview",
			style: "position:fixed;pointer-events:none;display:none;z-index:1000002;max-width:300px;max-height:300px;overflow:hidden;border-radius:6px;box-shadow:0 4px 12px rgba(0,0,0,0.25);background:#fff;padding:6px;"
		});
		const img = createEl("img", {
			className: "emoji-picker-hover-img",
			style: "display:block;max-width:100%;max-height:220px;object-fit:contain;"
		});
		const label = createEl("div", {
			className: "emoji-picker-hover-label",
			style: "font-size:12px;color:#333;margin-top:6px;text-align:center;"
		});
		_hoverPreviewEl.appendChild(img);
		_hoverPreviewEl.appendChild(label);
		document.body.appendChild(_hoverPreviewEl);
		return _hoverPreviewEl;
	}
	function createMobileEmojiPicker(groups) {
		const modal = createEl("div", {
			className: "modal d-modal fk-d-menu-modal emoji-picker-content",
			attrs: {
				"data-identifier": "emoji-picker",
				"data-keyboard": "false",
				"aria-modal": "true",
				role: "dialog"
			}
		});
		const modalContainerDiv = createEl("div", { className: "d-modal__container" });
		const modalBody = createEl("div", { className: "d-modal__body" });
		modalBody.tabIndex = -1;
		const emojiPickerDiv = createEl("div", { className: "emoji-picker" });
		const filterContainer = createEl("div", { className: "emoji-picker__filter-container" });
		const filterInputContainer = createEl("div", { className: "emoji-picker__filter filter-input-container" });
		const filterInput = createEl("input", {
			className: "filter-input",
			placeholder: "按表情符号名称搜索…",
			type: "text"
		});
		filterInputContainer.appendChild(filterInput);
		const closeButton = createEl("button", {
			className: "btn no-text btn-icon btn-transparent emoji-picker__close-btn",
			type: "button",
			innerHTML: `<svg class="fa d-icon d-icon-xmark svg-icon svg-string" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"><use href="#xmark"></use></svg>`
		});
		closeButton.addEventListener("click", () => {
			const container = modal.closest(".modal-container") || modal;
			if (container) container.remove();
		});
		filterContainer.appendChild(filterInputContainer);
		filterContainer.appendChild(closeButton);
		const content = createEl("div", { className: "emoji-picker__content" });
		const sectionsNav = createEl("div", { className: "emoji-picker__sections-nav" });
		const managementButton = createEl("button", {
			className: "btn no-text btn-flat emoji-picker__section-btn management-btn",
			attrs: {
				tabindex: "-1",
				style: "border-right: 1px solid #ddd;"
			},
			innerHTML: "⚙️",
			title: "管理表情 - 点击打开完整管理界面",
			type: "button"
		});
		managementButton.addEventListener("click", () => {
			__vitePreload(async () => {
				const { openManagementInterface: openManagementInterface$1 } = await Promise.resolve().then(() => (init_manager(), manager_exports));
				return { openManagementInterface: openManagementInterface$1 };
			}, void 0).then(({ openManagementInterface: openManagementInterface$1 }) => {
				openManagementInterface$1();
			});
		});
		sectionsNav.appendChild(managementButton);
		const settingsButton = createEl("button", {
			className: "btn no-text btn-flat emoji-picker__section-btn settings-btn",
			innerHTML: "🔧",
			title: "设置",
			attrs: {
				tabindex: "-1",
				style: "border-right: 1px solid #ddd;"
			},
			type: "button"
		});
		settingsButton.addEventListener("click", () => {
			__vitePreload(async () => {
				const { showSettingsModal: showSettingsModal$1 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
				return { showSettingsModal: showSettingsModal$1 };
			}, void 0).then(({ showSettingsModal: showSettingsModal$1 }) => {
				showSettingsModal$1();
			});
		});
		sectionsNav.appendChild(settingsButton);
		const scrollableContent = createEl("div", { className: "emoji-picker__scrollable-content" });
		const sections = createEl("div", {
			className: "emoji-picker__sections",
			attrs: { role: "button" }
		});
		let hoverPreviewEl = null;
		function ensureHoverPreview$1() {
			if (hoverPreviewEl && document.body.contains(hoverPreviewEl)) return hoverPreviewEl;
			hoverPreviewEl = createEl("div", {
				className: "emoji-picker-hover-preview",
				style: "position:fixed;pointer-events:none;display:none;z-index:1000002;max-width:300px;max-height:300px;overflow:hidden;border-radius:6px;box-shadow:0 4px 12px rgba(0,0,0,0.25);background:#fff;padding:6px;"
			});
			const img = createEl("img", {
				className: "emoji-picker-hover-img",
				style: "display:block;max-width:100%;max-height:220px;object-fit:contain;"
			});
			const label = createEl("div", {
				className: "emoji-picker-hover-label",
				style: "font-size:12px;color:#333;margin-top:6px;text-align:center;"
			});
			hoverPreviewEl.appendChild(img);
			hoverPreviewEl.appendChild(label);
			document.body.appendChild(hoverPreviewEl);
			return hoverPreviewEl;
		}
		groups.forEach((group, index) => {
			if (!group?.emojis?.length) return;
			const navButton = createEl("button", {
				className: `btn no-text btn-flat emoji-picker__section-btn ${index === 0 ? "active" : ""}`,
				attrs: {
					tabindex: "-1",
					"data-section": group.id,
					type: "button"
				}
			});
			const iconVal = group.icon || "📁";
			if (isImageUrl(iconVal)) {
				const img = createEl("img", {
					src: iconVal,
					alt: group.name || "",
					className: "emoji",
					style: "width: 18px; height: 18px; object-fit: contain;"
				});
				navButton.appendChild(img);
			} else navButton.textContent = String(iconVal);
			navButton.title = group.name;
			navButton.addEventListener("click", () => {
				sectionsNav.querySelectorAll(".emoji-picker__section-btn").forEach((btn) => btn.classList.remove("active"));
				navButton.classList.add("active");
				const target = sections.querySelector(`[data-section="${group.id}"]`);
				if (target) target.scrollIntoView({
					behavior: "smooth",
					block: "start"
				});
			});
			sectionsNav.appendChild(navButton);
			const section = createEl("div", {
				className: "emoji-picker__section",
				attrs: {
					"data-section": group.id,
					role: "region",
					"aria-label": group.name
				}
			});
			const titleContainer = createEl("div", { className: "emoji-picker__section-title-container" });
			const title = createEl("h2", {
				className: "emoji-picker__section-title",
				text: group.name
			});
			titleContainer.appendChild(title);
			const sectionEmojis = createEl("div", { className: "emoji-picker__section-emojis" });
			group.emojis.forEach((emoji) => {
				if (!emoji || typeof emoji !== "object" || !emoji.url || !emoji.name) return;
				const img = createEl("img", {
					src: emoji.url,
					alt: emoji.name,
					className: "emoji",
					title: `:${emoji.name}:`,
					style: "width: 32px; height: 32px; object-fit: contain;",
					attrs: {
						"data-emoji": emoji.name,
						tabindex: "0",
						loading: "lazy"
					}
				});
				(function bindHover(imgEl, emo) {
					const preview = ensureHoverPreview$1();
					const previewImg = preview.querySelector("img");
					const previewLabel = preview.querySelector(".emoji-picker-hover-label");
					function onEnter(e) {
						previewImg.src = emo.url;
						previewLabel.textContent = emo.name || "";
						preview.style.display = "block";
						move(e);
					}
					function move(e) {
						const pad = 12;
						const vw = window.innerWidth;
						const vh = window.innerHeight;
						const rect = preview.getBoundingClientRect();
						let left = e.clientX + pad;
						let top = e.clientY + pad;
						if (left + rect.width > vw) left = e.clientX - rect.width - pad;
						if (top + rect.height > vh) top = e.clientY - rect.height - pad;
						preview.style.left = left + "px";
						preview.style.top = top + "px";
					}
					function onLeave() {
						preview.style.display = "none";
					}
					imgEl.addEventListener("mouseenter", onEnter);
					imgEl.addEventListener("mousemove", move);
					imgEl.addEventListener("mouseleave", onLeave);
				})(img, emoji);
				img.addEventListener("click", () => {
					insertEmojiIntoEditor(emoji);
					const modalContainer = modal.closest(".modal-container");
					if (modalContainer) modalContainer.remove();
					else modal.remove();
				});
				img.addEventListener("keydown", (e) => {
					if (e.key === "Enter" || e.key === " ") {
						e.preventDefault();
						insertEmojiIntoEditor(emoji);
						const modalContainer = modal.closest(".modal-container");
						if (modalContainer) modalContainer.remove();
						else modal.remove();
					}
				});
				sectionEmojis.appendChild(img);
			});
			section.appendChild(titleContainer);
			section.appendChild(sectionEmojis);
			sections.appendChild(section);
		});
		filterInput.addEventListener("input", (e) => {
			const q = (e.target.value || "").toLowerCase();
			sections.querySelectorAll("img").forEach((img) => {
				const emojiName = (img.dataset.emoji || "").toLowerCase();
				img.style.display = q === "" || emojiName.includes(q) ? "" : "none";
			});
			sections.querySelectorAll(".emoji-picker__section").forEach((section) => {
				const visibleEmojis = section.querySelectorAll("img:not([style*=\"display: none\"])");
				section.style.display = visibleEmojis.length > 0 ? "" : "none";
			});
		});
		scrollableContent.appendChild(sections);
		content.appendChild(sectionsNav);
		content.appendChild(scrollableContent);
		emojiPickerDiv.appendChild(filterContainer);
		emojiPickerDiv.appendChild(content);
		modalBody.appendChild(emojiPickerDiv);
		modalContainerDiv.appendChild(modalBody);
		modal.appendChild(modalContainerDiv);
		return modal;
	}
	function createDesktopEmojiPicker(groups) {
		const picker = createEl("div", {
			className: "fk-d-menu -animated -expanded",
			style: "max-width: 400px; visibility: visible; z-index: 999999;",
			attrs: {
				"data-identifier": "emoji-picker",
				role: "dialog"
			}
		});
		const innerContent = createEl("div", { className: "fk-d-menu__inner-content" });
		const emojiPickerDiv = createEl("div", { className: "emoji-picker" });
		const filterContainer = createEl("div", { className: "emoji-picker__filter-container" });
		const filterDiv = createEl("div", { className: "emoji-picker__filter filter-input-container" });
		const searchInput = createEl("input", {
			className: "filter-input",
			placeholder: "按表情符号名称搜索…",
			type: "text"
		});
		filterDiv.appendChild(searchInput);
		filterContainer.appendChild(filterDiv);
		const content = createEl("div", { className: "emoji-picker__content" });
		const sectionsNav = createEl("div", { className: "emoji-picker__sections-nav" });
		const managementButton = createEl("button", {
			className: "btn no-text btn-flat emoji-picker__section-btn management-btn",
			attrs: {
				tabindex: "-1",
				style: "border-right: 1px solid #ddd;"
			},
			type: "button",
			innerHTML: "⚙️",
			title: "管理表情 - 点击打开完整管理界面"
		});
		managementButton.addEventListener("click", () => {
			__vitePreload(async () => {
				const { openManagementInterface: openManagementInterface$1 } = await Promise.resolve().then(() => (init_manager(), manager_exports));
				return { openManagementInterface: openManagementInterface$1 };
			}, void 0).then(({ openManagementInterface: openManagementInterface$1 }) => {
				openManagementInterface$1();
			});
		});
		sectionsNav.appendChild(managementButton);
		const settingsButton = createEl("button", {
			className: "btn no-text btn-flat emoji-picker__section-btn settings-btn",
			attrs: {
				tabindex: "-1",
				style: "border-right: 1px solid #ddd;"
			},
			type: "button",
			innerHTML: "🔧",
			title: "设置"
		});
		settingsButton.addEventListener("click", () => {
			__vitePreload(async () => {
				const { showSettingsModal: showSettingsModal$1 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
				return { showSettingsModal: showSettingsModal$1 };
			}, void 0).then(({ showSettingsModal: showSettingsModal$1 }) => {
				showSettingsModal$1();
			});
		});
		sectionsNav.appendChild(settingsButton);
		const scrollableContent = createEl("div", { className: "emoji-picker__scrollable-content" });
		const sections = createEl("div", {
			className: "emoji-picker__sections",
			attrs: { role: "button" }
		});
		groups.forEach((group, index) => {
			if (!group?.emojis?.length) return;
			const navButton = createEl("button", {
				className: `btn no-text btn-flat emoji-picker__section-btn ${index === 0 ? "active" : ""}`,
				attrs: {
					tabindex: "-1",
					"data-section": group.id
				},
				type: "button"
			});
			const iconVal = group.icon || "📁";
			if (isImageUrl(iconVal)) {
				const img = createEl("img", {
					src: iconVal,
					alt: group.name || "",
					className: "emoji-group-icon",
					style: "width: 18px; height: 18px; object-fit: contain;"
				});
				navButton.appendChild(img);
			} else navButton.textContent = String(iconVal);
			navButton.title = group.name;
			navButton.addEventListener("click", () => {
				sectionsNav.querySelectorAll(".emoji-picker__section-btn").forEach((btn) => btn.classList.remove("active"));
				navButton.classList.add("active");
				const target = sections.querySelector(`[data-section="${group.id}"]`);
				if (target) target.scrollIntoView({
					behavior: "smooth",
					block: "start"
				});
			});
			sectionsNav.appendChild(navButton);
			const section = createEl("div", {
				className: "emoji-picker__section",
				attrs: {
					"data-section": group.id,
					role: "region",
					"aria-label": group.name
				}
			});
			const titleContainer = createEl("div", { className: "emoji-picker__section-title-container" });
			const title = createEl("h2", {
				className: "emoji-picker__section-title",
				text: group.name
			});
			titleContainer.appendChild(title);
			const sectionEmojis = createEl("div", { className: "emoji-picker__section-emojis" });
			let added = 0;
			group.emojis.forEach((emoji) => {
				if (!emoji || typeof emoji !== "object" || !emoji.url || !emoji.name) return;
				const img = createEl("img", {
					width: "32px",
					height: "32px",
					className: "emoji",
					src: emoji.url,
					alt: emoji.name,
					title: `:${emoji.name}:`,
					attrs: {
						"data-emoji": emoji.name,
						tabindex: "0",
						loading: "lazy"
					}
				});
				(function bindHover(imgEl, emo) {
					const preview = ensureHoverPreview();
					const previewImg = preview.querySelector("img");
					const previewLabel = preview.querySelector(".emoji-picker-hover-label");
					function onEnter(e) {
						previewImg.src = emo.url;
						previewLabel.textContent = emo.name || "";
						preview.style.display = "block";
						move(e);
					}
					function move(e) {
						const pad = 12;
						const vw = window.innerWidth;
						const vh = window.innerHeight;
						const rect = preview.getBoundingClientRect();
						let left = e.clientX + pad;
						let top = e.clientY + pad;
						if (left + rect.width > vw) left = e.clientX - rect.width - pad;
						if (top + rect.height > vh) top = e.clientY - rect.height - pad;
						preview.style.left = left + "px";
						preview.style.top = top + "px";
					}
					function onLeave() {
						preview.style.display = "none";
					}
					imgEl.addEventListener("mouseenter", onEnter);
					imgEl.addEventListener("mousemove", move);
					imgEl.addEventListener("mouseleave", onLeave);
				})(img, emoji);
				img.addEventListener("click", () => {
					insertEmojiIntoEditor(emoji);
					picker.remove();
				});
				img.addEventListener("keydown", (e) => {
					if (e.key === "Enter" || e.key === " ") {
						e.preventDefault();
						insertEmojiIntoEditor(emoji);
						picker.remove();
					}
				});
				sectionEmojis.appendChild(img);
				added++;
			});
			if (added === 0) {
				const msg = createEl("div", {
					text: `${group.name} 组暂无有效表情`,
					style: "padding: 20px; text-align: center; color: #999;"
				});
				sectionEmojis.appendChild(msg);
			}
			section.appendChild(titleContainer);
			section.appendChild(sectionEmojis);
			sections.appendChild(section);
		});
		searchInput.addEventListener("input", (e) => {
			const q = (e.target.value || "").toLowerCase();
			sections.querySelectorAll("img").forEach((img) => {
				const emojiName = img.getAttribute("data-emoji")?.toLowerCase() || "";
				img.style.display = q === "" || emojiName.includes(q) ? "" : "none";
			});
			sections.querySelectorAll(".emoji-picker__section").forEach((section) => {
				const visibleEmojis = section.querySelectorAll("img:not([style*=\"none\"])");
				const titleContainer = section.querySelector(".emoji-picker__section-title-container");
				if (titleContainer) titleContainer.style.display = visibleEmojis.length > 0 ? "" : "none";
			});
		});
		scrollableContent.appendChild(sections);
		content.appendChild(sectionsNav);
		content.appendChild(scrollableContent);
		emojiPickerDiv.appendChild(filterContainer);
		emojiPickerDiv.appendChild(content);
		innerContent.appendChild(emojiPickerDiv);
		picker.appendChild(innerContent);
		return picker;
	}
	async function createEmojiPicker() {
		const groups = userscriptState.emojiGroups;
		const mobile = isMobileView();
		try {
			injectEmojiPickerStyles();
		} catch (e) {
			console.warn("injectEmojiPickerStyles failed", e);
		}
		if (mobile) return createMobileEmojiPicker(groups);
		else return createDesktopEmojiPicker(groups);
	}
	init_createEl();
	var toolbarSelectors = [".d-editor-button-bar[role=\"toolbar\"]", ".chat-composer__inner-container"];
	function findAllToolbars() {
		const toolbars = [];
		for (const selector of toolbarSelectors) {
			const elements = document.querySelectorAll(selector);
			toolbars.push(...Array.from(elements));
		}
		return toolbars;
	}
	var currentPicker = null;
	function closeCurrentPicker() {
		if (currentPicker) {
			currentPicker.remove();
			currentPicker = null;
		}
	}
	function injectEmojiButton(toolbar) {
		if (toolbar.querySelector(".emoji-extension-button")) return;
		const isChatComposer = toolbar.classList.contains("chat-composer__inner-container");
		const button = createEl("button", {
			className: "btn no-text btn-icon toolbar__button nacho-emoji-picker-button emoji-extension-button",
			title: "表情包",
			type: "button",
			innerHTML: "🐈‍⬛"
		});
		if (isChatComposer) {
			button.classList.add("fk-d-menu__trigger", "emoji-picker-trigger", "chat-composer-button", "btn-transparent", "-emoji");
			button.setAttribute("aria-expanded", "false");
			button.setAttribute("data-identifier", "emoji-picker");
			button.setAttribute("data-trigger", "");
		}
		button.addEventListener("click", async (e) => {
			e.stopPropagation();
			if (currentPicker) {
				closeCurrentPicker();
				return;
			}
			currentPicker = await createEmojiPicker();
			if (!currentPicker) return;
			document.body.appendChild(currentPicker);
			const buttonRect = button.getBoundingClientRect();
			if (currentPicker.classList.contains("modal") || currentPicker.className.includes("d-modal")) {
				currentPicker.style.position = "fixed";
				currentPicker.style.top = "0";
				currentPicker.style.left = "0";
				currentPicker.style.right = "0";
				currentPicker.style.bottom = "0";
				currentPicker.style.zIndex = "999999";
			} else {
				currentPicker.style.position = "fixed";
				const margin = 8;
				const vpWidth = window.innerWidth;
				const vpHeight = window.innerHeight;
				currentPicker.style.top = buttonRect.bottom + margin + "px";
				currentPicker.style.left = buttonRect.left + "px";
				const pickerRect = currentPicker.getBoundingClientRect();
				const spaceBelow = vpHeight - buttonRect.bottom;
				const neededHeight = pickerRect.height + margin;
				let top = buttonRect.bottom + margin;
				if (spaceBelow < neededHeight) top = Math.max(margin, buttonRect.top - pickerRect.height - margin);
				let left = buttonRect.left;
				if (left + pickerRect.width + margin > vpWidth) left = Math.max(margin, vpWidth - pickerRect.width - margin);
				if (left < margin) left = margin;
				currentPicker.style.top = top + "px";
				currentPicker.style.left = left + "px";
			}
			setTimeout(() => {
				const handleClick = (e$1) => {
					if (currentPicker && !currentPicker.contains(e$1.target) && e$1.target !== button) {
						closeCurrentPicker();
						document.removeEventListener("click", handleClick);
					}
				};
				document.addEventListener("click", handleClick);
			}, 100);
		});
		try {
			if (isChatComposer) {
				const existingEmojiTrigger = toolbar.querySelector(".emoji-picker-trigger:not(.emoji-extension-button)");
				if (existingEmojiTrigger) toolbar.insertBefore(button, existingEmojiTrigger);
				else toolbar.appendChild(button);
			} else toolbar.appendChild(button);
		} catch (error) {
			console.error("[Emoji Extension Userscript] Failed to inject button:", error);
		}
	}
	function attemptInjection() {
		const toolbars = findAllToolbars();
		let injectedCount = 0;
		toolbars.forEach((toolbar) => {
			if (!toolbar.querySelector(".emoji-extension-button")) {
				console.log("[Emoji Extension Userscript] Toolbar found, injecting button.");
				injectEmojiButton(toolbar);
				injectedCount++;
			}
		});
		return {
			injectedCount,
			totalToolbars: toolbars.length
		};
	}
	function startPeriodicInjection() {
		setInterval(() => {
			findAllToolbars().forEach((toolbar) => {
				if (!toolbar.querySelector(".emoji-extension-button")) {
					console.log("[Emoji Extension Userscript] New toolbar found, injecting button.");
					injectEmojiButton(toolbar);
				}
			});
		}, 3e4);
	}
	init_userscript_storage();
	init_state();
	async function initializeUserscriptData() {
		const data = await loadDataFromLocalStorageAsync().catch((err) => {
			console.warn("[Userscript] loadDataFromLocalStorageAsync failed, falling back to sync loader", err);
			return loadDataFromLocalStorage();
		});
		userscriptState.emojiGroups = data.emojiGroups || [];
		userscriptState.settings = data.settings || userscriptState.settings;
	}
	function shouldInjectEmoji() {
		if (document.querySelectorAll("meta[name*=\"discourse\"], meta[content*=\"discourse\"], meta[property*=\"discourse\"]").length > 0) {
			console.log("[Emoji Extension Userscript] Discourse detected via meta tags");
			return true;
		}
		const generatorMeta = document.querySelector("meta[name=\"generator\"]");
		if (generatorMeta) {
			const content = generatorMeta.getAttribute("content")?.toLowerCase() || "";
			if (content.includes("discourse") || content.includes("flarum") || content.includes("phpbb")) {
				console.log("[Emoji Extension Userscript] Forum platform detected via generator meta");
				return true;
			}
		}
		const hostname = window.location.hostname.toLowerCase();
		if ([
			"linux.do",
			"meta.discourse.org",
			"pixiv.net"
		].some((domain) => hostname.includes(domain))) {
			console.log("[Emoji Extension Userscript] Allowed domain detected:", hostname);
			return true;
		}
		if (document.querySelectorAll("textarea.d-editor-input, .ProseMirror.d-editor-input, .composer-input, .reply-area textarea").length > 0) {
			console.log("[Emoji Extension Userscript] Discussion editor detected");
			return true;
		}
		console.log("[Emoji Extension Userscript] No compatible platform detected");
		return false;
	}
	async function initializeEmojiFeature(maxAttempts = 10, delay = 1e3) {
		console.log("[Emoji Extension Userscript] Initializing...");
		await initializeUserscriptData();
		initOneClickAdd();
		let attempts = 0;
		function attemptToolbarInjection() {
			attempts++;
			const result = attemptInjection();
			if (result.injectedCount > 0 || result.totalToolbars > 0) {
				console.log(`[Emoji Extension Userscript] Injection successful: ${result.injectedCount} buttons injected into ${result.totalToolbars} toolbars`);
				return;
			}
			if (attempts < maxAttempts) {
				console.log(`[Emoji Extension Userscript] Toolbar not found, attempt ${attempts}/${maxAttempts}. Retrying in ${delay / 1e3}s.`);
				setTimeout(attemptToolbarInjection, delay);
			} else console.error("[Emoji Extension Userscript] Failed to find toolbar after multiple attempts.");
		}
		if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", attemptToolbarInjection);
		else attemptToolbarInjection();
		startPeriodicInjection();
	}
	if (shouldInjectEmoji()) {
		console.log("[Emoji Extension Userscript] Initializing emoji feature");
		initializeEmojiFeature();
	} else console.log("[Emoji Extension Userscript] Skipping injection - incompatible platform");
})();

})();