您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在 V2ex 评论中支持--自定义表情/快速上传图片
- // ==UserScript==
- // @name V2ex Better Comment
- // @namespace http://tampermonkey.net/1436051
- // @version 1.0
- // @description 在 V2ex 评论中支持--自定义表情/快速上传图片
- // @author Dogxi <dogxi.me>
- // @match https://www.v2ex.com/t/*
- // @match https://v2ex.com/t/*
- // @icon https://www.google.com/s2/favicons?sz=64&domain=v2ex.com
- // @grant GM_xmlhttpRequest
- // @grant GM_setValue
- // @grant GM_getValue
- // @connect api.imgur.com
- // @license MIT
- // ==/UserScript==
- (function () {
- "use strict";
- const IMGUR_CLIENT_ID_KEY = "imgurClientId";
- const EMOJI_CONFIG_KEY = "emojiConfig";
- let CLIENT_ID = GM_getValue(IMGUR_CLIENT_ID_KEY, null);
- // 默认表情配置
- const DEFAULT_EMOJI_CONFIG = {
- 颜文字: {
- type: "text",
- container: [
- { icon: "OωO", text: "呆" },
- { icon: "|´・ω・)ノ", text: "Hi" },
- { icon: "ヾ(≧∇≦*)ゝ", text: "开心" },
- { icon: "(☆ω☆)", text: "星星眼" },
- { icon: "(╯‵□′)╯︵┴─┴", text: "掀桌" },
- { icon: " ̄﹃ ̄", text: "流口水" },
- { icon: "(/ω\)", text: "捂脸" },
- { icon: "∠( ᐛ 」∠)_", text: "给跪" },
- { icon: "(๑•̀ㅁ•́ฅ)", text: "Hi" },
- { icon: "→_→", text: "斜眼" },
- { icon: "୧(๑•̀⌄•́๑)૭", text: "加油" },
- { icon: "٩(ˊᗜˋ*)و", text: "有木有WiFi" },
- { icon: "(ノ°ο°)ノ", text: "前方高能预警" },
- { icon: "(´இ皿இ`)", text: "我从未见过如此厚颜无耻之人" },
- { icon: "⌇●﹏●⌇", text: "吓死宝宝惹" },
- { icon: "(ฅ´ω`ฅ)", text: "已阅留爪" },
- { icon: "(╯°A°)╯︵○○○", text: "去吧大师球" },
- { icon: "φ( ̄∇ ̄o)", text: "太萌惹" },
- { icon: 'ヾ(´・ ・`。)ノ"', text: "咦咦咦" },
- { icon: "( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃", text: "气呼呼" },
- { icon: "(ó﹏ò。)", text: "我受到了惊吓" },
- { icon: "Σ(っ °Д °;)っ", text: "什么鬼" },
- { icon: '( ,,´・ω・)ノ"(´っω・`。)', text: "摸摸头" },
- { icon: "╮(╯▽╰)╭ ", text: "无奈" },
- { icon: "o(*////▽////*)q ", text: "脸红" },
- { icon: ">﹏<", text: "" },
- { icon: '( ๑´•ω•) "(ㆆᴗㆆ)', text: "" },
- ],
- },
- Emoji: {
- type: "emoji",
- container: [
- { icon: "😂", text: "" },
- { icon: "😀", text: "" },
- { icon: "😅", text: "" },
- { icon: "😊", text: "" },
- { icon: "🙂", text: "" },
- { icon: "🙃", text: "" },
- { icon: "😌", text: "" },
- { icon: "😍", text: "" },
- { icon: "😘 ", text: "" },
- { icon: "😜", text: "" },
- { icon: "😝", text: "" },
- { icon: "😏", text: "" },
- { icon: "😒", text: "" },
- { icon: "🙄", text: "" },
- { icon: "😳", text: "" },
- { icon: "😡", text: "" },
- { icon: "😔", text: "" },
- { icon: "😫", text: "" },
- { icon: "😱", text: "" },
- { icon: "😭", text: "" },
- { icon: "💩", text: "" },
- { icon: "👻", text: "" },
- { icon: "🙌", text: "" },
- { icon: "🖕", text: "" },
- { icon: "👍", text: "" },
- { icon: "👫", text: "" },
- { icon: "👬", text: "" },
- { icon: "👭", text: "" },
- { icon: "🌚", text: "" },
- { icon: "🌝", text: "" },
- { icon: "🙈", text: "" },
- { icon: "💊", text: "" },
- { icon: "😶", text: "" },
- { icon: "🙏", text: "" },
- { icon: "🍦", text: "" },
- { icon: "🍉", text: "" },
- { icon: "😣", text: "" },
- ],
- },
- 收藏表情: {
- type: "sticker",
- container: [
- { icon: "https://i.imgur.com/2by85Ui.jpeg", text: "小冒蜜" },
- { icon: "https://i.imgur.com/HCEidtT.jpeg", text: "老鼠玩手机" },
- { icon: "https://i.imgur.com/6W0VDcT.gif", text: "猫脸蹭墙" },
- ],
- },
- };
- const STYLE = `
- .imgur-upload-btn, .emoji-btn {
- background: none;
- border: none;
- color: #778087;
- cursor: pointer;
- font-size: 13px;
- padding: 0;
- margin-left: 15px;
- text-decoration: none;
- transition: color 0.2s ease;
- }
- .imgur-upload-btn:hover, .emoji-btn:hover {
- color: #4d5256;
- text-decoration: underline;
- }
- .hidden {
- display: none !important;
- }
- .imgur-upload-modal, .emoji-modal {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background-color: rgba(0, 0, 0, 0.5);
- display: flex;
- justify-content: center;
- align-items: center;
- z-index: 9999;
- }
- .imgur-upload-modal-content {
- background-color: #fff;
- padding: 20px;
- border-radius: 3px;
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
- max-width: 450px;
- width: 90%;
- position: relative;
- font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
- }
- .emoji-modal-content {
- background-color: #fff;
- border-radius: 8px;
- box-shadow: 0 2px 20px rgba(0, 0, 0, 0.15);
- width: 90%;
- max-width: 500px;
- max-height: 70vh;
- position: relative;
- font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
- display: flex;
- flex-direction: column;
- }
- .imgur-upload-modal-header, .emoji-modal-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 15px;
- padding-bottom: 10px;
- border-bottom: 1px solid #e2e2e2;
- }
- .emoji-modal-header {
- padding: 15px 20px 10px;
- margin-bottom: 0;
- }
- .imgur-upload-modal-title, .emoji-modal-title {
- font-size: 15px;
- font-weight: normal;
- color: #000;
- }
- .imgur-upload-modal-close, .emoji-modal-close {
- cursor: pointer;
- font-size: 18px;
- color: #ccc;
- width: 20px;
- height: 20px;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: color 0.2s ease;
- }
- .imgur-upload-modal-close:hover, .emoji-modal-close:hover {
- color: #999;
- }
- .emoji-content {
- padding: 15px 20px;
- flex: 1;
- overflow-y: auto;
- min-height: 200px;
- }
- .emoji-grid {
- display: flex;
- flex-wrap: wrap;
- gap: 6px;
- margin-bottom: 15px;
- }
- .emoji-item {
- display: flex;
- align-items: center;
- justify-content: center;
- min-width: 35px;
- height: 35px;
- padding: 4px 6px;
- cursor: pointer;
- border-radius: 4px;
- transition: background-color 0.2s ease;
- font-size: 16px;
- text-align: center;
- border: 1px solid transparent;
- background: none;
- position: relative;
- word-break: keep-all;
- white-space: nowrap;
- }
- .emoji-item:hover {
- background-color: #f0f0f0;
- border-color: #ddd;
- }
- .emoji-item.text {
- border: 1px solid #d0d0d0;
- background-color: #f8f8f8;
- font-family: "Microsoft YaHei", "PingFang SC", "Hiragino Sans GB", "Helvetica Neue", Helvetica, Arial, sans-serif;
- box-shadow: 0 1px 2px rgba(0,0,0,0.05);
- }
- .emoji-item.text:hover {
- background-color: #eeeeee;
- border-color: #bbb;
- box-shadow: 0 1px 3px rgba(0,0,0,0.1);
- }
- .emoji-item.image {
- background-size: contain;
- background-repeat: no-repeat;
- background-position: center;
- width: 35px;
- min-width: 35px;
- }
- .emoji-item.image.large {
- width: 60px;
- min-width: 60px;
- height: 60px;
- }
- .emoji-item .delete-btn {
- position: absolute;
- top: -4px;
- right: -4px;
- width: 14px;
- height: 14px;
- background: #ff4444;
- color: white;
- border: none;
- border-radius: 50%;
- font-size: 9px;
- cursor: pointer;
- display: none;
- align-items: center;
- justify-content: center;
- line-height: 1;
- }
- .emoji-item:hover .delete-btn {
- display: flex;
- }
- .emoji-tabs {
- display: flex;
- border-top: 1px solid #e2e2e2;
- background-color: #f9f9f9;
- overflow-x: auto;
- }
- .emoji-tab {
- flex: 1;
- padding: 12px 16px;
- text-align: center;
- cursor: pointer;
- font-size: 12px;
- color: #666;
- border: none;
- background: none;
- transition: all 0.2s ease;
- border-right: 1px solid #e2e2e2;
- white-space: nowrap;
- }
- .emoji-tab:last-child {
- border-right: none;
- }
- .emoji-tab.active {
- background-color: #fff;
- color: #333;
- border-top: 2px solid #778087;
- }
- .emoji-tab:hover:not(.active) {
- background-color: #f0f0f0;
- }
- .emoji-config-panel {
- padding: 20px;
- height: calc(100% - 0px);
- box-sizing: border-box;
- display: flex;
- flex-direction: column;
- width: 100%;
- }
- .emoji-config-title {
- font-size: 14px;
- font-weight: bold;
- margin-bottom: 10px;
- color: #333;
- }
- .emoji-config-textarea {
- width: 100%;
- flex: 1;
- min-height: 280px;
- padding: 10px;
- border: 1px solid #ccc;
- border-radius: 4px;
- font-family: "Consolas", "Monaco", "Courier New", monospace;
- font-size: 12px;
- resize: vertical;
- margin-bottom: 15px;
- box-sizing: border-box;
- line-height: 1.4;
- }
- .emoji-config-actions {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding-top: 15px;
- border-top: 1px solid #e2e2e2;
- margin-top: auto;
- }
- .emoji-config-btn {
- background: none;
- border: none;
- color: #778087;
- cursor: pointer;
- font-size: 12px;
- padding: 0;
- transition: color 0.2s ease;
- text-decoration: none;
- }
- .emoji-config-btn:hover {
- color: #4d5256;
- text-decoration: underline;
- }
- .imgur-upload-dropzone {
- border: 1px dashed #ccc;
- padding: 25px;
- text-align: center;
- margin-bottom: 15px;
- cursor: pointer;
- border-radius: 3px;
- transition: border-color 0.2s ease;
- font-size: 13px;
- color: #666;
- }
- .imgur-upload-dropzone:hover {
- border-color: #999;
- }
- .imgur-upload-dropzone.dragover {
- border-color: #778087;
- background-color: #f9f9f9;
- }
- .imgur-upload-preview {
- margin-top: 10px;
- max-width: 100%;
- max-height: 150px;
- border-radius: 2px;
- }
- .imgur-upload-actions {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-top: 15px;
- padding-top: 10px;
- border-top: 1px solid #e2e2e2;
- }
- .imgur-upload-config-btn {
- background: none;
- border: none;
- color: #778087;
- cursor: pointer;
- font-size: 12px;
- padding: 0;
- }
- .imgur-upload-config-btn:hover {
- color: #4d5256;
- text-decoration: underline;
- }
- .imgur-upload-submit-btn {
- background-color: #f5f5f5;
- border: 1px solid #ccc;
- border-radius: 3px;
- color: #333;
- cursor: pointer;
- font-size: 12px;
- padding: 6px 12px;
- transition: all 0.2s ease;
- }
- .imgur-upload-submit-btn:hover {
- background-color: #e8e8e8;
- }
- .imgur-upload-submit-btn:disabled {
- background-color: #f9f9f9;
- color: #ccc;
- cursor: not-allowed;
- }
- .imgur-upload-config-panel {
- margin-top: 10px;
- padding: 10px;
- background-color: #f9f9f9;
- border-radius: 3px;
- border: 1px solid #e2e2e2;
- }
- .imgur-upload-config-row {
- display: flex;
- align-items: center;
- margin-bottom: 8px;
- }
- .imgur-upload-config-row:last-child {
- margin-bottom: 0;
- }
- .imgur-upload-config-label {
- font-size: 12px;
- color: #666;
- width: 70px;
- flex-shrink: 0;
- }
- .imgur-upload-config-input {
- flex: 1;
- padding: 3px 6px;
- border: 1px solid #ccc;
- border-radius: 2px;
- font-size: 12px;
- }
- .imgur-upload-config-save {
- background-color: #f5f5f5;
- border: 1px solid #ccc;
- border-radius: 2px;
- color: #333;
- cursor: pointer;
- font-size: 11px;
- margin-left: 6px;
- padding: 3px 8px;
- }
- .imgur-upload-config-save:hover {
- background-color: #e8e8e8;
- }
- .imgur-upload-modal-status {
- color: #666;
- font-size: 12px;
- text-align: center;
- }
- .imgur-upload-modal-status.success {
- color: #5cb85c;
- }
- .imgur-upload-modal-status.error {
- color: #d9534f;
- }
- .save-emoji-checkbox {
- display: flex;
- align-items: center;
- gap: 5px;
- font-size: 12px;
- color: #666;
- }
- .save-emoji-checkbox input[type="checkbox"] {
- margin: 0;
- }
- `;
- // 获取表情配置
- function getEmojiConfig() {
- try {
- const savedConfig = GM_getValue(EMOJI_CONFIG_KEY, null);
- if (!savedConfig) {
- return DEFAULT_EMOJI_CONFIG;
- }
- // 如果savedConfig已经是对象,直接返回
- if (typeof savedConfig === "object") {
- return savedConfig;
- }
- // 如果是字符串,尝试解析
- if (typeof savedConfig === "string") {
- return JSON.parse(savedConfig);
- }
- // 其他情况返回默认配置
- return DEFAULT_EMOJI_CONFIG;
- } catch (error) {
- console.error("解析表情配置失败,使用默认配置:", error);
- // 清除错误的配置并使用默认配置
- GM_setValue(EMOJI_CONFIG_KEY, JSON.stringify(DEFAULT_EMOJI_CONFIG));
- return DEFAULT_EMOJI_CONFIG;
- }
- }
- // 保存表情配置
- function saveEmojiConfig(config) {
- try {
- // 确保保存的是JSON字符串
- const configString =
- typeof config === "string" ? config : JSON.stringify(config);
- GM_setValue(EMOJI_CONFIG_KEY, configString);
- } catch (error) {
- console.error("保存表情配置失败:", error);
- alert("保存配置失败,请检查配置格式");
- }
- }
- // 添加样式到页面
- function addStyle() {
- const styleElement = document.createElement("style");
- styleElement.textContent = STYLE;
- document.head.appendChild(styleElement);
- }
- // 创建上传弹窗
- function createUploadModal(textareaElement) {
- const modal = document.createElement("div");
- modal.className = "imgur-upload-modal";
- const content = document.createElement("div");
- content.className = "imgur-upload-modal-content";
- content.innerHTML = `
- <div class="imgur-upload-modal-header">
- <div class="imgur-upload-modal-title">上传图片</div>
- <div class="imgur-upload-modal-close">×</div>
- </div>
- <div class="imgur-upload-dropzone">
- <div>点击选择图片或拖拽图片到此处</div>
- <div style="font-size: 11px; color: #999; margin-top: 5px;">支持 JPG, PNG, GIF 格式</div>
- </div>
- <div class="imgur-upload-actions">
- <button class="imgur-upload-config-btn">⚙️ 配置</button>
- <button class="imgur-upload-submit-btn" disabled>确认上传</button>
- </div>
- <div class="imgur-upload-config-panel hidden">
- <div class="imgur-upload-config-row">
- <div class="imgur-upload-config-label">Imgur ID:</div>
- <input type="text" class="imgur-upload-config-input" placeholder="请输入 Imgur Client ID" value="${
- CLIENT_ID || ""
- }">
- <button class="imgur-upload-config-save">保存</button>
- </div>
- <div style="font-size: 11px; color: #666; margin-top: 8px;">
- 在 <a href="https://api.imgur.com/oauth2/addclient" target="_blank">https://api.imgur.com/oauth2/addclient</a> 注册(不可用)获取(无回调)
- </div>
- </div>
- `;
- modal.appendChild(content);
- document.body.appendChild(modal);
- setupModalEvents(modal, textareaElement);
- return modal;
- }
- // 创建表情选择器弹窗
- function createEmojiModal(textareaElement) {
- const modal = document.createElement("div");
- modal.className = "emoji-modal";
- const content = document.createElement("div");
- content.className = "emoji-modal-content";
- const emojiConfig = getEmojiConfig();
- const categories = Object.keys(emojiConfig);
- content.innerHTML = `
- <div class="emoji-modal-header">
- <div class="emoji-modal-title">选择表情</div>
- <div style="display: flex; align-items: center; gap: 15px;">
- <a class="emoji-config-btn" id="emoji-config-btn" href="javascript:void(0);">⚙️ 配置</a>
- <div class="emoji-modal-close">×</div>
- </div>
- </div>
- <div class="emoji-content">
- <div class="emoji-grid" id="emoji-grid"></div>
- </div>
- <div class="emoji-tabs" id="emoji-tabs"></div>
- `;
- modal.appendChild(content);
- document.body.appendChild(modal);
- // 预渲染所有分类内容
- preRenderCategories(emojiConfig);
- // 创建分栏
- const tabsContainer = content.querySelector("#emoji-tabs");
- const fragment = document.createDocumentFragment();
- categories.forEach((category, index) => {
- const tab = document.createElement("button");
- tab.className = `emoji-tab ${index === 0 ? "active" : ""}`;
- tab.textContent = category;
- tab.dataset.category = category;
- fragment.appendChild(tab);
- });
- tabsContainer.appendChild(fragment);
- // 使用事件委托处理分栏点击
- tabsContainer.addEventListener("click", (e) => {
- if (e.target.classList.contains("emoji-tab")) {
- const category = e.target.dataset.category;
- switchEmojiCategoryOptimized(category);
- }
- });
- // 显示第一个分类的表情
- if (categories.length > 0) {
- switchEmojiCategoryOptimized(categories[0]);
- }
- // 设置事件监听
- setupEmojiModalEvents(modal, textareaElement);
- return modal;
- }
- // 全局缓存已渲染的分类内容
- let renderedCategories = new Map();
- let currentActiveCategory = null;
- // 预渲染所有分类内容
- function preRenderCategories(emojiConfig) {
- renderedCategories.clear();
- Object.keys(emojiConfig).forEach((category) => {
- const categoryData = emojiConfig[category];
- if (categoryData && categoryData.container) {
- const container = document.createElement("div");
- categoryData.container.forEach((item, index) => {
- const emojiEl = document.createElement("button");
- emojiEl.className = "emoji-item";
- emojiEl.dataset.emoji = item.icon;
- emojiEl.dataset.category = category;
- emojiEl.dataset.index = index;
- if (
- categoryData.type === "image" ||
- categoryData.type === "sticker"
- ) {
- emojiEl.className += " image";
- // 检查是否是imgur链接,如果是则使用大图标
- if (
- item.icon.includes("imgur.com") ||
- item.icon.includes("i.imgur.com")
- ) {
- emojiEl.className += " large";
- }
- emojiEl.style.backgroundImage = `url(${item.icon})`;
- emojiEl.title = item.text || item.icon;
- // 为自定义分组添加删除按钮
- if (categoryData.editable !== false) {
- const deleteBtn = document.createElement("button");
- deleteBtn.className = "delete-btn";
- deleteBtn.innerHTML = "×";
- deleteBtn.dataset.action = "delete";
- emojiEl.appendChild(deleteBtn);
- }
- } else {
- emojiEl.textContent = item.icon;
- emojiEl.title = item.text || item.icon;
- // 为颜文字添加特殊样式
- if (
- categoryData.type === "text" ||
- categoryData.type === "emoticon"
- ) {
- emojiEl.className += " text";
- }
- }
- container.appendChild(emojiEl);
- });
- renderedCategories.set(category, container);
- }
- });
- }
- // 优化的切换表情分类函数
- function switchEmojiCategoryOptimized(category) {
- if (currentActiveCategory === category) return;
- const grid = document.getElementById("emoji-grid");
- const tabs = document.querySelectorAll(".emoji-tab");
- if (!grid) {
- console.error("找不到表情网格元素");
- return;
- }
- // 更新分栏状态
- tabs.forEach((tab) => {
- tab.classList.toggle("active", tab.dataset.category === category);
- });
- // 清空网格并插入预渲染的内容
- grid.innerHTML = "";
- const categoryContainer = renderedCategories.get(category);
- if (categoryContainer) {
- // 克隆容器内容
- const clonedContainer = categoryContainer.cloneNode(true);
- // 将克隆的子元素添加到网格中
- while (clonedContainer.firstChild) {
- grid.appendChild(clonedContainer.firstChild);
- }
- }
- currentActiveCategory = category;
- }
- // 使用事件委托处理表情点击和删除
- function setupEmojiGridEvents() {
- const grid = document.getElementById("emoji-grid");
- if (!grid) return;
- grid.addEventListener("click", (e) => {
- const target = e.target.closest(".emoji-item");
- if (!target) return;
- const deleteBtn = e.target.closest(".delete-btn");
- if (deleteBtn) {
- e.stopPropagation();
- const category = target.dataset.category;
- const index = parseInt(target.dataset.index);
- deleteEmojiOptimized(category, index);
- } else {
- const emoji = target.dataset.emoji;
- insertEmoji(emoji);
- }
- });
- }
- // 优化的删除表情函数
- function deleteEmojiOptimized(category, index) {
- if (confirm("确定要删除这个表情吗?")) {
- try {
- const config = getEmojiConfig();
- if (config[category] && config[category].container) {
- config[category].container.splice(index, 1);
- saveEmojiConfig(config);
- // 重新预渲染并刷新当前分类
- preRenderCategories(config);
- switchEmojiCategoryOptimized(category);
- }
- } catch (error) {
- console.error("删除表情失败:", error);
- alert("删除表情失败: " + error.message);
- }
- }
- }
- // 显示配置页面
- function showEmojiConfig() {
- const content = document.querySelector(".emoji-content");
- const tabs = document.querySelectorAll(".emoji-tab");
- const tabsContainer = document.querySelector("#emoji-tabs");
- // 清除所有分栏的激活状态并隐藏分栏容器
- tabs.forEach((tab) => tab.classList.remove("active"));
- if (tabsContainer) {
- tabsContainer.style.display = "none";
- }
- const currentConfig = getEmojiConfig();
- content.innerHTML = `
- <div class="emoji-config-panel">
- <div class="emoji-config-title">表情配置</div>
- <textarea class="emoji-config-textarea" id="emoji-config-textarea"
- placeholder="请输入 JSON 格式的表情配置...">${JSON.stringify(
- currentConfig,
- null,
- 2
- )}</textarea>
- <div class="emoji-config-actions">
- <a href="https://owo.dogxi.me/" target="_blank" style="background-color: #f5f5f5; border: 1px solid #ccc; border-radius: 3px; color: #333; cursor: pointer; font-size: 12px; padding: 6px 12px; transition: all 0.2s ease; white-space: nowrap; text-decoration: none;">更多配置</a>
- <div>
- <button style="background-color: #f5f5f5; border: 1px solid #ccc; border-radius: 3px; color: #333; cursor: pointer; font-size: 12px; padding: 6px 12px; transition: all 0.2s ease; white-space: nowrap;" id="cancel-config-btn">取消</button>
- <button style="background-color: #778087; color: white; border: 1px solid #778087; border-radius: 3px; cursor: pointer; font-size: 12px; padding: 6px 12px; transition: all 0.2s ease; white-space: nowrap; margin-left: 10px;" id="save-config-btn">保存配置</button>
- </div>
- </div>
- </div>
- `;
- // 添加事件监听
- setupEmojiConfigEvents();
- }
- // 设置表情配置页面事件
- function setupEmojiConfigEvents() {
- const cancelBtn = document.getElementById("cancel-config-btn");
- const saveBtn = document.getElementById("save-config-btn");
- if (cancelBtn) {
- cancelBtn.addEventListener("click", function () {
- const modal = document.querySelector(".emoji-modal");
- if (modal) {
- modal.remove();
- }
- });
- }
- if (saveBtn) {
- saveBtn.addEventListener("click", function () {
- const textarea = document.getElementById("emoji-config-textarea");
- try {
- const config = JSON.parse(textarea.value);
- saveEmojiConfig(config);
- alert("配置保存成功!");
- // 重新创建表情弹窗以应用新配置
- const modal = document.querySelector(".emoji-modal");
- if (modal) {
- const textareaElement = document.getElementById("reply_content");
- modal.remove();
- if (textareaElement) {
- createEmojiModal(textareaElement);
- }
- }
- } catch (e) {
- console.error("配置保存失败:", e);
- alert("JSON 格式错误,请检查配置:\n" + e.message);
- }
- });
- }
- }
- // 设置表情弹窗事件监听
- function setupEmojiModalEvents(modal, textareaElement) {
- const closeBtn = modal.querySelector(".emoji-modal-close");
- const configBtn = modal.querySelector("#emoji-config-btn");
- function closeModal() {
- if (document.body.contains(modal)) {
- // 清理缓存
- renderedCategories.clear();
- currentActiveCategory = null;
- document.body.removeChild(modal);
- }
- }
- if (closeBtn) {
- closeBtn.addEventListener("click", closeModal);
- }
- if (configBtn) {
- configBtn.addEventListener("click", showEmojiConfig);
- }
- // 设置表情网格事件委托
- setupEmojiGridEvents();
- modal.addEventListener("click", function (e) {
- if (e.target === modal) closeModal();
- });
- // ESC键关闭
- const escHandler = function (e) {
- if (e.key === "Escape") {
- closeModal();
- document.removeEventListener("keydown", escHandler);
- }
- };
- document.addEventListener("keydown", escHandler);
- }
- // 设置弹窗事件监听
- function setupModalEvents(modal, textareaElement) {
- const closeBtn = modal.querySelector(".imgur-upload-modal-close");
- const dropzone = modal.querySelector(".imgur-upload-dropzone");
- const configBtn = modal.querySelector(".imgur-upload-config-btn");
- const configPanel = modal.querySelector(".imgur-upload-config-panel");
- const configInput = modal.querySelector(".imgur-upload-config-input");
- const configSave = modal.querySelector(".imgur-upload-config-save");
- const submitBtn = modal.querySelector(".imgur-upload-submit-btn");
- let selectedFile = null;
- function closeModal() {
- if (document.body.contains(modal)) {
- document.body.removeChild(modal);
- }
- }
- closeBtn.addEventListener("click", closeModal);
- modal.addEventListener("click", function (e) {
- if (e.target === modal) closeModal();
- });
- configBtn.addEventListener("click", function () {
- configPanel.classList.toggle("hidden");
- });
- configSave.addEventListener("click", function () {
- const newClientId = configInput.value.trim();
- if (newClientId) {
- GM_setValue(IMGUR_CLIENT_ID_KEY, newClientId);
- CLIENT_ID = newClientId;
- configPanel.classList.add("hidden");
- showStatusInModal(modal, "配置已保存", "success");
- }
- });
- const fileInput = document.createElement("input");
- fileInput.type = "file";
- fileInput.accept = "image/*";
- fileInput.style.display = "none";
- modal.appendChild(fileInput);
- dropzone.addEventListener("click", () => fileInput.click());
- fileInput.addEventListener("change", function (e) {
- handleFileSelect(e.target.files[0]);
- });
- dropzone.addEventListener("dragover", function (e) {
- e.preventDefault();
- dropzone.classList.add("dragover");
- });
- dropzone.addEventListener("dragleave", function (e) {
- e.preventDefault();
- dropzone.classList.remove("dragover");
- });
- dropzone.addEventListener("drop", function (e) {
- e.preventDefault();
- dropzone.classList.remove("dragover");
- const files = e.dataTransfer.files;
- if (files.length > 0) {
- handleFileSelect(files[0]);
- }
- });
- // 处理文件选择
- function handleFileSelect(file) {
- if (!file || !file.type.match(/image\/.*/)) {
- showStatusInModal(modal, "请选择图片文件", "error");
- return;
- }
- selectedFile = file;
- const reader = new FileReader();
- reader.onload = function (e) {
- const preview = modal.querySelector(".imgur-upload-preview");
- if (preview) preview.remove();
- const img = document.createElement("img");
- img.src = e.target.result;
- img.className = "imgur-upload-preview";
- dropzone.appendChild(img);
- submitBtn.disabled = false;
- dropzone.querySelector("div").textContent = "已选择: " + file.name;
- };
- reader.readAsDataURL(file);
- }
- submitBtn.addEventListener("click", function () {
- if (!selectedFile) return;
- if (!CLIENT_ID) {
- showStatusInModal(modal, "请先配置 Imgur Client ID", "error");
- configPanel.classList.remove("hidden");
- return;
- }
- submitBtn.disabled = true;
- submitBtn.textContent = "上传中...";
- uploadToImgur(selectedFile, textareaElement, modal);
- });
- }
- // 在弹窗中显示状态信息
- function showStatusInModal(modal, message, type) {
- let statusEl = modal.querySelector(".imgur-upload-modal-status");
- if (!statusEl) {
- statusEl = document.createElement("div");
- statusEl.className = "imgur-upload-modal-status";
- statusEl.style.cssText =
- "margin-top: 10px; font-size: 12px; text-align: center;";
- modal.querySelector(".imgur-upload-modal-content").appendChild(statusEl);
- }
- statusEl.textContent = message;
- statusEl.className = "imgur-upload-modal-status " + (type || "");
- if (type === "success") {
- setTimeout(() => (statusEl.textContent = ""), 3000);
- }
- }
- // 修改上传成功处理逻辑
- function handleUploadSuccess(imageUrl, fileName, modal) {
- const textarea = document.getElementById("reply_content");
- // 插入链接到文本框
- insertLinkIntoTextarea(textarea, imageUrl, fileName);
- showStatusInModal(modal, "上传成功!", "success");
- setTimeout(() => {
- if (document.body.contains(modal)) {
- document.body.removeChild(modal);
- }
- }, 1500);
- }
- // 修改uploadToImgur函数中的成功处理部分
- function uploadToImgur(file, textareaElement, modal) {
- if (!file.type.match(/image\/.*/)) {
- showStatusInModal(modal, "请选择图片文件", "error");
- const submitBtn = modal.querySelector(".imgur-upload-submit-btn");
- submitBtn.disabled = false;
- submitBtn.textContent = "确认上传";
- return;
- }
- const formData = new FormData();
- formData.append("image", file);
- GM_xmlhttpRequest({
- method: "POST",
- url: "https://api.imgur.com/3/image",
- headers: {
- Authorization: "Client-ID " + CLIENT_ID,
- },
- data: formData,
- responseType: "json",
- onload: function (response) {
- const submitBtn = modal.querySelector(".imgur-upload-submit-btn");
- try {
- let responseData;
- if (typeof response.response === "string") {
- responseData = JSON.parse(response.response);
- } else {
- responseData = response.response;
- }
- if (response.status === 200 && responseData && responseData.success) {
- const imageUrl = responseData.data.link;
- handleUploadSuccess(imageUrl, file.name, modal);
- } else {
- let errorMessage = "";
- if (response.status === 400) {
- if (
- responseData &&
- responseData.data &&
- responseData.data.error
- ) {
- if (
- responseData.data.error === "These actions are forbidden."
- ) {
- errorMessage = "Client ID 无效或已被禁用,请检查配置";
- } else {
- errorMessage = responseData.data.error;
- }
- } else {
- errorMessage = "Client ID 配置错误";
- }
- } else if (response.status === 403) {
- errorMessage = "访问被拒绝,请检查 Client ID 权限";
- } else if (response.status === 429) {
- errorMessage = "请求过于频繁,请稍后再试";
- } else {
- errorMessage = `上传失败 (${response.status})`;
- }
- console.error("Imgur 上传错误:", response);
- showStatusInModal(modal, errorMessage, "error");
- if (response.status === 400 || response.status === 403) {
- const configPanel = modal.querySelector(
- ".imgur-upload-config-panel"
- );
- configPanel.classList.remove("hidden");
- }
- submitBtn.disabled = false;
- submitBtn.textContent = "确认上传";
- }
- } catch (e) {
- console.error("解析响应失败:", e, response);
- showStatusInModal(modal, "响应解析失败,请重试", "error");
- submitBtn.disabled = false;
- submitBtn.textContent = "确认上传";
- }
- },
- onerror: function (error) {
- console.error("GM_xmlhttpRequest 错误:", error);
- showStatusInModal(modal, "网络请求失败,请检查连接", "error");
- const submitBtn = modal.querySelector(".imgur-upload-submit-btn");
- submitBtn.disabled = false;
- submitBtn.textContent = "确认上传";
- },
- ontimeout: function () {
- console.error("Imgur 上传超时");
- showStatusInModal(modal, "上传超时,请重试", "error");
- const submitBtn = modal.querySelector(".imgur-upload-submit-btn");
- submitBtn.disabled = false;
- submitBtn.textContent = "确认上传";
- },
- });
- }
- // 将图片链接插入到文本框
- function insertLinkIntoTextarea(textareaElement, imageUrl, fileName) {
- const altText = fileName ? fileName.split(".")[0] : "image";
- const textToInsert = imageUrl;
- const currentValue = textareaElement.value;
- const selectionStart = textareaElement.selectionStart;
- const selectionEnd = textareaElement.selectionEnd;
- const newText =
- currentValue.substring(0, selectionStart) +
- textToInsert +
- currentValue.substring(selectionEnd);
- textareaElement.value = newText;
- const newCursorPosition = selectionStart + textToInsert.length;
- textareaElement.selectionStart = newCursorPosition;
- textareaElement.selectionEnd = newCursorPosition;
- textareaElement.focus();
- textareaElement.dispatchEvent(
- new Event("input", { bubbles: true, cancelable: true })
- );
- }
- // 在页面头部添加表情和上传按钮
- function addUploadButtonToHeader() {
- const replyBox = document.getElementById("reply-box");
- if (!replyBox) return;
- const headerCell = replyBox.querySelector(".cell.flex-one-row");
- if (!headerCell) return;
- if (headerCell.querySelector(".imgur-upload-btn")) return;
- const leftDiv = headerCell.querySelector("div:first-child");
- if (leftDiv) {
- // 添加表情按钮
- const emojiBtn = document.createElement("a");
- emojiBtn.className = "emoji-btn";
- emojiBtn.textContent = "表情";
- emojiBtn.href = "javascript:void(0);";
- emojiBtn.title = "选择表情";
- emojiBtn.style.marginLeft = "10px";
- leftDiv.appendChild(emojiBtn);
- emojiBtn.addEventListener("click", function (e) {
- e.preventDefault();
- // console.log("表情按钮被点击"); // 调试日志
- const textarea = document.getElementById("reply_content");
- if (textarea) {
- // console.log("创建表情弹窗"); // 调试日志
- createEmojiModal(textarea);
- } else {
- console.error("找不到回复文本框");
- }
- });
- // 添加上传按钮
- const uploadBtn = document.createElement("a");
- uploadBtn.className = "imgur-upload-btn";
- uploadBtn.textContent = "上传";
- uploadBtn.href = "javascript:void(0);";
- uploadBtn.title = "上传图片";
- uploadBtn.style.marginLeft = "10px";
- leftDiv.appendChild(uploadBtn);
- uploadBtn.addEventListener("click", function (e) {
- e.preventDefault();
- // console.log("上传按钮被点击"); // 调试日志
- const textarea = document.getElementById("reply_content");
- if (textarea) {
- // console.log("创建上传弹窗"); // 调试日志
- createUploadModal(textarea);
- }
- });
- }
- }
- // 查找并添加上传按钮
- function findTextareasAndAddButtons() {
- addUploadButtonToHeader();
- setupMutationObserver();
- }
- // 监听DOM变化
- function setupMutationObserver() {
- const observer = new MutationObserver(function (mutations) {
- let shouldCheck = false;
- mutations.forEach(function (mutation) {
- mutation.addedNodes.forEach(function (node) {
- if (
- node.nodeType === Node.ELEMENT_NODE &&
- (node.id === "reply-box" || node.querySelector("#reply-box"))
- ) {
- shouldCheck = true;
- }
- });
- });
- if (shouldCheck) {
- setTimeout(addUploadButtonToHeader, 100);
- }
- });
- observer.observe(document.body, {
- childList: true,
- subtree: true,
- });
- }
- // 初始化脚本
- function init() {
- addStyle();
- setTimeout(findTextareasAndAddButtons, 100);
- }
- if (document.readyState === "loading") {
- document.addEventListener("DOMContentLoaded", init);
- } else {
- init();
- }
- // 插入表情到文本框
- function insertEmoji(emoji) {
- const textarea = document.getElementById("reply_content");
- if (!textarea) {
- console.error("找不到回复文本框");
- return;
- }
- const textToInsert = emoji;
- const currentValue = textarea.value;
- const selectionStart = textarea.selectionStart;
- const selectionEnd = textarea.selectionEnd;
- const newText =
- currentValue.substring(0, selectionStart) +
- textToInsert +
- currentValue.substring(selectionEnd);
- textarea.value = newText;
- const newCursorPosition = selectionStart + textToInsert.length;
- textarea.selectionStart = newCursorPosition;
- textarea.selectionEnd = newCursorPosition;
- textarea.focus();
- textarea.dispatchEvent(
- new Event("input", { bubbles: true, cancelable: true })
- );
- // 关闭弹窗
- const modal = document.querySelector(".emoji-modal");
- if (modal) {
- renderedCategories.clear();
- currentActiveCategory = null;
- modal.remove();
- }
- }
- })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址