TriX Executor's ChatBox (for territorial.io)!
目前為
// ==UserScript==
// @name TrixBox
// @namespace http://tampermonkey.net/
// @version 0.3.6
// @description TriX Executor's ChatBox (for territorial.io)!
// @author Painsel
// @match https://territorial.io/*
// @match https://fxclient.github.io/FXclient/*
// @match https://discord.com/*
// @grant GM_xmlhttpRequest
// @grant GM_info
// ==/UserScript==
/*
Copyright (c) 2025 Painsel
All Rights Reserved.
This script is proprietary software. You may not use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software.
UNAUTHORIZED COPYING, DISTRIBUTION, OR MODIFICATION OF THIS SCRIPT,
EITHER IN WHOLE OR IN PART, IS STRICTLY PROHIBITED.
*/
(function() {
'use strict';
// --- Theme Colors (for UI elements outside the iframe) ---
const theme = {
icon: '#2f3136',
modalBg: '#2f3136',
text: '#dcddde',
buttonPrimary: '#5865f2',
buttonSecondary: '#4f545c'
};
const SCRIPT_UPDATE_URL = 'https://update.greasyfork.org/scripts/555536/TrixBox.meta.js';
const SCRIPT_INSTALL_URL = 'https://update.greasyfork.org/scripts/555536/TrixBox.user.js';
// --- 1. UPDATE CHECKING LOGIC ---
const showUpdateModal = () => {
const modalStyle = `
.trixbox-modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); z-index: 100000; display: flex; align-items: center; justify-content: center; }
.trixbox-modal-content { background: ${theme.modalBg}; color: ${theme.text}; padding: 25px; border-radius: 10px; text-align: center; font-family: sans-serif; box-shadow: 0 5px 15px rgba(0,0,0,0.5); }
.trixbox-modal-content h2 { margin-top: 0; }
.trixbox-modal-content p { margin: 15px 0; }
.trixbox-modal-btn { color: white; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer; margin: 0 10px; font-size: 14px; }
.trixbox-modal-btn.primary { background: ${theme.buttonPrimary}; }
.trixbox-modal-btn.secondary { background: ${theme.buttonSecondary}; }
`;
const styleSheet = document.createElement("style");
styleSheet.innerText = modalStyle;
document.head.appendChild(styleSheet);
const modalOverlay = document.createElement('div');
modalOverlay.className = 'trixbox-modal-overlay';
modalOverlay.innerHTML = `
<div class="trixbox-modal-content">
<h2>OUTDATED VERSION</h2>
<p>You are using an Outdated version of TrixBox.<br>Please update for the best experience!</p>
<button id="trixbox-update-btn" class="trixbox-modal-btn primary">Update Now!</button>
<button id="trixbox-later-btn" class="trixbox-modal-btn secondary">Remind Me Later!</button>
</div>
`;
document.body.appendChild(modalOverlay);
document.getElementById('trixbox-update-btn').onclick = () => { window.location.href = SCRIPT_INSTALL_URL; };
document.getElementById('trixbox-later-btn').onclick = () => { modalOverlay.remove(); };
};
const checkForUpdates = () => {
try {
const localVersion = GM_info.script.version;
GM_xmlhttpRequest({
method: 'GET',
url: SCRIPT_UPDATE_URL,
onload: function(response) {
if (response.status !== 200) return;
const remoteVersionMatch = response.responseText.match(/@version\s+([0-9.]+)/);
if (remoteVersionMatch && remoteVersionMatch[1]) {
if (remoteVersionMatch[1] > localVersion) {
showUpdateModal();
}
}
}
});
} catch (e) { console.error('TrixBox: Update check failed.', e); }
};
// --- 2. CHATTABLE CUSTOM COMMANDS ---
// Initialize custom commands object before chat loads
window.chattable = window.chattable || {};
window.chattable.commands = {
cf: function() {
const result = Math.random() < 0.5 ? 'Heads' : 'Tails';
if (typeof chattable !== 'undefined' && chattable.sendMessage) {
chattable.sendMessage(`🪙 Coinflip Result: **${result}**`);
}
},
coinflip: function() {
const result = Math.random() < 0.5 ? 'Heads' : 'Tails';
if (typeof chattable !== 'undefined' && chattable.sendMessage) {
chattable.sendMessage(`🪙 Coinflip Result: **${result}**`);
}
},
userinfo: function() {
if (typeof chattable !== 'undefined' && chattable.sendMessage) {
chattable.sendMessage('📊 User info command executed!');
}
},
stats: function(fullstring) {
if (typeof chattable !== 'undefined' && chattable.sendMessage) {
chattable.sendMessage('📈 Stats: Chat is fully loaded and operational!');
}
}
};
// --- 2A. THEME STORAGE AND SELECTOR ---
const themes = [
'Amber', 'Amdroid', 'Comment Section', 'Confidential', 'Glass',
'Hacker Terminal', 'Kick', 'Moderno', 'Notepad', 'Pastel Pink',
'Retrowave Red', 'Tendo', 'Wannabe XP', 'Professional', 'Bytechat'
];
let currentTheme = localStorage.getItem('trixbox-theme') || 'Tendo';
let notificationSound = localStorage.getItem('trixbox-notification-sound') || 'default';
let customCSSEnabled = localStorage.getItem('trixbox-custom-css') === 'true';
const applyTheme = (themeName) => {
localStorage.setItem('trixbox-theme', themeName);
currentTheme = themeName;
// Defer initialization until library is loaded
setTimeout(() => {
if (typeof chattable !== 'undefined' && chattable.initialize) {
chattable.initialize({
theme: themeName.toLowerCase().replace(/\s+/g, ''),
stylesheet: customCSSEnabled ? localStorage.getItem('trixbox-custom-styles') : undefined
});
}
}, 100);
};
// CSS Styling configuration
const customStyleManager = {
getDefaultStyles: () => `
/* TrixBox Default Styles */
:root {
--notification-sfx: ${notificationSound === 'none' ? 'none' : 'default'};
--advanced-layout: true;
}
/* Message styling */
.allMessages {
padding: 8px;
border-radius: 4px;
margin: 4px 0;
}
.allMessages.sent {
background: rgba(88, 101, 242, 0.1);
}
.allMessages.received {
background: rgba(79, 84, 92, 0.1);
}
/* Badge styling */
.mod, .owner, .beta {
border-radius: 3px;
padding: 2px 6px;
font-size: 11px;
font-weight: bold;
}
/* Input styling */
#input {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
font-size: 14px;
}
#input:empty::before {
content: "Type a message...";
color: #72767d;
}
/* Settings and UI elements */
#settings {
opacity: 0.8;
transition: opacity 0.2s;
}
#settings:hover {
opacity: 1;
}
/* Timestamp styling */
#timestamp {
font-size: 11px;
color: #72767d;
}
/* Reply styling */
.reply {
border-left: 3px solid #5865f2;
padding-left: 8px;
opacity: 0.8;
}
/* Context menu */
.ctxMenuOption {
padding: 8px 12px;
border-radius: 4px;
transition: background 0.15s;
}
.ctxMenuOption:hover {
background: #5865f2;
}
/* Emoji styling */
.emoji {
transition: transform 0.2s;
}
.emoji:hover {
transform: scale(1.2);
}
/* Load more button */
#loadMore {
background: #5865f2;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
transition: background 0.2s;
}
#loadMore:hover {
background: #4752c4;
}
/* Scroll to bottom button */
#scrollToBottom {
background: #5865f2;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
}
/* Pinned messages */
.pinned {
background: rgba(88, 101, 242, 0.2);
border-left: 4px solid #5865f2;
padding: 8px;
}
/* Highlight mentions */
.highlight {
background: #faa61a;
padding: 0 4px;
border-radius: 3px;
font-weight: bold;
}
/* Reply banner */
#replyBanner {
background: #2f3136;
border-top: 2px solid #5865f2;
padding: 8px 12px;
font-size: 13px;
}
/* Emoji tray */
#emojiTray {
background: #2f3136;
border: 1px solid #202225;
}
.emojiInGrid {
padding: 6px;
border-radius: 4px;
cursor: pointer;
transition: transform 0.1s;
}
.emojiInGrid:hover {
transform: scale(1.15);
}
#emojiTrayToggle {
opacity: 0.8;
transition: opacity 0.2s;
}
#emojiTrayToggle:hover {
opacity: 1;
}
/* Typing indicator */
#is_typing {
font-style: italic;
color: #72767d;
}
/* Tutorial */
#shadow {
display: none;
}
/* Settings menu */
#settingsWindow {
background: #2f3136;
}
#settingsMenu {
background: #36393f;
color: #dcddde;
}
#account, #name, #dltranscript, #save, #closeSettingsBtn {
background: #5865f2;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
transition: background 0.2s;
}
#account:hover, #name:hover, #dltranscript:hover, #save:hover, #closeSettingsBtn:hover {
background: #4752c4;
}
/* Dialogue modal */
.dialogueModal {
background: #36393f;
color: #dcddde;
border-radius: 8px;
padding: 20px;
}
/* Background */
#background {
background-color: #36393f;
}
/* Top banner */
#top_banner {
background: linear-gradient(135deg, #5865f2, #4752c4);
}
`,
applyCustomStyles: (css) => {
localStorage.setItem('trixbox-custom-styles', css);
// Defer initialization until library is loaded
setTimeout(() => {
if (typeof chattable !== 'undefined' && chattable.initialize) {
chattable.initialize({
stylesheet: css
});
}
}, 100);
},
resetStyles: () => {
localStorage.removeItem('trixbox-custom-styles');
customCSSEnabled = false;
localStorage.setItem('trixbox-custom-css', 'false');
applyTheme(currentTheme);
}
};
// --- 2B. PROFILE PICTURES STORAGE ---
let profilePictures = JSON.parse(localStorage.getItem('trixbox-profiles')) || {};
const setProfilePicture = (userId, imageUrl) => {
profilePictures[userId] = imageUrl;
localStorage.setItem('trixbox-profiles', JSON.stringify(profilePictures));
};
const getProfilePicture = (userId) => {
return profilePictures[userId] || null;
};
// --- 2C. EMBEDS STORAGE ---
let embeds = JSON.parse(localStorage.getItem('trixbox-embeds')) || {};
const addEmbed = (messageId, embedData) => {
embeds[messageId] = embedData;
localStorage.setItem('trixbox-embeds', JSON.stringify(embeds));
};
const getEmbed = (messageId) => {
return embeds[messageId] || null;
};
// --- 2D. FILE ATTACHMENTS STORAGE ---
let attachments = JSON.parse(localStorage.getItem('trixbox-attachments')) || {};
const addAttachment = (messageId, fileData) => {
attachments[messageId] = fileData;
localStorage.setItem('trixbox-attachments', JSON.stringify(attachments));
};
const getAttachment = (messageId) => {
return attachments[messageId] || null;
};
// --- 2E. CHANGELOG MODAL ---
const showChangelogModal = () => {
const changelog = `
<h2>TrixBox v0.3.2 - Changelog</h2>
<div style="text-align: left; font-size: 13px; line-height: 1.6;">
<h3 style="color: #faa61a;">🔧 v0.3.2 Bug Fixes & Improvements:</h3>
<ul>
<li><strong>Fixed Chattable Initialization Error:</strong> Resolved "chattable.initialize is not a function" error</li>
<li><strong>Improved Library Loading:</strong> Added retry mechanism with exponential backoff</li>
<li><strong>Better Error Handling:</strong> Deferred initialization prevents timing issues</li>
<li><strong>Theme Loading Optimization:</strong> Themes now load reliably on first attempt</li>
<li><strong>Custom Styles Support:</strong> CSS application now works correctly with library lifecycle</li>
</ul>
<h3 style="color: #faa61a;">🆕 v0.3.1 Updates:</h3>
<ul>
<li><strong>Improved Settings Modal:</strong> Better organization with expandable sections</li>
<li><strong>Enhanced CSS Editor:</strong> Syntax highlighting for CSS with better textarea</li>
<li><strong>Chattable API Integration:</strong> Full support for custom commands and themes</li>
<li><strong>Sound Testing:</strong> Preview notification sounds before saving</li>
<li><strong>Settings Info Panel:</strong> View current theme, CSS status, and notification settings</li>
</ul>
<h3>✨ v0.3.0 Features:</h3>
<ul>
<li><strong>!cf / !coinflip Command:</strong> Flip a coin in chat with !cf or !coinflip</li>
<li><strong>Custom Commands API:</strong> Integrated Chattable API for custom command system</li>
<li><strong>Custom Profile Pictures:</strong> Set custom profile pictures for users</li>
<li><strong>Embeds Support:</strong> Send rich embeds in messages</li>
<li><strong>File Attachments:</strong> Attach files with .p4 and .mp3 player support</li>
<li><strong>Custom Themes:</strong> Choose from 15 unique themes (Amber, Glass, Hacker Terminal, Kick, Moderno, Notepad, Pastel Pink, Retrowave Red, Tendo, Wannabe XP, Professional, Bytechat, etc)</li>
<li><strong>Advanced CSS Styling:</strong> Edit and apply custom CSS styles directly in settings</li>
<li><strong>Notification Sound Control:</strong> Customize or disable notification sounds</li>
</ul>
<h3>🔧 Improvements:</h3>
<ul>
<li>Integrated Chattable Library with custom command support</li>
<li>Added persistent theme selection using localStorage</li>
<li>Added profile picture and embed storage system</li>
<li>Enhanced chat functionality with command parsing and API integration</li>
<li>Added comprehensive default CSS styles for all chat elements</li>
<li>Settings modal now includes advanced customization options</li>
</ul>
<h3>📝 Technical:</h3>
<ul>
<li>Version bumped from 0.2.9 to 0.3.1</li>
<li>Chattable API fully integrated for theme and custom command handling</li>
<li>localStorage optimization for better performance</li>
</ul>
</div>
`;
const modalStyle = `
.trixbox-changelog-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); z-index: 100001; display: flex; align-items: center; justify-content: center; }
.trixbox-changelog-content { background: ${theme.modalBg}; color: ${theme.text}; padding: 30px; border-radius: 10px; text-align: center; font-family: sans-serif; box-shadow: 0 5px 15px rgba(0,0,0,0.5); max-width: 500px; max-height: 600px; overflow-y: auto; }
.trixbox-changelog-content h2 { margin-top: 0; color: ${theme.buttonPrimary}; }
.trixbox-changelog-content h3 { margin: 20px 0 10px 0; color: ${theme.buttonPrimary}; font-size: 15px; }
.trixbox-changelog-content ul { list-style-position: inside; margin: 10px 0; padding: 0; }
.trixbox-changelog-content li { margin: 5px 0; text-align: left; }
.trixbox-changelog-btn { color: white; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer; margin: 10px 10px 0 0; font-size: 14px; background: ${theme.buttonPrimary}; }
`;
if (!document.getElementById('trixbox-changelog-style')) {
const styleSheet = document.createElement("style");
styleSheet.id = 'trixbox-changelog-style';
styleSheet.innerText = modalStyle;
document.head.appendChild(styleSheet);
}
const modalOverlay = document.createElement('div');
modalOverlay.className = 'trixbox-changelog-overlay';
modalOverlay.innerHTML = `
<div class="trixbox-changelog-content">
${changelog}
<button class="trixbox-changelog-btn">Close</button>
</div>
`;
document.body.appendChild(modalOverlay);
modalOverlay.querySelector('.trixbox-changelog-btn').onclick = () => { modalOverlay.remove(); };
modalOverlay.onclick = (e) => {
if (e.target === modalOverlay) modalOverlay.remove();
};
};
// --- 3. CREATE THE TOGGLE ICON ---
const toggleIcon = document.createElement('div');
toggleIcon.id = 'trixbox-toggle-icon';
toggleIcon.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="${theme.text}" width="28px" height="28px"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z"/></svg>`;
// **FIX IS HERE**: Changed z-index to zIndex
Object.assign(toggleIcon.style, {
backgroundColor: theme.icon,
position: 'fixed', bottom: '20px', right: '20px', width: '50px', height: '50px',
borderRadius: '50%', display: 'flex', alignItems: 'center',
justifyContent: 'center', cursor: 'pointer', zIndex: '99998',
boxShadow: '0 2px 8px rgba(0,0,0,0.3)', transition: 'transform 0.2s ease-in-out'
});
toggleIcon.onmouseover = () => { toggleIcon.style.transform = 'scale(1.1)'; };
toggleIcon.onmouseout = () => { toggleIcon.style.transform = 'scale(1.0)'; };
document.body.appendChild(toggleIcon);
// --- 3. CREATE THE CHATBOX AND LINK BLOCKERS ---
const chatContainer = document.createElement('div');
chatContainer.id = 'trixbox-container';
Object.assign(chatContainer.style, {
position: 'fixed', bottom: '15px', right: '15px', width: '350px', height: '500px',
zIndex: '99999', display: 'none', flexDirection: 'column',
boxShadow: '0 4px 12px rgba(0,0,0,0.3)', borderRadius: '8px', overflow: 'hidden'
});
const dragHeader = document.createElement('div');
dragHeader.id = 'trixbox-header';
Object.assign(dragHeader.style, {
height: '30px', backgroundColor: 'rgb(210, 210, 255)', cursor: 'move',
userSelect: 'none', display: 'flex', alignItems: 'center',
justifyContent: 'space-between', padding: '0 5px 0 15px'
});
const headerTitle = document.createElement('span');
headerTitle.textContent = 'TrixBox Chat';
Object.assign(headerTitle.style, {
color: 'white', fontWeight: 'bold', fontSize: '14px',
textShadow: '1px 1px 0 rgb(160, 160, 230), 2px 2px 0 rgba(0, 0, 0, 0.15)'
});
dragHeader.appendChild(headerTitle);
const closeButton = document.createElement('button');
closeButton.innerHTML = '×';
Object.assign(closeButton.style, {
background: 'none', border: 'none', color: 'rgb(80, 80, 120)',
fontSize: '24px', lineHeight: '1', cursor: 'pointer', padding: '0 8px'
});
dragHeader.appendChild(closeButton);
// Settings/Themes button
const settingsBtn = document.createElement('button');
settingsBtn.innerHTML = '⚙️';
Object.assign(settingsBtn.style, {
background: 'none', border: 'none', color: 'rgb(80, 80, 120)',
fontSize: '18px', lineHeight: '1', cursor: 'pointer', padding: '0 8px', marginRight: '5px'
});
dragHeader.insertBefore(settingsBtn, closeButton);
// Changelog button
const changelogBtn = document.createElement('button');
changelogBtn.innerHTML = '📋';
Object.assign(changelogBtn.style, {
background: 'none', border: 'none', color: 'rgb(80, 80, 120)',
fontSize: '18px', lineHeight: '1', cursor: 'pointer', padding: '0 8px', marginRight: '5px'
});
dragHeader.insertBefore(changelogBtn, settingsBtn);
chatContainer.appendChild(dragHeader);
const iframeWrapper = document.createElement('div');
Object.assign(iframeWrapper.style, { position: 'relative', flexGrow: '1' });
chatContainer.appendChild(iframeWrapper);
// --- 3A. ADVANCED SETTINGS MODAL ---
const showAdvancedSettings = () => {
const settingsStyle = `
.trixbox-settings-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); z-index: 100001; display: flex; align-items: center; justify-content: center; }
.trixbox-settings-content { background: ${theme.modalBg}; color: ${theme.text}; padding: 25px; border-radius: 10px; text-align: left; font-family: sans-serif; box-shadow: 0 5px 15px rgba(0,0,0,0.5); max-width: 500px; max-height: 700px; overflow-y: auto; }
.trixbox-settings-content h2 { margin-top: 0; color: ${theme.buttonPrimary}; text-align: center; }
.trixbox-settings-section { margin: 20px 0; padding: 15px; background: ${theme.buttonSecondary}; border-radius: 5px; }
.trixbox-settings-section h3 { margin: 0 0 10px 0; color: ${theme.buttonPrimary}; font-size: 14px; }
.trixbox-settings-label { display: block; margin: 10px 0 5px 0; font-size: 13px; color: ${theme.text}; }
.trixbox-settings-input, .trixbox-settings-select, .trixbox-settings-textarea { width: 100%; padding: 8px; margin: 5px 0 10px 0; background: #36393f; color: ${theme.text}; border: 1px solid #202225; border-radius: 4px; font-family: monospace; font-size: 12px; box-sizing: border-box; }
.trixbox-settings-textarea { min-height: 150px; resize: vertical; }
.trixbox-settings-btn { color: white; border: none; padding: 10px 15px; border-radius: 5px; cursor: pointer; margin: 5px 5px 5px 0; font-size: 13px; }
.trixbox-settings-btn.primary { background: ${theme.buttonPrimary}; }
.trixbox-settings-btn.secondary { background: #5865f2; }
.trixbox-settings-btn.danger { background: #f04747; }
.trixbox-settings-btn:hover { opacity: 0.9; }
.trixbox-sound-preview { padding: 10px; background: #2f3136; border-radius: 4px; margin: 10px 0; }
.trixbox-sound-preview button { padding: 8px 12px; font-size: 12px; }
`;
if (!document.getElementById('trixbox-settings-style')) {
const styleSheet = document.createElement("style");
styleSheet.id = 'trixbox-settings-style';
styleSheet.innerText = settingsStyle;
document.head.appendChild(styleSheet);
}
const modalOverlay = document.createElement('div');
modalOverlay.className = 'trixbox-settings-overlay';
const savedStyles = localStorage.getItem('trixbox-custom-styles') || customStyleManager.getDefaultStyles();
modalOverlay.innerHTML = `
<div class="trixbox-settings-content">
<h2>⚙️ Advanced Settings</h2>
<div class="trixbox-settings-section">
<h3>🔊 Notification Sound</h3>
<label class="trixbox-settings-label">Sound Type:</label>
<select class="trixbox-settings-select" id="notification-sound-select">
<option value="default" ${notificationSound === 'default' ? 'selected' : ''}>Default</option>
<option value="none" ${notificationSound === 'none' ? 'selected' : ''}>Disabled</option>
<option value="custom">Custom URL</option>
</select>
<div class="trixbox-sound-preview">
<button class="trixbox-settings-btn secondary" id="test-sound-btn">🔊 Test Sound</button>
</div>
<label class="trixbox-settings-label">Custom Sound URL (if selected above):</label>
<input type="text" class="trixbox-settings-input" id="custom-sound-url" placeholder="https://example.com/sound.mp3" value="${localStorage.getItem('trixbox-custom-sound-url') || ''}">
</div>
<div class="trixbox-settings-section">
<h3>🎨 Custom CSS Styles</h3>
<p style="font-size: 12px; color: #72767d; margin: 10px 0;">Edit the CSS below to customize chat appearance. Leave blank to use theme defaults.</p>
<textarea class="trixbox-settings-textarea" id="custom-css-input" placeholder="Enter custom CSS here...">${savedStyles}</textarea>
<button class="trixbox-settings-btn primary" id="apply-css-btn">✅ Apply Styles</button>
<button class="trixbox-settings-btn secondary" id="reset-css-btn">↻ Reset to Default</button>
<button class="trixbox-settings-btn secondary" id="load-default-btn">📋 Load Default CSS</button>
</div>
<div class="trixbox-settings-section">
<h3>ℹ️ Settings Info</h3>
<p style="font-size: 12px; color: #72767d; margin: 0;">
Current Theme: <strong>${currentTheme}</strong><br>
Custom Styles: <strong>${customCSSEnabled ? 'Enabled' : 'Disabled'}</strong><br>
Notification: <strong>${notificationSound === 'default' ? 'Default' : notificationSound === 'none' ? 'Disabled' : 'Custom'}</strong>
</p>
</div>
<div style="text-align: center;">
<button class="trixbox-settings-btn primary" id="close-settings-btn">Close Settings</button>
</div>
</div>
`;
document.body.appendChild(modalOverlay);
// Event listeners
document.getElementById('notification-sound-select').addEventListener('change', (e) => {
notificationSound = e.target.value;
localStorage.setItem('trixbox-notification-sound', notificationSound);
});
document.getElementById('test-sound-btn').addEventListener('click', () => {
const audio = new Audio();
if (notificationSound === 'custom') {
audio.src = document.getElementById('custom-sound-url').value;
} else if (notificationSound !== 'none') {
audio.src = 'data:audio/wav;base64,UklGRiYAAABXQVZFZm10IBAAAAABAAEAQB8AAAB9AAACABAAZGF0YQIAAAAAAA==';
}
if (audio.src) audio.play().catch(() => {});
});
document.getElementById('apply-css-btn').addEventListener('click', () => {
const css = document.getElementById('custom-css-input').value;
customStyleManager.applyCustomStyles(css);
customCSSEnabled = true;
localStorage.setItem('trixbox-custom-css', 'true');
alert('✅ Custom styles applied!');
});
document.getElementById('reset-css-btn').addEventListener('click', () => {
customStyleManager.resetStyles();
document.getElementById('custom-css-input').value = customStyleManager.getDefaultStyles();
alert('✅ Styles reset to default theme!');
});
document.getElementById('load-default-btn').addEventListener('click', () => {
document.getElementById('custom-css-input').value = customStyleManager.getDefaultStyles();
});
document.getElementById('custom-sound-url').addEventListener('change', (e) => {
localStorage.setItem('trixbox-custom-sound-url', e.target.value);
});
document.getElementById('close-settings-btn').addEventListener('click', () => {
modalOverlay.remove();
});
modalOverlay.addEventListener('click', (e) => {
if (e.target === modalOverlay) modalOverlay.remove();
});
};
// --- 3A. THEME SELECTOR MODAL ---
const showThemeSelector = () => {
const themeSelectorStyle = `
.trixbox-theme-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); z-index: 100001; display: flex; align-items: center; justify-content: center; }
.trixbox-theme-content { background: ${theme.modalBg}; color: ${theme.text}; padding: 25px; border-radius: 10px; text-align: center; font-family: sans-serif; box-shadow: 0 5px 15px rgba(0,0,0,0.5); max-width: 400px; max-height: 500px; overflow-y: auto; }
.trixbox-theme-content h2 { margin-top: 0; color: ${theme.buttonPrimary}; }
.trixbox-theme-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; margin: 20px 0; }
.trixbox-theme-option { padding: 12px; background: ${theme.buttonSecondary}; border: 2px solid transparent; border-radius: 5px; cursor: pointer; transition: all 0.2s; color: ${theme.text}; }
.trixbox-theme-option:hover { background: ${theme.buttonPrimary}; }
.trixbox-theme-option.active { border-color: ${theme.buttonPrimary}; background: ${theme.buttonPrimary}; }
.trixbox-theme-btn { color: white; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer; margin: 10px 5px 0 0; font-size: 14px; background: ${theme.buttonPrimary}; }
`;
if (!document.getElementById('trixbox-theme-style')) {
const styleSheet = document.createElement("style");
styleSheet.id = 'trixbox-theme-style';
styleSheet.innerText = themeSelectorStyle;
document.head.appendChild(styleSheet);
}
const modalOverlay = document.createElement('div');
modalOverlay.className = 'trixbox-theme-overlay';
let themeOptionsHTML = '<div class="trixbox-theme-grid">';
themes.forEach(t => {
const isActive = t === currentTheme ? 'active' : '';
themeOptionsHTML += `<div class="trixbox-theme-option ${isActive}" data-theme="${t}">${t}</div>`;
});
themeOptionsHTML += '</div>';
modalOverlay.innerHTML = `
<div class="trixbox-theme-content">
<h2>Choose Theme</h2>
${themeOptionsHTML}
<button class="trixbox-theme-btn close-theme-btn">Close</button>
</div>
`;
document.body.appendChild(modalOverlay);
modalOverlay.querySelectorAll('.trixbox-theme-option').forEach(option => {
option.addEventListener('click', () => {
const themeName = option.getAttribute('data-theme');
modalOverlay.querySelectorAll('.trixbox-theme-option').forEach(o => o.classList.remove('active'));
option.classList.add('active');
applyTheme(themeName);
});
});
modalOverlay.querySelector('.close-theme-btn').onclick = () => { modalOverlay.remove(); };
modalOverlay.onclick = (e) => {
if (e.target === modalOverlay) modalOverlay.remove();
};
};
// Create the chat library script
const chatLibraryScript = document.createElement('script');
chatLibraryScript.src = 'https://iframe.chat/scripts/main.min.js';
document.head.appendChild(chatLibraryScript);
// Create the iframe FIRST (before initialization)
const chatIframe = document.createElement('iframe');
chatIframe.src = 'https://iframe.chat/embed?chat=15234533';
chatIframe.id = 'chattable';
Object.assign(chatIframe.style, {
width: '100%', height: '100%', border: 'none',
backgroundColor: 'transparent'
});
iframeWrapper.appendChild(chatIframe);
document.body.appendChild(chatContainer);
// Initialize Chattable after iframe and library are ready
const initializeChattable = () => {
if (window.chattable && window.chattable.initialize && typeof window.chattable.initialize === 'function') {
const initConfig = {
theme: currentTheme.toLowerCase().replace(/\s+/g, '')
};
// Add custom stylesheet if CSS is enabled
const customStyles = localStorage.getItem('trixbox-custom-styles');
if (customCSSEnabled && customStyles) {
initConfig.stylesheet = customStyles;
}
window.chattable.initialize(initConfig);
console.log('TrixBox: Chattable initialized with config:', initConfig);
} else {
// Retry if library not ready
if (!window.chattableInitRetries) {
window.chattableInitRetries = 0;
}
window.chattableInitRetries++;
if (window.chattableInitRetries < 50) {
setTimeout(initializeChattable, 100);
} else {
console.error('TrixBox: Failed to initialize Chattable after 50 attempts');
}
}
};
// Wait for DOM to be fully loaded before initializing
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
setTimeout(initializeChattable, 1000);
});
} else {
setTimeout(initializeChattable, 1000);
}
const headerLinkBlocker = document.createElement('div');
Object.assign(headerLinkBlocker.style, { position: 'absolute', top: '0px', left: '0px', width: '100px', height: '45px', zIndex: '2' });
iframeWrapper.appendChild(headerLinkBlocker);
const settingsLinkBlocker = document.createElement('div');
Object.assign(settingsLinkBlocker.style, {
position: 'absolute', bottom: '12px', left: '50%',
transform: 'translateX(-50%)',
width: '140px',
height: '25px',
zIndex: '2'
});
iframeWrapper.appendChild(settingsLinkBlocker);
// --- 4. ADD FUNCTIONALITY (TOGGLE, DRAG, ETC.) ---
toggleIcon.addEventListener('click', () => { chatContainer.style.display = 'flex'; toggleIcon.style.display = 'none'; });
closeButton.addEventListener('click', (e) => {
e.stopPropagation();
chatContainer.style.display = 'none';
toggleIcon.style.display = 'flex';
});
settingsBtn.addEventListener('click', (e) => {
e.stopPropagation();
showAdvancedSettings();
});
changelogBtn.addEventListener('click', (e) => {
e.stopPropagation();
showChangelogModal();
});
let isDragging = false, offsetX, offsetY;
dragHeader.addEventListener('mousedown', (e) => {
if (e.target !== dragHeader && e.target !== headerTitle) return;
isDragging = true;
offsetX = e.clientX - chatContainer.getBoundingClientRect().left;
offsetY = e.clientY - chatContainer.getBoundingClientRect().top;
chatIframe.style.pointerEvents = 'none';
});
document.addEventListener('mousemove', (e) => {
if (!isDragging) return;
chatContainer.style.left = `${e.clientX - offsetX}px`;
chatContainer.style.top = `${e.clientY - offsetY}px`;
chatContainer.style.bottom = 'auto';
chatContainer.style.right = 'auto';
});
document.addEventListener('mouseup', () => {
if (!isDragging) return;
isDragging = false;
chatIframe.style.pointerEvents = 'auto';
});
// Show changelog on first load or when version changes
const lastSeenVersion = localStorage.getItem('trixbox-version');
if (lastSeenVersion !== '0.3.2') {
setTimeout(() => showChangelogModal(), 2000);
localStorage.setItem('trixbox-version', '0.3.2');
}
// --- 5. RUN THE UPDATE CHECKER ---
checkForUpdates();
})();