Linux do 表情扩展 (Emoji Extension) lite

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

当前为 2025-09-15 提交的版本,查看 最新版本

// ==UserScript==
// @name         Linux do 表情扩展 (Emoji Extension) lite
// @namespace    https://github.com/stevessr/bug-v3
// @version      1.0.2
// @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");
})();

})();