Gadget-CopyTextTool.js:修订间差异
来自卡厄思梦境WIKI
创建页面,内容为“// 文本复制工具 (function() { 'use strict'; // ===== 自定义配置区域 ===== const BUTTON_CONFIG = { '蓝色': '{{文本|蓝|', '绿色': '{{文本|绿|', '蓝色下划线': '{{文本|蓝|下划线|', '绿色描边': '{{描边|绿|', '词典': '{{词典|' }; // ======================== let toolElement = null; let isPinned = true; let isDragging = false; let currentX;…” |
无编辑摘要 |
||
| (未显示同一用户的3个中间版本) | |||
| 第1行: | 第1行: | ||
// | /** | ||
(function() { | * 文本复制工具 - 修复版 | ||
* 支持 MediaWiki 各种编辑器 | |||
*/ | |||
(function($, mw) { | |||
'use strict'; | 'use strict'; | ||
// ===== | // ===== 自定义配置 ===== | ||
var config = { | |||
'蓝色': '{{文本|蓝|', | '蓝色': { | ||
'绿色': '{{文本|绿|', | template: '{{文本|蓝|{text}}}', | ||
'蓝色下划线': '{{文本|蓝|下划线|', | fallback: '{{文本|蓝|' | ||
'绿色描边': '{{描边|绿|', | }, | ||
'词典': '{{词典|' | '绿色': { | ||
template: '{{文本|绿|{text}}}', | |||
fallback: '{{文本|绿|' | |||
}, | |||
'蓝色下划线': { | |||
template: '{{文本|蓝|下划线|{text}}}', | |||
fallback: '{{文本|蓝|下划线|' | |||
}, | |||
'绿色描边': { | |||
template: '{{描边|绿|{text}}}', | |||
fallback: '{{描边|绿|' | |||
}, | |||
'词典': { | |||
template: '{{词典|{text}}}', | |||
fallback: '{{词典|' | |||
}, | |||
'红色': { | |||
template: '{{文本|红|{text}}}', | |||
fallback: '{{文本|红|' | |||
} | |||
}; | |||
var defaultPosition = { | |||
top: '100px', | |||
right: '20px' | |||
}; | }; | ||
// | // ==================== | ||
var toolPanel = null; | |||
var floatButton = null; | |||
var isVisible = false; | |||
var isMinimized = false; | |||
var currentMode = 'auto'; | |||
$(function() { | |||
var savedState = localStorage.getItem('copyTextTool_visible'); | |||
isVisible = savedState === 'true'; | |||
createFloatButton(); | |||
createToolPanel(); | |||
if (isVisible) { | |||
showTool(); | |||
} | |||
}); | |||
function createFloatButton() { | |||
floatButton = $('<div>') | |||
.attr('id', 'copy-tool-fab') | |||
.attr('title', '文本复制工具') | |||
.html('📋') | |||
.css({ | |||
position: 'fixed', | |||
bottom: '30px', | |||
right: '30px', | |||
width: '56px', | |||
height: '56px', | |||
borderRadius: '50%', | |||
background: 'linear-gradient(135deg, #2196F3 0%, #1976D2 100%)', | |||
color: 'white', | |||
fontSize: '24px', | |||
display: 'flex', | |||
alignItems: 'center', | |||
justifyContent: 'center', | |||
cursor: 'pointer', | |||
boxShadow: '0 4px 12px rgba(33, 150, 243, 0.4)', | |||
zIndex: 9998, | |||
transition: 'all 0.3s ease', | |||
userSelect: 'none' | |||
}) | |||
.on('click', toggleTool) | |||
.hover( | |||
function() { | |||
$(this).css({ | |||
transform: 'scale(1.1)', | |||
boxShadow: '0 6px 16px rgba(33, 150, 243, 0.6)' | |||
}); | |||
}, | |||
function() { | |||
$(this).css({ | |||
transform: 'scale(1)', | |||
boxShadow: '0 4px 12px rgba(33, 150, 243, 0.4)' | |||
}); | |||
} | |||
) | |||
.appendTo('body'); | |||
} | |||
function createToolPanel() { | function createToolPanel() { | ||
var savedPos = localStorage.getItem('copyTextTool_position'); | |||
var position = savedPos ? JSON.parse(savedPos) : defaultPosition; | |||
toolPanel = $('<div>') | |||
<span | .attr('id', 'copy-text-tool') | ||
.css({ | |||
position: 'fixed', | |||
top: position.top, | |||
< | right: position.right, | ||
< | left: position.left || 'auto', | ||
<div | width: '340px', | ||
< | background: 'white', | ||
border: '2px solid #2196F3', | |||
borderRadius: '12px', | |||
< | boxShadow: '0 8px 24px rgba(0,0,0,0.15)', | ||
<div id= | zIndex: 9999, | ||
display: 'none', | |||
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif' | |||
}); | |||
var header = $('<div>') | |||
.attr('id', 'tool-header') | |||
.css({ | |||
background: 'linear-gradient(135deg, #2196F3 0%, #1976D2 100%)', | |||
color: 'white', | |||
padding: '12px 15px', | |||
borderRadius: '10px 10px 0 0', | |||
cursor: 'move', | |||
display: 'flex', | |||
justifyContent: 'space-between', | |||
alignItems: 'center', | |||
userSelect: 'none' | |||
}) | |||
.appendTo(toolPanel); | |||
$('<div>') | |||
.css({ | |||
fontWeight: 'bold', | |||
fontSize: '15px', | |||
display: 'flex', | |||
alignItems: 'center', | |||
gap: '8px' | |||
}) | |||
.html('<span style="font-size: 18px">📋</span> 文本工具') | |||
.appendTo(header); | |||
var controls = $('<div>') | |||
.css({ | |||
display: 'flex', | |||
gap: '8px' | |||
}) | |||
.appendTo(header); | |||
$('<button>') | |||
.attr('title', '最小化') | |||
.html('−') | |||
.css({ | |||
background: 'rgba(255,255,255,0.2)', | |||
border: 'none', | |||
color: 'white', | |||
width: '24px', | |||
height: '24px', | |||
borderRadius: '4px', | |||
cursor: 'pointer', | |||
fontSize: '16px' | |||
}) | |||
.on('click', minimizeTool) | |||
.hover( | |||
function() { $(this).css('background', 'rgba(255,255,255,0.3)'); }, | |||
function() { $(this).css('background', 'rgba(255,255,255,0.2)'); } | |||
) | |||
.appendTo(controls); | |||
$('<button>') | |||
.attr('title', '关闭') | |||
.html('×') | |||
.css({ | |||
background: 'rgba(255,255,255,0.2)', | |||
border: 'none', | |||
color: 'white', | |||
width: '24px', | |||
height: '24px', | |||
borderRadius: '4px', | |||
cursor: 'pointer', | |||
fontSize: '20px' | |||
}) | |||
.on('click', hideTool) | |||
.hover( | |||
function() { $(this).css('background', 'rgba(255,255,255,0.3)'); }, | |||
function() { $(this).css('background', 'rgba(255,255,255,0.2)'); } | |||
) | |||
.appendTo(controls); | |||
var body = $('<div>') | |||
.attr('id', 'tool-body') | |||
.css({ | |||
padding: '15px' | |||
}) | |||
.appendTo(toolPanel); | |||
// 模式选择 | |||
var modeSelector = $('<div>') | |||
.css({ | |||
display: 'flex', | |||
gap: '8px', | |||
marginBottom: '12px', | |||
padding: '8px', | |||
background: '#f5f5f5', | |||
borderRadius: '6px' | |||
}) | |||
.appendTo(body); | |||
$('<div>') | |||
.text('模式:') | |||
.css({ | |||
fontSize: '13px', | |||
color: '#666', | |||
lineHeight: '28px', | |||
fontWeight: '500' | |||
}) | |||
.appendTo(modeSelector); | |||
var modes = [ | |||
{ id: 'auto', name: '智能', title: '自动检测' }, | |||
{ id: 'insert', name: '插入', title: '插入到编辑器' }, | |||
{ id: 'copy', name: '复制', title: '复制到剪贴板' } | |||
]; | |||
var modeButtons = $('<div>') | |||
.css({ | |||
display: 'flex', | |||
gap: '4px', | |||
flex: '1' | |||
}) | |||
.appendTo(modeSelector); | |||
modes.forEach(function(mode) { | |||
$('<button>') | |||
.attr({ | |||
'data-mode': mode.id, | |||
'title': mode.title | |||
}) | |||
.text(mode.name) | |||
.css({ | |||
flex: '1', | |||
padding: '6px 8px', | |||
background: mode.id === 'auto' ? '#2196F3' : 'white', | |||
color: mode.id === 'auto' ? 'white' : '#666', | |||
border: '1px solid #ddd', | |||
borderRadius: '4px', | |||
cursor: 'pointer', | |||
fontSize: '12px', | |||
transition: 'all 0.2s' | |||
}) | |||
.on('click', function() { | |||
currentMode = mode.id; | |||
modeButtons.find('button').css({ | |||
background: 'white', | |||
color: '#666' | |||
}); | |||
$(this).css({ | |||
background: '#2196F3', | |||
color: 'white' | |||
}); | |||
updateStatus('✓ 切换到' + mode.name + '模式', true); | |||
}) | |||
.appendTo(modeButtons); | |||
}); | |||
// 按钮容器 | |||
var buttonContainer = $('<div>') | |||
.css({ | |||
display: 'grid', | |||
gridTemplateColumns: 'repeat(2, 1fr)', | |||
gap: '8px', | |||
marginBottom: '10px' | |||
}) | |||
.appendTo(body); | |||
$.each(config, function(name, conf) { | |||
$('<button>') | |||
.text(name) | |||
.css({ | |||
padding: '12px 8px', | |||
background: '#2196F3', | |||
color: 'white', | |||
border: 'none', | |||
borderRadius: '6px', | |||
cursor: 'pointer', | |||
fontSize: '13px', | |||
fontWeight: '500', | |||
transition: 'all 0.2s ease' | |||
}) | |||
.on('click', function() { | |||
handleButtonClick(name, conf); | |||
}) | |||
.hover( | |||
function() { | |||
$(this).css({ | |||
background: '#1976D2', | |||
transform: 'translateY(-1px)', | |||
boxShadow: '0 2px 8px rgba(33, 150, 243, 0.3)' | |||
}); | |||
}, | |||
function() { | |||
$(this).css({ | |||
background: '#2196F3', | |||
transform: 'translateY(0)', | |||
boxShadow: 'none' | |||
}); | |||
} | |||
) | |||
.appendTo(buttonContainer); | |||
}); | |||
$('<div>') | |||
.attr('id', 'copy-status') | |||
.css({ | |||
textAlign: 'center', | |||
color: '#666', | |||
padding: '10px', | |||
fontSize: '12px', | |||
minHeight: '20px', | |||
background: '#f9f9f9', | |||
borderRadius: '6px' | |||
}) | |||
.html('💡 先选中文字,再点击按钮') | |||
.appendTo(body); | |||
toolPanel.appendTo('body'); | |||
makeDraggable(toolPanel[0], header[0]); | |||
} | |||
// ===== 核心功能:处理按钮点击 ===== | |||
function handleButtonClick(name, conf) { | |||
console.log('[文本工具] 按钮点击:', name); | |||
// 获取选中文字 | |||
var selectedText = getSelectedText(); | |||
console.log('[文本工具] 选中文字:', selectedText); | |||
// 决定使用什么文本 | |||
var finalText = selectedText ? | |||
conf.template.replace('{text}', selectedText) : | |||
conf.fallback; | |||
console.log('[文本工具] 最终文本:', finalText); | |||
// 根据模式决定操作 | |||
if (currentMode === 'copy') { | |||
// 强制复制模式 | |||
copyToClipboard(finalText); | |||
updateStatus('✓ 已复制: ' + name, true); | |||
return; | |||
} | |||
// 尝试插入 | |||
var editor = findEditor(); | |||
console.log('[文本工具] 找到的编辑器:', editor); | |||
if (editor && currentMode !== 'copy') { | |||
// 找到编辑器,执行插入 | |||
var success = insertToEditor(editor, finalText, selectedText ? true : false); | |||
if (success) { | |||
updateStatus('✓ 已插入: ' + name, true); | |||
} else { | |||
// 插入失败,回退到复制 | |||
copyToClipboard(finalText); | |||
updateStatus('⚠ 插入失败,已复制', false); | |||
} | |||
} else { | |||
// 未找到编辑器或处于复制模式 | |||
copyToClipboard(finalText); | |||
updateStatus('✓ 已复制: ' + name, true); | |||
} | |||
} | |||
// ===== 查找编辑器 ===== | |||
function findEditor() { | |||
// 1. 检查 wpTextbox1 (MediaWiki 标准编辑器) | |||
var wpTextbox = document.getElementById('wpTextbox1'); | |||
if (wpTextbox) { | |||
console.log('[文本工具] 找到 wpTextbox1'); | |||
return { type: 'textarea', element: wpTextbox }; | |||
} | |||
// 2. 检查 CodeMirror | |||
if (typeof $ !== 'undefined' && $('.CodeMirror').length > 0) { | |||
var cm = $('.CodeMirror')[0]; | |||
if (cm.CodeMirror) { | |||
console.log('[文本工具] 找到 CodeMirror'); | |||
return { type: 'codemirror', element: cm.CodeMirror }; | |||
} | |||
} | |||
// 3. 检查当前焦点元素 | |||
var activeEl = document.activeElement; | |||
if (activeEl && (activeEl.tagName === 'TEXTAREA' || | |||
(activeEl.tagName === 'INPUT' && activeEl.type === 'text'))) { | |||
console.log('[文本工具] 找到活动输入框:', activeEl.tagName); | |||
return { type: 'textarea', element: activeEl }; | |||
} | |||
// 4. 检查可编辑元素 | |||
if (activeEl && activeEl.contentEditable === 'true') { | |||
console.log('[文本工具] 找到可编辑元素'); | |||
return { type: 'contenteditable', element: activeEl }; | |||
} | |||
console.log('[文本工具] 未找到编辑器'); | |||
return null; | |||
} | |||
// ===== 获取选中文字 ===== | |||
function getSelectedText() { | |||
// 1. 先检查编辑器内的选中 | |||
var editor = findEditor(); | |||
if (editor) { | |||
if (editor.type === 'textarea') { | |||
var el = editor.element; | |||
var start = el.selectionStart; | |||
var end = el.selectionEnd; | |||
if (start !== end) { | |||
return el.value.substring(start, end); | |||
} | |||
} else if (editor.type === 'codemirror') { | |||
var selection = editor.element.getSelection(); | |||
if (selection) { | |||
return selection; | |||
} | |||
} | |||
} | |||
// | // 2. 检查页面上的选中文字 | ||
var selection = window.getSelection(); | |||
if (selection && selection.toString().trim()) { | |||
return selection.toString().trim(); | |||
} | |||
return ''; | |||
} | |||
// ===== 插入到编辑器 ===== | |||
function insertToEditor(editor, text, replaceSelection) { | |||
console.log('[文本工具] 执行插入:', editor.type, text, replaceSelection); | |||
try { | |||
if (editor.type === 'textarea') { | |||
var el = editor.element; | |||
var start = el.selectionStart; | |||
var end = el.selectionEnd; | |||
var value = el.value; | |||
if (replaceSelection && start !== end) { | |||
// 替换选中的文本 | |||
el.value = value.substring(0, start) + text + value.substring(end); | |||
var newPos = start + text.length; | |||
el.selectionStart = el.selectionEnd = newPos; | |||
} else { | |||
// 在光标位置插入 | |||
el.value = value.substring(0, start) + text + value.substring(start); | |||
var newPos = start + text.length; | |||
el.selectionStart = el.selectionEnd = newPos; | |||
} | |||
// 触发事件 | |||
var event = new Event('input', { bubbles: true }); | |||
el.dispatchEvent(event); | |||
// 聚焦 | |||
el.focus(); | |||
console.log('[文本工具] 插入成功 (textarea)'); | |||
return true; | |||
} else if (editor.type === 'codemirror') { | |||
var cm = editor.element; | |||
if (replaceSelection) { | |||
cm.replaceSelection(text); | |||
} else { | |||
var cursor = cm.getCursor(); | |||
cm.replaceRange(text, cursor); | |||
} | |||
cm.focus(); | |||
console.log('[文本工具] 插入成功 (codemirror)'); | |||
return true; | |||
} else if (editor.type === 'contenteditable') { | |||
document.execCommand('insertText', false, text); | |||
console.log('[文本工具] 插入成功 (contenteditable)'); | |||
return true; | |||
} | |||
} catch (e) { | |||
console.error('[文本工具] 插入失败:', e); | |||
return false; | |||
} | } | ||
return false; | |||
return | |||
} | } | ||
// | // ===== 复制到剪贴板 ===== | ||
function | function copyToClipboard(text) { | ||
console.log('[文本工具] 复制到剪贴板:', text); | |||
if (navigator.clipboard && navigator.clipboard.writeText) { | if (navigator.clipboard && navigator.clipboard.writeText) { | ||
navigator.clipboard.writeText(text).then(() | navigator.clipboard.writeText(text).then(function() { | ||
console.log('[文本工具] 复制成功 (现代API)'); | |||
}).catch(() | }).catch(function(err) { | ||
fallbackCopy(text | console.log('[文本工具] 现代API失败,使用备用方法'); | ||
fallbackCopy(text); | |||
}); | }); | ||
} else { | } else { | ||
fallbackCopy(text | fallbackCopy(text); | ||
} | } | ||
} | } | ||
function fallbackCopy(text) { | |||
function fallbackCopy(text | var $temp = $('<textarea>') | ||
.val(text) | |||
.css({ | |||
position: 'fixed', | |||
opacity: 0, | |||
top: '-9999px', | |||
left: '-9999px' | |||
}) | |||
.appendTo('body'); | |||
$temp[0].select(); | |||
$temp[0].setSelectionRange(0, 99999); | |||
try { | try { | ||
document.execCommand('copy'); | var success = document.execCommand('copy'); | ||
console.log('[文本工具] 复制', success ? '成功' : '失败', '(备用方法)'); | |||
} catch (err) { | } catch(err) { | ||
console.error('[文本工具] 复制失败:', err); | |||
} | } | ||
$temp.remove(); | |||
} | } | ||
// | // ===== 状态更新 ===== | ||
function | function updateStatus(message, success) { | ||
console.log('[文本工具] 状态:', message); | |||
status. | |||
status. | var icon = success ? '✓' : '⚠'; | ||
var color = success ? '#2196F3' : '#ff9800'; | |||
$('#copy-status') | |||
.html('<b>' + icon + '</b> ' + message) | |||
.css({ | |||
color: color, | |||
fontWeight: 'bold' | |||
}); | |||
setTimeout(function() { | |||
$('#copy-status') | |||
.html('💡 先选中文字,再点击按钮') | |||
.css({ | |||
color: '#666', | |||
fontWeight: 'normal' | |||
}); | |||
}, 3000); | |||
} | } | ||
// | // ===== UI 控制 ===== | ||
function | function toggleTool() { | ||
if ( | if (isVisible) { | ||
hideTool(); | |||
} else { | } else { | ||
showTool(); | |||
} | } | ||
} | } | ||
function | function showTool() { | ||
if ( | if (isMinimized) { | ||
isMinimized = false; | |||
$('#tool-body').show(); | |||
toolPanel.css('width', '340px'); | |||
} | } | ||
toolPanel.fadeIn(200); | |||
isVisible = true; | |||
localStorage.setItem('copyTextTool_visible', 'true'); | |||
} | } | ||
function | function hideTool() { | ||
toolPanel.fadeOut(200); | |||
isVisible = false; | |||
localStorage.setItem('copyTextTool_visible', 'false'); | |||
} | } | ||
function | function minimizeTool() { | ||
if (isMinimized) { | |||
$('#tool-body').slideDown(200); | |||
toolPanel.css('width', '340px'); | |||
isMinimized = false; | |||
} else { | |||
$('#tool-body').slideUp(200); | |||
toolPanel.css('width', 'auto'); | |||
isMinimized = true; | |||
} | |||
} | } | ||
// | // ===== 拖动功能 ===== | ||
function | function makeDraggable(element, handle) { | ||
var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; | |||
handle.onmousedown = dragMouseDown; | |||
function dragMouseDown(e) { | |||
e = e || window.event; | |||
e.preventDefault(); | |||
pos3 = e.clientX; | |||
pos4 = e.clientY; | |||
document.onmouseup = closeDragElement; | |||
document.onmousemove = elementDrag; | |||
} | |||
function elementDrag(e) { | |||
e = e || window.event; | |||
e.preventDefault(); | e.preventDefault(); | ||
pos1 = pos3 - e.clientX; | |||
pos2 = pos4 - e.clientY; | |||
pos3 = e.clientX; | |||
pos4 = e.clientY; | |||
var newTop = (element.offsetTop - pos2); | |||
var newLeft = (element.offsetLeft - pos1); | |||
element.style.top = newTop + "px"; | |||
element.style.left = newLeft + "px"; | |||
element.style.right = "auto"; | |||
} | |||
function closeDragElement() { | |||
document.onmouseup = null; | |||
document.onmousemove = null; | |||
var position = { | |||
top: element.style.top, | |||
left: element.style.left | |||
}; | |||
localStorage.setItem('copyTextTool_position', JSON.stringify(position)); | |||
} | |||
} | |||
} | } | ||
})(jQuery, mediaWiki); | |||
2025年10月3日 (五) 21:06的最新版本
/**
* 文本复制工具 - 修复版
* 支持 MediaWiki 各种编辑器
*/
(function($, mw) {
'use strict';
// ===== 自定义配置 =====
var config = {
'蓝色': {
template: '{{文本|蓝|{text}}}',
fallback: '{{文本|蓝|'
},
'绿色': {
template: '{{文本|绿|{text}}}',
fallback: '{{文本|绿|'
},
'蓝色下划线': {
template: '{{文本|蓝|下划线|{text}}}',
fallback: '{{文本|蓝|下划线|'
},
'绿色描边': {
template: '{{描边|绿|{text}}}',
fallback: '{{描边|绿|'
},
'词典': {
template: '{{词典|{text}}}',
fallback: '{{词典|'
},
'红色': {
template: '{{文本|红|{text}}}',
fallback: '{{文本|红|'
}
};
var defaultPosition = {
top: '100px',
right: '20px'
};
// ====================
var toolPanel = null;
var floatButton = null;
var isVisible = false;
var isMinimized = false;
var currentMode = 'auto';
$(function() {
var savedState = localStorage.getItem('copyTextTool_visible');
isVisible = savedState === 'true';
createFloatButton();
createToolPanel();
if (isVisible) {
showTool();
}
});
function createFloatButton() {
floatButton = $('<div>')
.attr('id', 'copy-tool-fab')
.attr('title', '文本复制工具')
.html('📋')
.css({
position: 'fixed',
bottom: '30px',
right: '30px',
width: '56px',
height: '56px',
borderRadius: '50%',
background: 'linear-gradient(135deg, #2196F3 0%, #1976D2 100%)',
color: 'white',
fontSize: '24px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
cursor: 'pointer',
boxShadow: '0 4px 12px rgba(33, 150, 243, 0.4)',
zIndex: 9998,
transition: 'all 0.3s ease',
userSelect: 'none'
})
.on('click', toggleTool)
.hover(
function() {
$(this).css({
transform: 'scale(1.1)',
boxShadow: '0 6px 16px rgba(33, 150, 243, 0.6)'
});
},
function() {
$(this).css({
transform: 'scale(1)',
boxShadow: '0 4px 12px rgba(33, 150, 243, 0.4)'
});
}
)
.appendTo('body');
}
function createToolPanel() {
var savedPos = localStorage.getItem('copyTextTool_position');
var position = savedPos ? JSON.parse(savedPos) : defaultPosition;
toolPanel = $('<div>')
.attr('id', 'copy-text-tool')
.css({
position: 'fixed',
top: position.top,
right: position.right,
left: position.left || 'auto',
width: '340px',
background: 'white',
border: '2px solid #2196F3',
borderRadius: '12px',
boxShadow: '0 8px 24px rgba(0,0,0,0.15)',
zIndex: 9999,
display: 'none',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
});
var header = $('<div>')
.attr('id', 'tool-header')
.css({
background: 'linear-gradient(135deg, #2196F3 0%, #1976D2 100%)',
color: 'white',
padding: '12px 15px',
borderRadius: '10px 10px 0 0',
cursor: 'move',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
userSelect: 'none'
})
.appendTo(toolPanel);
$('<div>')
.css({
fontWeight: 'bold',
fontSize: '15px',
display: 'flex',
alignItems: 'center',
gap: '8px'
})
.html('<span style="font-size: 18px">📋</span> 文本工具')
.appendTo(header);
var controls = $('<div>')
.css({
display: 'flex',
gap: '8px'
})
.appendTo(header);
$('<button>')
.attr('title', '最小化')
.html('−')
.css({
background: 'rgba(255,255,255,0.2)',
border: 'none',
color: 'white',
width: '24px',
height: '24px',
borderRadius: '4px',
cursor: 'pointer',
fontSize: '16px'
})
.on('click', minimizeTool)
.hover(
function() { $(this).css('background', 'rgba(255,255,255,0.3)'); },
function() { $(this).css('background', 'rgba(255,255,255,0.2)'); }
)
.appendTo(controls);
$('<button>')
.attr('title', '关闭')
.html('×')
.css({
background: 'rgba(255,255,255,0.2)',
border: 'none',
color: 'white',
width: '24px',
height: '24px',
borderRadius: '4px',
cursor: 'pointer',
fontSize: '20px'
})
.on('click', hideTool)
.hover(
function() { $(this).css('background', 'rgba(255,255,255,0.3)'); },
function() { $(this).css('background', 'rgba(255,255,255,0.2)'); }
)
.appendTo(controls);
var body = $('<div>')
.attr('id', 'tool-body')
.css({
padding: '15px'
})
.appendTo(toolPanel);
// 模式选择
var modeSelector = $('<div>')
.css({
display: 'flex',
gap: '8px',
marginBottom: '12px',
padding: '8px',
background: '#f5f5f5',
borderRadius: '6px'
})
.appendTo(body);
$('<div>')
.text('模式:')
.css({
fontSize: '13px',
color: '#666',
lineHeight: '28px',
fontWeight: '500'
})
.appendTo(modeSelector);
var modes = [
{ id: 'auto', name: '智能', title: '自动检测' },
{ id: 'insert', name: '插入', title: '插入到编辑器' },
{ id: 'copy', name: '复制', title: '复制到剪贴板' }
];
var modeButtons = $('<div>')
.css({
display: 'flex',
gap: '4px',
flex: '1'
})
.appendTo(modeSelector);
modes.forEach(function(mode) {
$('<button>')
.attr({
'data-mode': mode.id,
'title': mode.title
})
.text(mode.name)
.css({
flex: '1',
padding: '6px 8px',
background: mode.id === 'auto' ? '#2196F3' : 'white',
color: mode.id === 'auto' ? 'white' : '#666',
border: '1px solid #ddd',
borderRadius: '4px',
cursor: 'pointer',
fontSize: '12px',
transition: 'all 0.2s'
})
.on('click', function() {
currentMode = mode.id;
modeButtons.find('button').css({
background: 'white',
color: '#666'
});
$(this).css({
background: '#2196F3',
color: 'white'
});
updateStatus('✓ 切换到' + mode.name + '模式', true);
})
.appendTo(modeButtons);
});
// 按钮容器
var buttonContainer = $('<div>')
.css({
display: 'grid',
gridTemplateColumns: 'repeat(2, 1fr)',
gap: '8px',
marginBottom: '10px'
})
.appendTo(body);
$.each(config, function(name, conf) {
$('<button>')
.text(name)
.css({
padding: '12px 8px',
background: '#2196F3',
color: 'white',
border: 'none',
borderRadius: '6px',
cursor: 'pointer',
fontSize: '13px',
fontWeight: '500',
transition: 'all 0.2s ease'
})
.on('click', function() {
handleButtonClick(name, conf);
})
.hover(
function() {
$(this).css({
background: '#1976D2',
transform: 'translateY(-1px)',
boxShadow: '0 2px 8px rgba(33, 150, 243, 0.3)'
});
},
function() {
$(this).css({
background: '#2196F3',
transform: 'translateY(0)',
boxShadow: 'none'
});
}
)
.appendTo(buttonContainer);
});
$('<div>')
.attr('id', 'copy-status')
.css({
textAlign: 'center',
color: '#666',
padding: '10px',
fontSize: '12px',
minHeight: '20px',
background: '#f9f9f9',
borderRadius: '6px'
})
.html('💡 先选中文字,再点击按钮')
.appendTo(body);
toolPanel.appendTo('body');
makeDraggable(toolPanel[0], header[0]);
}
// ===== 核心功能:处理按钮点击 =====
function handleButtonClick(name, conf) {
console.log('[文本工具] 按钮点击:', name);
// 获取选中文字
var selectedText = getSelectedText();
console.log('[文本工具] 选中文字:', selectedText);
// 决定使用什么文本
var finalText = selectedText ?
conf.template.replace('{text}', selectedText) :
conf.fallback;
console.log('[文本工具] 最终文本:', finalText);
// 根据模式决定操作
if (currentMode === 'copy') {
// 强制复制模式
copyToClipboard(finalText);
updateStatus('✓ 已复制: ' + name, true);
return;
}
// 尝试插入
var editor = findEditor();
console.log('[文本工具] 找到的编辑器:', editor);
if (editor && currentMode !== 'copy') {
// 找到编辑器,执行插入
var success = insertToEditor(editor, finalText, selectedText ? true : false);
if (success) {
updateStatus('✓ 已插入: ' + name, true);
} else {
// 插入失败,回退到复制
copyToClipboard(finalText);
updateStatus('⚠ 插入失败,已复制', false);
}
} else {
// 未找到编辑器或处于复制模式
copyToClipboard(finalText);
updateStatus('✓ 已复制: ' + name, true);
}
}
// ===== 查找编辑器 =====
function findEditor() {
// 1. 检查 wpTextbox1 (MediaWiki 标准编辑器)
var wpTextbox = document.getElementById('wpTextbox1');
if (wpTextbox) {
console.log('[文本工具] 找到 wpTextbox1');
return { type: 'textarea', element: wpTextbox };
}
// 2. 检查 CodeMirror
if (typeof $ !== 'undefined' && $('.CodeMirror').length > 0) {
var cm = $('.CodeMirror')[0];
if (cm.CodeMirror) {
console.log('[文本工具] 找到 CodeMirror');
return { type: 'codemirror', element: cm.CodeMirror };
}
}
// 3. 检查当前焦点元素
var activeEl = document.activeElement;
if (activeEl && (activeEl.tagName === 'TEXTAREA' ||
(activeEl.tagName === 'INPUT' && activeEl.type === 'text'))) {
console.log('[文本工具] 找到活动输入框:', activeEl.tagName);
return { type: 'textarea', element: activeEl };
}
// 4. 检查可编辑元素
if (activeEl && activeEl.contentEditable === 'true') {
console.log('[文本工具] 找到可编辑元素');
return { type: 'contenteditable', element: activeEl };
}
console.log('[文本工具] 未找到编辑器');
return null;
}
// ===== 获取选中文字 =====
function getSelectedText() {
// 1. 先检查编辑器内的选中
var editor = findEditor();
if (editor) {
if (editor.type === 'textarea') {
var el = editor.element;
var start = el.selectionStart;
var end = el.selectionEnd;
if (start !== end) {
return el.value.substring(start, end);
}
} else if (editor.type === 'codemirror') {
var selection = editor.element.getSelection();
if (selection) {
return selection;
}
}
}
// 2. 检查页面上的选中文字
var selection = window.getSelection();
if (selection && selection.toString().trim()) {
return selection.toString().trim();
}
return '';
}
// ===== 插入到编辑器 =====
function insertToEditor(editor, text, replaceSelection) {
console.log('[文本工具] 执行插入:', editor.type, text, replaceSelection);
try {
if (editor.type === 'textarea') {
var el = editor.element;
var start = el.selectionStart;
var end = el.selectionEnd;
var value = el.value;
if (replaceSelection && start !== end) {
// 替换选中的文本
el.value = value.substring(0, start) + text + value.substring(end);
var newPos = start + text.length;
el.selectionStart = el.selectionEnd = newPos;
} else {
// 在光标位置插入
el.value = value.substring(0, start) + text + value.substring(start);
var newPos = start + text.length;
el.selectionStart = el.selectionEnd = newPos;
}
// 触发事件
var event = new Event('input', { bubbles: true });
el.dispatchEvent(event);
// 聚焦
el.focus();
console.log('[文本工具] 插入成功 (textarea)');
return true;
} else if (editor.type === 'codemirror') {
var cm = editor.element;
if (replaceSelection) {
cm.replaceSelection(text);
} else {
var cursor = cm.getCursor();
cm.replaceRange(text, cursor);
}
cm.focus();
console.log('[文本工具] 插入成功 (codemirror)');
return true;
} else if (editor.type === 'contenteditable') {
document.execCommand('insertText', false, text);
console.log('[文本工具] 插入成功 (contenteditable)');
return true;
}
} catch (e) {
console.error('[文本工具] 插入失败:', e);
return false;
}
return false;
}
// ===== 复制到剪贴板 =====
function copyToClipboard(text) {
console.log('[文本工具] 复制到剪贴板:', text);
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(function() {
console.log('[文本工具] 复制成功 (现代API)');
}).catch(function(err) {
console.log('[文本工具] 现代API失败,使用备用方法');
fallbackCopy(text);
});
} else {
fallbackCopy(text);
}
}
function fallbackCopy(text) {
var $temp = $('<textarea>')
.val(text)
.css({
position: 'fixed',
opacity: 0,
top: '-9999px',
left: '-9999px'
})
.appendTo('body');
$temp[0].select();
$temp[0].setSelectionRange(0, 99999);
try {
var success = document.execCommand('copy');
console.log('[文本工具] 复制', success ? '成功' : '失败', '(备用方法)');
} catch(err) {
console.error('[文本工具] 复制失败:', err);
}
$temp.remove();
}
// ===== 状态更新 =====
function updateStatus(message, success) {
console.log('[文本工具] 状态:', message);
var icon = success ? '✓' : '⚠';
var color = success ? '#2196F3' : '#ff9800';
$('#copy-status')
.html('<b>' + icon + '</b> ' + message)
.css({
color: color,
fontWeight: 'bold'
});
setTimeout(function() {
$('#copy-status')
.html('💡 先选中文字,再点击按钮')
.css({
color: '#666',
fontWeight: 'normal'
});
}, 3000);
}
// ===== UI 控制 =====
function toggleTool() {
if (isVisible) {
hideTool();
} else {
showTool();
}
}
function showTool() {
if (isMinimized) {
isMinimized = false;
$('#tool-body').show();
toolPanel.css('width', '340px');
}
toolPanel.fadeIn(200);
isVisible = true;
localStorage.setItem('copyTextTool_visible', 'true');
}
function hideTool() {
toolPanel.fadeOut(200);
isVisible = false;
localStorage.setItem('copyTextTool_visible', 'false');
}
function minimizeTool() {
if (isMinimized) {
$('#tool-body').slideDown(200);
toolPanel.css('width', '340px');
isMinimized = false;
} else {
$('#tool-body').slideUp(200);
toolPanel.css('width', 'auto');
isMinimized = true;
}
}
// ===== 拖动功能 =====
function makeDraggable(element, handle) {
var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
handle.onmousedown = dragMouseDown;
function dragMouseDown(e) {
e = e || window.event;
e.preventDefault();
pos3 = e.clientX;
pos4 = e.clientY;
document.onmouseup = closeDragElement;
document.onmousemove = elementDrag;
}
function elementDrag(e) {
e = e || window.event;
e.preventDefault();
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
var newTop = (element.offsetTop - pos2);
var newLeft = (element.offsetLeft - pos1);
element.style.top = newTop + "px";
element.style.left = newLeft + "px";
element.style.right = "auto";
}
function closeDragElement() {
document.onmouseup = null;
document.onmousemove = null;
var position = {
top: element.style.top,
left: element.style.left
};
localStorage.setItem('copyTextTool_position', JSON.stringify(position));
}
}
})(jQuery, mediaWiki);