// ==UserScript==
// @name 【SillyTavern / ST酒馆】html代码注入器-改
// @name:zh 【ST酒馆】html代码注入器-改
// @name:zh-CN 【ST酒馆】html代码注入器-改
// @name:zh-TW 【ST酒館】html程式碼注入器-改
// @name:ja 【SillyTavern】 HTMLコードインジェクター-改
// @name:ko 【SillyTavern】 HTML코드 삽입기-수정
// @name:en 【SillyTavern】 HTML Code Injector - Modified
// @name:fr 【SillyTavern】 Injecteur de code HTML - Modifié
// @name:de 【SillyTavern】 HTML-Code-Injektor - Modifiziert
// @namespace https://greasyfork.org/users/590339-miaotouy
// @version 1.1.6.1
// @description 可以让ST酒馆独立运行html代码 (Inject HTML code into SillyTavern pages.)
// @description:zh 可以让ST酒馆独立运行html代码
// @description:zh-CN 可以让ST酒馆独立运行html代码
// @description:zh-TW 讓SillyTavern獨立運行html程式碼
// @description:ja SillyTavernでhtmlコードを独立して実行できるようにします
// @description:ko SillyTavern에서 HTML 코드를 독립적으로 실행할 수 있습니다.
// @description:en Inject HTML code into SillyTavern pages.
// @description:fr Permet d'exécuter du code HTML de manière indépendante dans SillyTavern.
// @description:de Ermöglicht die unabhängige Ausführung von HTML-Code in SillyTavern.
// @author Qianzhuo
// @match *://localhost:8000/*
// @match *://127.0.0.1:8000/*
// @match *://192.168.*.*:*/*
// @match *://*/*:8000/*
// @match *://frp-kit.top:*/*
// @include /^https?:\/\/.*:8000\//
// @grant GM_setValue
// @grant GM_getValue
// @require https://code.jquery.com/jquery-3.6.0.min.js
// @license CC BY-NC 4.0
// ==/UserScript==
/*
原作者:Qianzhuo
修改者:miaotouy
【SillyTavern / ST酒馆】html代码注入器 © 2024 by Qianzhuo is licensed under CC BY-NC 4.0. To view a copy of this license, visit https://creativecommons.org/licenses/by-nc/4.0/
*/
(function () {
'use strict';
// ---------------------------------------- 全局变量 ----------------------------------------
let isInjectionEnabled, displayMode, lastMesTextContent, activationMode, customStartFloor, customEndFloor, savedPosition, isEdgeControlsCollapsed;
let edgeControls, settingsPanel;
// ---------------------------------------- 初始化函数 ----------------------------------------
function initScript() {
if (!document.title.includes('SillyTavern')) {
console.log('页面标题不是 "SillyTavern",脚本未运行。');
return;
}
initVariables();
createUI();
addEventListeners();
addDragFunctionality();
startObservers();
console.log('HTML注入器脚本已初始化');
}
function initVariables() {
isInjectionEnabled = false;
displayMode = parseInt(GM_getValue('displayMode', 1));
lastMesTextContent = '';
activationMode = GM_getValue('activationMode', 'all');
customStartFloor = GM_getValue('customStartFloor', 1);
customEndFloor = GM_getValue('customEndFloor', -1);
savedPosition = GM_getValue('edgeControlsPosition', 'top-right');
isEdgeControlsCollapsed = GM_getValue('isEdgeControlsCollapsed', true);
}
// ---------------------------------------- UI 创建函数 ----------------------------------------
function createUI() {
createSettingsPanel();
createEdgeControls();
addStyles();
}
function createSettingsPanel() {
settingsPanel = document.createElement('div');
settingsPanel.id = 'html-injector-settings';
settingsPanel.classList.add('drawer');
settingsPanel.style.display = 'none';
settingsPanel.innerHTML = `
<div id="html-injector-settings-header" class="inline-drawer-header">
<span class="inline-drawer-title">HTML注入器设置</span>
<div id="html-injector-close-settings" class="inline-drawer-icon fa-solid fa-circle-xmark"></div>
</div>
<div id="settings-content">
<div class="settings-section">
<h3 class="settings-subtitle">边缘控制面板位置</h3>
<select id="edge-controls-position" class="settings-select theme-element">
<option value="top-right">界面右上角</option>
<option value="right-three-quarters">界面右侧3/4位置</option>
<option value="right-middle">界面右侧中间</option>
</select>
</div>
<div class="settings-section">
<h3 class="settings-subtitle">显示模式</h3>
<label class="settings-option"><input type="radio" name="display-mode" value="1" ${displayMode === 1 ? 'checked' : ''}> 原代码和注入效果一起显示</label>
<label class="settings-option"><input type="radio" name="display-mode" value="2" ${displayMode === 2 ? 'checked' : ''}> 原代码以摘要形式显示</label>
<label class="settings-option"><input type="radio" name="display-mode" value="3" ${displayMode === 3 ? 'checked' : ''}> 隐藏原代码,只显示注入效果</label>
</div>
<div class="settings-section">
<h3 class="settings-subtitle">激活楼层</h3>
<select id="activation-mode" class="settings-select theme-element">
<option value="all">全部楼层</option>
<option value="first">第一层</option>
<option value="last">最后一层</option>
<option value="lastN">最后N层</option>
<option value="custom">自定义楼层</option>
</select>
<div id="custom-floor-settings" class="settings-subsection" style="display: none;">
<label class="settings-option">起始楼层: <input type="number" id="custom-start-floor" min="1" value="1"></label>
<label class="settings-option">结束楼层: <input type="number" id="custom-end-floor" min="-1" value="-1"></label>
<p class="settings-note">(-1 表示最后一层)</p>
</div>
<div id="last-n-settings" class="settings-subsection" style="display: none;">
<label class="settings-option">最后 <input type="number" id="last-n-floors" min="1" value="1"> 层</label>
</div>
</div>
</div>
<div class="settings-footer">
<p>安全提醒:请仅注入您信任的代码。不安全的代码可能会对您的系统造成潜在风险。</p>
<p>注意:要注入的 HTML 代码应该用 \`\`\` 包裹,例如:</p>
<pre class="code-example">
\`\`\`
<h1>Hello, World!</h1>
<p>This is an example.</p>
\`\`\`
</pre>
<p>以下是对应ST酒馆功能的特殊类名及简单的使用方法:</p>
<pre class="code-example">
\`\`\`
<button class="qr-button">(你的QR按钮名字)</button>
<textarea class="st-text">(对应酒馆的输入文本框,输入内容会同步到酒馆的文本框里)</textarea>
<button class="st-send-button">(对应酒馆的发送按钮)</button>
\`\`\`
</pre>
<p>【注意】通过JavaScript动态插入st-text框的内容同步到st酒馆的输入框需要处理时间,如果需要同步,请添加一个小延迟来确保文本有时间进行同步.</p>
<a href="https://discord.com/channels/1134557553011998840/1271783456690409554" target="_blank"> →Discord教程帖指路← 有详细说明与gal界面等模版 </a>
</div>
`;
document.body.appendChild(settingsPanel);
}
function createEdgeControls() {
edgeControls = document.createElement('div');
edgeControls.id = 'html-injector-edge-controls';
edgeControls.innerHTML = `
<div id="html-injector-drag-handle">
<div class="drag-dots"></div>
</div>
<label class="html-injector-switch">
<input type="checkbox" id="edge-injection-toggle">
<span class="html-injector-slider"></span>
</label>
<button id="html-injector-toggle-panel" class="html-injector-button menu_button">显示面板</button>
`;
document.body.appendChild(edgeControls);
// 创建拖拽点
const dragDots = edgeControls.querySelector('.drag-dots');
for (let i = 0; i < 3; i++) {
const column = document.createElement('div');
column.style.display = 'flex';
column.style.flexDirection = 'column';
column.style.justifyContent = 'space-between';
column.style.height = '15px'; // 调整高度以适应三个点
for (let j = 0; j < 2; j++) {
const dot = document.createElement('div');
dot.style.width = '4px';
dot.style.height = '4px';
dot.style.borderRadius = '50%';
dot.style.backgroundColor = 'var(--smart-theme-body-color)';
column.appendChild(dot);
}
dragDots.appendChild(column);
}
const toggleEdgeControlsButton = document.createElement('button');
toggleEdgeControlsButton.id = 'toggle-edge-controls';
toggleEdgeControlsButton.textContent = '>>';
toggleEdgeControlsButton.style.cssText = `
position: absolute;
left: -20px;
top: 50%;
transform: translateY(-50%);
background-color: var(--SmartThemeBlurTintColor, rgba(22, 11, 18, 0.73));
color: var(--SmartThemeBodyColor, rgba(220, 220, 210, 1));
border: 1px solid var(--SmartThemeBorderColor, rgba(217, 90, 157, 0.5));
border-radius: 5px 0 0 5px;
cursor: pointer;
padding: 5px;
user-select: none;
font-size: 12px;
height: 60px;
`;
edgeControls.appendChild(toggleEdgeControlsButton);
updateEdgeControlsPosition(savedPosition);
updateEdgeControlsDisplay();
}
// ---------------------------------------- 事件监听器 ----------------------------------------
function addEventListeners() {
addSettingsPanelListeners();
addEdgeControlsListeners();
addGlobalListeners();
document.getElementsByName('display-mode').forEach(radio => {
radio.addEventListener('change', handleDisplayModeChange);
});
}
function addSettingsPanelListeners() {
document.getElementById('activation-mode').addEventListener('change', handleActivationModeChange);
document.getElementById('custom-start-floor').addEventListener('change', handleCustomStartFloorChange);
document.getElementById('custom-end-floor').addEventListener('change', handleCustomEndFloorChange);
document.getElementById('last-n-floors').addEventListener('change', handleLastNFloorsChange);
document.getElementsByName('display-mode').forEach(radio => {
radio.addEventListener('change', handleDisplayModeChange);
});
document.getElementById('html-injector-close-settings').addEventListener('click', toggleSettingsPanel);
}
function addEdgeControlsListeners() {
document.getElementById('edge-injection-toggle').addEventListener('change', handleToggleChange);
document.getElementById('html-injector-toggle-panel').addEventListener('click', toggleSettingsPanel);
document.getElementById('toggle-edge-controls').addEventListener('click', toggleEdgeControls);
addDragFunctionality();
}
function addGlobalListeners() {
window.addEventListener('message', handleMessage);
window.addEventListener('resize', handleResize);
window.matchMedia('(prefers-color-scheme: dark)').addListener(updateAllIframesTheme);
}
function addDragFunctionality() {
const dragHandle = document.getElementById('html-injector-drag-handle');
const edgeControls = document.getElementById('html-injector-edge-controls');
let isDragging = false;
let startY, startTop;
function handleDragStart(e) {
isDragging = true;
startY = e.type.includes('mouse') ? e.clientY : e.touches[0].clientY;
startTop = edgeControls.offsetTop;
e.preventDefault();
}
function handleDragMove(e) {
if (!isDragging) return;
const clientY = e.type.includes('mouse') ? e.clientY : e.touches[0].clientY;
let newTop = startTop + (clientY - startY);
newTop = Math.max(0, Math.min(newTop, window.innerHeight - edgeControls.offsetHeight));
edgeControls.style.top = newTop + 'px';
}
function handleDragEnd() {
isDragging = false;
}
dragHandle.addEventListener('mousedown', handleDragStart);
dragHandle.addEventListener('touchstart', handleDragStart);
document.addEventListener('mousemove', handleDragMove);
document.addEventListener('touchmove', handleDragMove);
document.addEventListener('mouseup', handleDragEnd);
document.addEventListener('touchend', handleDragEnd);
}
// ---------------------------------------- 观察器和定时器 ----------------------------------------
function startObservers() {
const observer = new MutationObserver(handleDOMMutations);
observer.observe(document.body, { childList: true, subtree: true });
setInterval(checkLastMesTextChange, 2000);
}
// ---------------------------------------- 核心功能函数 ----------------------------------------
function injectHtmlCode(specificMesText = null) {
try {
let mesTextElements = specificMesText ? [specificMesText] : Array.from(document.getElementsByClassName('mes_text'));
let targetElements;
switch (activationMode) {
case 'first':
targetElements = mesTextElements.slice(0, 1);
break;
case 'last':
targetElements = mesTextElements.slice(-1);
break;
case 'lastN':
targetElements = mesTextElements.slice(-customEndFloor);
break;
case 'custom': {
const start = customStartFloor - 1;
const end = customEndFloor === -1 ? undefined : customEndFloor;
targetElements = mesTextElements.slice(start, end);
break;
}
default: // 'all'
targetElements = mesTextElements;
}
for (const mesText of targetElements) {
const codeElements = mesText.getElementsByTagName('code');
for (const codeElement of codeElements) {
let htmlContent = codeElement.innerText.trim();
if (htmlContent.startsWith('<') && htmlContent.endsWith('>')) {
const iframe = document.createElement('iframe');
iframe.style.width = '100%';
iframe.style.border = 'none';
iframe.style.marginTop = '10px';
iframe.srcdoc = `
<html>
<head>
<style>
/* 自定义样式 */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.1);
border-radius: 4px;
}
::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.3);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(0, 0, 0, 0.5);
}
[data-theme="dark"] ::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.1);
}
[data-theme="dark"] ::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.3);
}
[data-theme="dark"] ::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.5);
}
.container[data-theme="light"] {
--bg-color: rgba(240, 240, 255, 0.1);
--text-color: #1e1e1e;
--border-color: rgba(139,226,115,0.3);
--nav-bg-color: rgba(240,240,255,0.4);
}
.container[data-theme="dark"] {
--bg-color: rgba(40, 40, 40, 0.2);
--text-color: #e0e0e0;
--border-color: rgba(74,74,74,0.3);
--nav-bg-color: rgba(30,30,30,0.4);
}
.container {
background-color: var(--bg-color);
color: var(--text-color);
}
.container .left-nav {
background-color: var(--nav-bg-color);
}
.container .button, .container .left-nav .section {
border: 1px solid var(--border-color);
}
</style>
</head>
<body>
<div class="theme-content">
${htmlContent}
</div>
<script>
window.addEventListener('load', function() {
window.parent.postMessage('loaded', '*');
const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
function handleThemeChange(e) {
document.body.setAttribute('data-theme', e.matches ? 'dark' : 'light');
window.parent.postMessage({type: 'themeChange', theme: e.matches ? 'dark' : 'light'}, '*');
}
darkModeMediaQuery.addListener(handleThemeChange);
handleThemeChange(darkModeMediaQuery);
document.querySelectorAll('.qr-button').forEach(button => {
button.addEventListener('click', function() {
const buttonName = this.textContent.trim();
window.parent.postMessage({type: 'buttonClick', name: buttonName}, '*');
});
});
document.querySelectorAll('.st-text').forEach(textarea => {
textarea.addEventListener('input', function() {
window.parent.postMessage({type: 'textInput', text: this.value}, '*');
});
textarea.addEventListener('change', function() {
window.parent.postMessage({type: 'textInput', text: this.value}, '*');
});
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'attributes' && mutation.attributeName === 'value') {
window.parent.postMessage({type: 'textInput', text: textarea.value}, '*');
}
});
});
observer.observe(textarea, { attributes: true });
});
document.querySelectorAll('.st-send-button').forEach(button => {
button.addEventListener('click', function() {
window.parent.postMessage({type: 'sendClick'}, '*');
});
});
});
window.addEventListener('message', function(event) {
if (event.data.type === 'themeChange') {
document.body.setAttribute('data-theme', event.data.theme);
}
});
</script>
</body>
</html>
`;
if (displayMode === 2) {
const details = document.createElement('details');
const summary = document.createElement('summary');
summary.textContent = '[原代码]';
details.appendChild(summary);
codeElement.parentNode.insertBefore(details, codeElement);
details.appendChild(codeElement);
} else if (displayMode === 3) {
codeElement.style.display = 'none';
}
codeElement.parentNode.insertBefore(iframe, codeElement.nextSibling);
iframe.onload = function () {
adjustIframeHeight(iframe);
setTimeout(() => adjustIframeHeight(iframe), 500);
};
if (iframe.contentWindow) {
const resizeObserver = new ResizeObserver(() => adjustIframeHeight(iframe));
resizeObserver.observe(iframe.contentWindow.document.body);
}
}
}
}
} catch (error) {
console.error('HTML注入失败:', error);
}
}
function removeInjectedIframes() {
const iframes = document.querySelectorAll('.mes_text iframe');
iframes.forEach(iframe => iframe.remove());
const codeElements = document.querySelectorAll('.mes_text code');
codeElements.forEach(code => {
code.style.display = '';
const details = code.closest('details');
if (details) {
details.parentNode.insertBefore(code, details);
details.remove();
}
});
}
// ---------------------------------------- 辅助函数 ----------------------------------------
function adjustIframeHeight(iframe) {
try {
if (iframe.contentWindow.document.body) {
const height = iframe.contentWindow.document.documentElement.scrollHeight;
iframe.style.height = (height + 5) + 'px';
}
} catch (error) {
console.error('调整iframe高度失败:', error);
}
}
function getSystemTheme() {
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
function updateAllIframesTheme() {
const iframes = document.querySelectorAll('.mes_text iframe');
iframes.forEach(iframe => {
try {
if (iframe.contentWindow) {
iframe.contentWindow.postMessage({ type: 'themeChange', theme: getSystemTheme() }, '*');
}
} catch (error) {
console.error('更新iframe主题失败:', error);
}
});
}
function updateEdgeControlsPosition(position) {
if (!edgeControls) return;
switch (position) {
case 'top-right':
edgeControls.style.top = '20vh';
edgeControls.style.transform = 'none';
break;
case 'right-three-quarters':
edgeControls.style.top = '75vh';
edgeControls.style.transform = 'none';
break;
case 'right-middle':
edgeControls.style.top = '50%';
edgeControls.style.transform = 'translateY(-50%)';
break;
}
edgeControls.style.right = isEdgeControlsCollapsed ? '-100px' : '0';
GM_setValue('edgeControlsPosition', position);
}
function updateEdgeControlsDisplay() {
if (!edgeControls) return;
edgeControls.style.right = isEdgeControlsCollapsed ? '-100px' : '0';
const toggleButton = document.getElementById('toggle-edge-controls');
if (toggleButton) {
toggleButton.textContent = isEdgeControlsCollapsed ? '<<' : '>>';
}
}
function toggleSettingsPanel() {
const isVisible = settingsPanel.style.display === 'block';
settingsPanel.style.display = isVisible ? 'none' : 'block';
document.getElementById('html-injector-toggle-panel').textContent = isVisible ? '显示面板' : '隐藏面板';
}
function toggleEdgeControls() {
isEdgeControlsCollapsed = !isEdgeControlsCollapsed;
GM_setValue('isEdgeControlsCollapsed', isEdgeControlsCollapsed);
updateEdgeControlsDisplay();
}
function handleResize() {
updateEdgeControlsPosition(savedPosition);
}
function handleActivationModeChange() {
const customSettings = document.getElementById('custom-floor-settings');
const lastNSettings = document.getElementById('last-n-settings');
customSettings.style.display = this.value === 'custom' ? 'block' : 'none';
lastNSettings.style.display = this.value === 'lastN' ? 'block' : 'none';
activationMode = this.value;
GM_setValue('activationMode', activationMode);
if (isInjectionEnabled) {
removeInjectedIframes();
injectHtmlCode();
}
}
function handleCustomStartFloorChange() {
customStartFloor = parseInt(this.value);
GM_setValue('customStartFloor', customStartFloor);
if (isInjectionEnabled) {
removeInjectedIframes();
injectHtmlCode();
}
}
function handleCustomEndFloorChange() {
customEndFloor = parseInt(this.value);
GM_setValue('customEndFloor', customEndFloor);
if (isInjectionEnabled) {
removeInjectedIframes();
injectHtmlCode();
}
}
function handleLastNFloorsChange() {
customEndFloor = parseInt(this.value);
GM_setValue('customEndFloor', customEndFloor);
if (isInjectionEnabled) {
removeInjectedIframes();
injectHtmlCode();
}
}
function handleDisplayModeChange(event) {
displayMode = parseInt(event.target.value);
GM_setValue('displayMode', displayMode);
if (isInjectionEnabled) {
removeInjectedIframes();
injectHtmlCode();
}
}
function handleToggleChange(e) {
isInjectionEnabled = e.target.checked;
document.getElementById('edge-injection-toggle').checked = isInjectionEnabled;
if (isInjectionEnabled) {
injectHtmlCode();
} else {
removeInjectedIframes();
}
}
function handleMessage(event) {
try {
if (event.data === 'loaded') {
const iframes = document.querySelectorAll('.mes_text iframe');
iframes.forEach(iframe => {
if (iframe.contentWindow === event.source) {
adjustIframeHeight(iframe);
}
});
} else if (event.data.type === 'buttonClick') {
const buttonName = event.data.name;
jQuery('.qr--button.menu_button').each(function () {
if (jQuery(this).find('.qr--button-label').text().trim() === buttonName) {
jQuery(this).click();
return false;
}
});
} else if (event.data.type === 'textInput') {
const sendTextarea = document.getElementById('send_textarea');
if (sendTextarea) {
sendTextarea.value = event.data.text;
sendTextarea.dispatchEvent(new Event('input', { bubbles: true }));
sendTextarea.dispatchEvent(new Event('change', { bubbles: true }));
}
} else if (event.data.type === 'sendClick') {
const sendButton = document.getElementById('send_but');
if (sendButton) {
sendButton.click();
}
}
} catch (error) {
console.error('处理消息失败:', error);
}
}
function handleDOMMutations(mutations) {
for (const mutation of mutations) {
if (mutation.type === 'childList') {
for (const node of mutation.addedNodes) {
if (node.nodeType === Node.ELEMENT_NODE &&
(node.classList.contains('mes_text') || node.querySelector('.mes_text'))) {
if (isInjectionEnabled) {
injectHtmlCode();
}
break;
}
}
}
}
}
function checkLastMesTextChange() {
const mesTextElements = document.getElementsByClassName('mes_text');
if (mesTextElements.length > 0) {
const lastMesText = mesTextElements[mesTextElements.length - 1];
const codeElement = lastMesText.querySelector('code');
if (codeElement) {
const currentContent = codeElement.innerText.trim();
const injectedIframe = lastMesText.querySelector('iframe');
if (currentContent !== lastMesTextContent || (isInjectionEnabled && !injectedIframe)) {
lastMesTextContent = currentContent;
if (isInjectionEnabled) {
if (injectedIframe) {
injectedIframe.remove();
}
injectHtmlCode(lastMesText);
const newIframe = lastMesText.querySelector('iframe');
if (newIframe) {
newIframe.onload = function () {
const currentTheme = getSystemTheme();
newIframe.contentWindow.postMessage({ type: 'themeChange', theme: currentTheme }, '*');
};
}
}
}
} else {
if (lastMesTextContent !== '') {
lastMesTextContent = '';
const injectedIframe = lastMesText.querySelector('iframe');
if (injectedIframe) {
injectedIframe.remove();
}
}
}
}
}
// ---------------------------------------- 样式函数 ----------------------------------------
function addStyles() {
const style = document.createElement('style');
style.textContent = `
/* 通用变量 */
:root {
--smart-theme-blur-tint: var(--SmartThemeBlurTintColor, rgba(22, 11, 18, 0.73));
--smart-theme-body-color: var(--SmartThemeBodyColor, rgba(220, 220, 210, 1));
--smart-theme-border-color: var(--SmartThemeBorderColor, rgba(217, 90, 157, 0.5));
--smart-theme-button-bg: var(--SmartThemeButtonBG, rgba(74, 74, 74, 0.5));
--smart-theme-button-hover-bg: var(--SmartThemeButtonHoverBG, rgba(90, 90, 90, 0.7));
--smart-theme-blur-strength: var(--SmartThemeBlurStrength, 6px);
}
/* 边缘控制面板样式 */
#html-injector-edge-controls {
position: fixed;
right: -80px;
top: 20vh;
transition: right 0.3s ease-in-out;
background-color: var(--smart-theme-blur-tint);
border: 1px solid var(--smart-theme-border-color);
border-radius: 10px 0 0 10px;
padding: 10px;
z-index: 9999;
display: flex;
flex-direction: column;
align-items: center;
width: 100px;
color: var(--smart-theme-body-color);
backdrop-filter: blur(var(--smart-theme-blur-strength));
}
/* 开关样式 */
#html-injector-edge-controls .html-injector-switch {
position: relative;
display: inline-block;
width: 50px;
height: 24px;
}
#html-injector-edge-controls .html-injector-switch input {
opacity: 0;
width: 0;
height: 0;
}
#html-injector-edge-controls .html-injector-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(128, 128, 128, 0.3);
transition: .2s;
border-radius: 24px;
}
#html-injector-edge-controls .html-injector-slider:before {
position: absolute;
content: "";
height: 18px;
width: 18px;
left: 3px;
bottom: 3px;
background-color: var(--smart-theme-body-color);
transition: .2s;
border-radius: 50%;
}
#html-injector-edge-controls .html-injector-switch input:checked + .html-injector-slider {
background-color: var(--smart-theme-border-color);
}
#html-injector-edge-controls .html-injector-switch input:checked + .html-injector-slider:before {
transform: translateX(26px);
}
/* 按钮样式 */
#html-injector-edge-controls .html-injector-button {
font-size: 14px;
padding: 5px 10px;
margin-top: 10px;
width: 100%;
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
background-color: var(--smart-theme-button-bg);
color: var(--smart-theme-body-color);
border: 1px solid var(--smart-theme-border-color);
border-radius: 5px;
transition: background-color 0.3s, color 0.3s;
}
#html-injector-edge-controls .html-injector-button:hover {
background-color: var(--smart-theme-button-hover-bg);
}
/* 拖拽句柄样式 */
#html-injector-edge-controls #html-injector-drag-handle {
width: 100%;
height: 20px;
background-color: var(--smart-theme-border-color);
cursor: ns-resize;
margin-bottom: 10px;
border-radius: 5px 5px 0 0;
display: flex;
justify-content: center;
align-items: center;
}
#html-injector-edge-controls #html-injector-drag-handle .drag-dots {
display: flex;
justify-content: space-between;
width: 20px;
height: 15px;
}
#html-injector-edge-controls #html-injector-drag-handle .drag-dots > div {
display: flex;
flex-direction: column;
justify-content: space-between;
}
#html-injector-edge-controls #html-injector-drag-handle .drag-dots > div > div {
width: 4px;
height: 4px;
border-radius: 50%;
background-color: var(--smart-theme-body-color);
}
#html-injector-edge-controls #html-injector-drag-handle:hover .drag-dots > div > div {
background-color: var(--smart-theme-button-hover-bg);
}
/* 设置面板样式 */
#html-injector-settings {
position: fixed;
top: 3vh;
left: 50%;
transform: translateX(-50%);
width: 90%;
max-width: 800px;
height: auto;
max-height: 90vh;
background-color: var(--smart-theme-blur-tint);
border: 1px solid var(--smart-theme-border-color);
border-radius: 10px;
padding: 20px;
z-index: 10000;
color: var(--smart-theme-body-color);
backdrop-filter: blur(10px);
display: flex;
flex-direction: column;
overflow-y: auto;
}
#html-injector-settings-header {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 15px;
border-bottom: 1px solid var(--smart-theme-border-color);
}
#html-injector-close-settings {
cursor: pointer;
font-size: 24px;
}
#settings-content {
flex-grow: 1;
overflow-y: auto;
padding-right: 10px;
margin-top: 15px;
max-height: calc(85vh - 150px);
}
.settings-footer {
margin-top: 15px;
padding-top: 15px;
border-top: 1px solid var(--smart-theme-border-color);
}
/* 滚动条样式 */
#settings-content::-webkit-scrollbar {
width: 8px;
}
#settings-content::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.1);
border-radius: 4px;
}
#settings-content::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.3);
border-radius: 4px;
}
#settings-content::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.5);
}
/* 表单元素样式 */
#html-injector-settings #settings-content label {
display: block;
margin: 10px 0;
color: var(--smart-theme-body-color);
}
#html-injector-settings #settings-content input[type="radio"] {
margin-right: 5px;
}
#html-injector-settings #settings-content input[type="number"],
#html-injector-settings #activation-mode,
#html-injector-settings .theme-element {
background-color: var(--smart-theme-blur-tint);
color: var(--smart-theme-body-color);
border: 1px solid var(--smart-theme-border-color);
padding: 5px;
border-radius: 3px;
}
#html-injector-settings #settings-content input[type="number"] {
width: 50px;
margin: 0 5px;
}
#html-injector-settings #settings-content input[type="number"]:focus,
#html-injector-settings #activation-mode:focus,
#html-injector-settings .theme-element:focus {
outline: none;
border-color: #0e639c;
}
#html-injector-settings .theme-element option {
background-color: var(--smart-theme-blur-tint);
}
/* 其他样式 */
#html-injector-settings .settings-section {
margin-bottom: 15px;
}
#html-injector-settings .settings-subtitle {
font-size: 14px;
margin: 0 0 5px 0;
color: var(--smart-theme-body-color);
}
#html-injector-settings .settings-option {
display: block;
margin: 5px 0;
font-size: 13px;
}
#html-injector-settings .settings-select {
width: 100%;
margin-bottom: 5px;
}
#html-injector-settings .settings-subsection {
margin-top: 5px;
padding-left: 10px;
}
#html-injector-settings .settings-note {
font-size: 12px;
color: #858585;
margin: 2px 0;
}
#html-injector-settings .settings-footer {
font-size: 12px;
color: #858585;
margin-top: 15px;
}
#html-injector-settings .code-example {
background-color: var(--smart-theme-blur-tint);
padding: 10px;
border-radius: 3px;
overflow-x: auto;
font-size: 12px;
color: var(--smart-theme-body-color);
}
/* 响应式设计 */
@media (max-width: 1000px) {
#html-injector-settings {
max-width: none;
height: 50vh;
max-height: none;
}
#settings-content {
max-height: calc(80vh - 180px);
}
#html-injector-edge-controls {
font-size: 10px;
min-width: 100px;
}
#html-injector-edge-controls button {
font-size: 12px;
padding: 6px 10px;
}
#html-injector-edge-controls .html-injector-switch {
width: 50px;
height: 28px;
}
#html-injector-edge-controls .html-injector-slider:before {
height: 20px;
width: 20px;
}
#html-injector-edge-controls .html-injector-switch input:checked + .html-injector-slider:before {
transform: translateX(22px);
}
}
`;
document.head.appendChild(style);
}
// ---------------------------------------- 初始化调用 ----------------------------------------
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initScript);
} else {
initScript();
}
})();