// ==UserScript==
// @name 即梦输入框优化
// @namespace http://tampermonkey.net/
// @version 0.5
// @description 调整即梦AI绘图页面的提示词输入框高度,并添加格式化按钮去除特殊字符。
// @author wlct
// @match https://jimeng.jianying.com/ai-tool/image/generate
// @grant GM_addStyle
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
// --- 配置 ---
const INPUT_SELECTOR = 'div[contenteditable="true"][role="textbox"].tiptap.ProseMirror'; // 提示词输入框的选择器
const INPUT_CONTAINER_SELECTOR = '.tiptap-container'; // 输入框外层容器
const TARGET_HEIGHT = '280px'; // 目标高度
const BUTTON_TEXT = '格式化提示词'; // 按钮文字
const BUTTON_ID = 'jimeng-format-prompt-button'; // 按钮 ID
const POLLING_INTERVAL = 500; // 轮询间隔 (毫秒)
const TIMEOUT_DURATION = 15000; // 超时时间 (毫秒)
// --- 状态变量 ---
let intervalId = null;
let timeoutId = null;
let isInitialized = false;
// --- 主要逻辑 ---
function init() {
// 如果已经初始化,则不重复执行
if (window.__jimengOptimizerInitialized) {
console.log('即梦优化脚本:已经初始化,跳过...');
return;
}
window.__jimengOptimizerInitialized = true;
console.log('即梦优化脚本:开始查找元素...');
// 清除之前的定时器(以防万一)
if (intervalId) clearInterval(intervalId);
if (timeoutId) clearTimeout(timeoutId);
// 使用 GM_addStyle 添加全局样式 - 结合 11.js 中成功的选择器和样式
addGlobalStyle();
intervalId = setInterval(() => {
if (isInitialized) {
clearInterval(intervalId);
clearTimeout(timeoutId);
return;
}
const inputElement = document.querySelector(INPUT_SELECTOR);
if (inputElement) {
console.log('即梦优化脚本:找到输入框元素!');
isInitialized = true;
clearInterval(intervalId);
clearTimeout(timeoutId);
// --- 创建并添加按钮 ---
createAndAppendButton(inputElement);
// ---------------------
}
}, POLLING_INTERVAL);
// 设置超时,如果在 TIMEOUT_DURATION 后仍未找到元素,则停止轮询
timeoutId = setTimeout(() => {
if (!isInitialized) {
clearInterval(intervalId);
console.warn('即梦优化脚本:超时!未能找到输入框元素。');
}
}, TIMEOUT_DURATION);
}
// --- 添加全局样式 (使用 GM_addStyle) ---
function addGlobalStyle() {
// 直接使用 11.js 中的有效选择器和样式,同时保留我们的样式
GM_addStyle(`
/* 从 11.js 借鉴的样式 */
#image-input-drag-content {
height: auto !important;
min-height: ${TARGET_HEIGHT} !important;
}
/* 尝试更多可能的选择器 */
.tiptap-editor,
.tiptap-container,
.editor-container,
.editor-wrapper,
div[role="textbox"],
div[data-slate-editor],
div.tiptap {
min-height: ${TARGET_HEIGHT} !important;
height: auto !important;
max-height: none !important;
}
/* 输入框本身 */
${INPUT_SELECTOR} {
min-height: ${TARGET_HEIGHT} !important;
height: auto !important;
}
/* 修复可能的父容器限制 */
.prompt-wrapper,
.input-container,
.prompt-container,
.textbox-container {
min-height: ${TARGET_HEIGHT} !important;
height: auto !important;
max-height: none !important;
}
/* 按钮样式 */
#${BUTTON_ID} {
display: inline-block !important;
margin: 10px 0 !important;
padding: 8px 15px !important;
cursor: pointer !important;
border: 1px solid #ccc !important;
border-radius: 4px !important;
background-color: #f0f0f0 !important;
font-size: 14px !important;
z-index: 9999 !important;
position: relative !important;
}
#${BUTTON_ID}:hover {
background-color: #e0e0e0 !important;
}
`);
console.log('即梦优化脚本:全局样式已通过 GM_addStyle 添加');
}
// --- 启动脚本 ---
// 尝试在 DOM 加载后直接运行
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init(); // 如果 DOM 已经加载,直接运行
}
function createAndAppendButton(inputElement) {
if (!inputElement || document.getElementById(BUTTON_ID)) {
// 如果输入框不存在或按钮已存在,则不执行
return;
}
console.log('即梦优化脚本:创建格式化按钮...');
const button = document.createElement('button');
button.id = BUTTON_ID;
button.textContent = BUTTON_TEXT;
// 尝试找到各种可能的容器,按优先级排序
const containers = [
document.querySelector(INPUT_CONTAINER_SELECTOR), // 我们原来的容器选择器
inputElement.closest('#image-input-drag-content'), // 从11.js借鉴的选择器
inputElement.closest('.tiptap-editor'),
inputElement.closest('.editor-container'),
inputElement.closest('.prompt-wrapper'),
inputElement.parentElement,
inputElement.parentElement?.parentElement
];
// 找到第一个有效的容器
const container = containers.find(c => c !== null && c !== undefined);
if (container && container.parentNode) {
// 创建按钮容器,并设置样式
const buttonContainer = document.createElement('div');
buttonContainer.style.cssText = 'margin: 15px 0; text-align: left; width: 100%; clear: both; position: relative; z-index: 1000;';
buttonContainer.appendChild(button);
// 将按钮容器插入到输入框容器后面
container.parentNode.insertBefore(buttonContainer, container.nextSibling);
console.log('即梦优化脚本:按钮已添加到输入框容器后面。');
} else {
// 备用方案:添加到body,并使用绝对定位
console.warn('即梦优化脚本:未能找到预期的容器,尝试备用方案添加按钮。');
const buttonContainer = document.createElement('div');
buttonContainer.style.cssText = 'margin: 15px 0; text-align: left; clear: both; position: absolute; z-index: 9999;';
buttonContainer.appendChild(button);
document.body.appendChild(buttonContainer);
const rect = inputElement.getBoundingClientRect();
buttonContainer.style.top = (window.scrollY + rect.bottom + 10) + 'px';
buttonContainer.style.left = (window.scrollX + rect.left) + 'px';
buttonContainer.style.width = 'auto';
console.log('即梦优化脚本:按钮已添加到绝对位置。');
}
// 绑定点击事件
button.addEventListener('click', function() {
console.log('即梦优化脚本:格式化按钮被点击');
try {
formatPrompt(inputElement);
} catch (error) {
console.error('即梦优化脚本:格式化过程出错', error);
backupFormatMethod(inputElement);
}
});
}
// 备用格式化方法
function backupFormatMethod(element) {
if (!element) return;
console.log('即梦优化脚本:尝试备用格式化方法...');
try {
const originalText = element.textContent || '';
// 移除所有不可见字符和特殊控制字符
const formattedText = cleanText(originalText);
if (originalText !== formattedText) {
element.textContent = formattedText;
// 尝试触发多种输入相关事件以确保网站检测到变化
triggerInputEvents(element);
console.log('即梦优化脚本:使用备用方法格式化完成');
showSuccess();
} else {
console.log('即梦优化脚本:无需格式化');
}
} catch (error) {
console.error('即梦优化脚本:备用格式化方法失败', error);
}
}
// 主要格式化方法
function formatPrompt(element) {
if (element) {
console.log('即梦优化脚本:格式化提示词...');
let originalText;
try {
originalText = element.innerText || '';
} catch (e) {
originalText = element.textContent || '';
console.log('即梦优化脚本:使用textContent替代innerText');
}
// 清理文本,移除所有不可见字符和特殊控制字符
const formattedText = cleanText(originalText);
if (originalText !== formattedText) {
try {
// 尝试使用不同方法设置内容
if (typeof element.innerText !== 'undefined') {
element.innerText = formattedText;
} else {
element.textContent = formattedText;
console.log('即梦优化脚本:使用textContent设置内容');
}
// 尝试触发多种输入相关事件
triggerInputEvents(element);
console.log('即梦优化脚本:提示词已格式化。');
showSuccess();
} catch (e) {
console.error('即梦优化脚本:设置内容失败', e);
// 最后尝试直接设置innerHTML
try {
element.innerHTML = formattedText;
triggerInputEvents(element);
} catch (error) {
console.error('即梦优化脚本:所有设置方法都失败', error);
}
}
} else {
console.log('即梦优化脚本:提示词无需格式化。');
}
}
}
// 清理文本,移除所有不可见字符和特殊控制字符
function cleanText(text) {
if (!text) return '';
let formattedText = text
// 移除零宽连接符
.replace(/\u200C/g, '')
// 移除零宽空格
.replace(/\u200B/g, '')
// 移除零宽非连接符
.replace(/\u200D/g, '')
// 移除零宽不换行空格
.replace(/\uFEFF/g, '')
// 移除各种控制字符
.replace(/[\u0000-\u001F\u007F-\u009F]/g, '')
// 移除各种特殊空格,但保留普通空格
.replace(/[\u00A0\u1680\u180E\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]/g, ' ')
// 移除Unicode组合标记
.replace(/[\u0300-\u036F\u1AB0-\u1AFF\u1DC0-\u1DFF\u20D0-\u20FF\uFE20-\uFE2F]/g, '')
// 删除英文破折号(en dash、em dash)
.replace(/[–—]/g, '');
// 不再移除任何标点符号和特殊字符,只将单引号替换为双引号
// 将所有类型的单引号替换成双引号
formattedText = formattedText.replace(/['']/g, '"');
console.log('即梦优化脚本:文本清理完成');
return formattedText;
}
// 尝试触发多种输入相关事件以确保网站检测到变化
function triggerInputEvents(element) {
const events = ['input', 'change', 'keyup', 'keydown', 'keypress'];
events.forEach(eventType => {
try {
const event = new Event(eventType, { bubbles: true });
element.dispatchEvent(event);
} catch (e) {
console.log(`即梦优化脚本:触发${eventType}事件失败`);
}
});
// 尝试模拟按键事件
try {
const keyEvent = new KeyboardEvent('keydown', {
bubbles: true,
cancelable: true,
key: ' ',
keyCode: 32
});
element.dispatchEvent(keyEvent);
} catch (e) {
console.log('即梦优化脚本:触发keydown事件失败');
}
}
// 显示成功提示
function showSuccess() {
const btn = document.getElementById(BUTTON_ID);
if(btn) {
const originalBg = btn.style.backgroundColor || '#f0f0f0';
btn.style.backgroundColor = '#c8e6c9';
setTimeout(() => { btn.style.backgroundColor = originalBg; }, 1000);
}
}
})();