Add initial implementation of General Bots Chrome extension with settings and UI enhancements
This commit is contained in:
commit
b939138349
14 changed files with 693 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
.chrome-debug-profile
|
||||
*.png~
|
33
.vscode/launch.json
vendored
Normal file
33
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Launch Chrome against localhost",
|
||||
"url": "https://web.whatsapp.com",
|
||||
"webRoot": "${workspaceFolder}",
|
||||
"runtimeArgs": [
|
||||
"--load-extension=${workspaceFolder}/GeneralBots",
|
||||
"--disable-extensions-except=${workspaceFolder}/GeneralBots",
|
||||
"--user-data-dir=${workspaceFolder}/.chrome-debug-profile"
|
||||
],
|
||||
"sourceMaps": true
|
||||
},
|
||||
{
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"name": "Debug Chrome Extension",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceFolder}/GeneralBots",
|
||||
"--load-extension=${workspaceFolder}/GeneralBots",
|
||||
"--disable-extensions-except=${workspaceFolder}/GeneralBots"
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/GeneralBots/**/*.js"
|
||||
],
|
||||
"sourceMaps": true
|
||||
}
|
||||
]
|
||||
}
|
17
LICENSE
Normal file
17
LICENSE
Normal file
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
General Bots - WhatsApp Web Enhancement Chrome Extension
|
||||
Copyright (C) 2025 pragmatismo.com.br
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
50
README.md
Normal file
50
README.md
Normal file
|
@ -0,0 +1,50 @@
|
|||
# General Bots Chrome Extension
|
||||
|
||||
A professional-grade Chrome extension developed by [pragmatismo.com.br](https://pragmatismo.com.br) that enhances WhatsApp Web with server-side message processing capabilities and UI improvements.
|
||||
|
||||
## Features
|
||||
|
||||
- Message Interception: Captures messages before they're sent
|
||||
- Server Processing: Sends message content to your server for processing
|
||||
- Message Replacement: Updates the message with processed content before sending
|
||||
- UI Enhancement: Option to hide the contact list for more chat space
|
||||
- User-friendly Settings: Simple configuration through the extension popup
|
||||
|
||||
## Installation
|
||||
|
||||
### Developer Mode Installation
|
||||
|
||||
1. Clone or download this repository
|
||||
2. Open Chrome and navigate to `chrome://extensions/`
|
||||
3. Enable "Developer mode" in the top-right corner
|
||||
4. Click "Load unpacked" and select the extension directory
|
||||
|
||||
### Chrome Web Store Installation
|
||||
|
||||
(Coming soon)
|
||||
|
||||
## Configuration
|
||||
|
||||
1. Click the General Bots icon in your Chrome toolbar
|
||||
2. Enter your processing server URL
|
||||
3. Toggle message processing on/off
|
||||
4. Toggle contact list visibility
|
||||
|
||||
## Server API Requirements
|
||||
|
||||
Your server endpoint should:
|
||||
|
||||
1. Accept POST requests with JSON payload: `{ "text": "message content", "timestamp": 1621234567890 }`
|
||||
2. Return JSON response: `{ "processedText": "updated message content" }`
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the [GNU Affero General Public License](LICENSE) - see the LICENSE file for details.
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please feel free to submit a Pull Request.
|
||||
|
||||
## Contact
|
||||
|
||||
For support or questions, please contact [pragmatismo.com.br](https://pragmatismo.com.br).
|
18
background.js
Normal file
18
background.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
chrome.runtime.onInstalled.addListener(function() {
|
||||
// Set default settings on installation
|
||||
chrome.storage.sync.set({
|
||||
serverUrl: 'https://api.pragmatismo.com.br/general-bots/process',
|
||||
enableProcessing: true,
|
||||
hideContacts: false
|
||||
});
|
||||
});
|
||||
|
||||
// Listen for tab updates
|
||||
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
|
||||
if (changeInfo.status === 'complete' && tab.url && tab.url.includes('web.whatsapp.com')) {
|
||||
// Inject content script
|
||||
chrome.tabs.sendMessage(tabId, {
|
||||
action: 'tabReady'
|
||||
});
|
||||
}
|
||||
});
|
239
content.js
Normal file
239
content.js
Normal file
|
@ -0,0 +1,239 @@
|
|||
// Global variables
|
||||
let settings = {
|
||||
serverUrl: 'https://api.pragmatismo.com.br/general-bots/process',
|
||||
enableProcessing: true,
|
||||
hideContacts: false
|
||||
};
|
||||
|
||||
// Original message storage (before processing)
|
||||
const originalMessages = new Map();
|
||||
|
||||
// Initialize the extension
|
||||
function init() {
|
||||
console.log('General Bots: Initializing...');
|
||||
|
||||
// Load settings
|
||||
chrome.storage.sync.get({
|
||||
serverUrl: 'https://api.pragmatismo.com.br/general-bots/process',
|
||||
enableProcessing: true,
|
||||
hideContacts: false
|
||||
}, function(items) {
|
||||
settings = items;
|
||||
console.log('General Bots: Settings loaded', settings);
|
||||
|
||||
// Apply hide contacts if enabled
|
||||
applyContactVisibility();
|
||||
|
||||
// Start monitoring the input field
|
||||
setupInputListener();
|
||||
});
|
||||
}
|
||||
|
||||
// Apply contact list visibility based on settings
|
||||
function applyContactVisibility() {
|
||||
const contactList = document.querySelector('#pane-side');
|
||||
if (contactList) {
|
||||
if (settings.hideContacts) {
|
||||
contactList.parentElement.classList.add('gb-hide-contacts');
|
||||
} else {
|
||||
contactList.parentElement.classList.remove('gb-hide-contacts');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Setup input field listener
|
||||
function setupInputListener() {
|
||||
// The main input field where users type messages
|
||||
const inputSelector = 'div[contenteditable="true"][data-tab="10"]';
|
||||
|
||||
// Use MutationObserver to detect when the input field appears
|
||||
const observer = new MutationObserver(mutations => {
|
||||
const inputField = document.querySelector(inputSelector);
|
||||
if (inputField && !inputField.getAttribute('gb-monitored')) {
|
||||
setupFieldMonitoring(inputField);
|
||||
inputField.setAttribute('gb-monitored', 'true');
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
|
||||
// Also check immediately in case the field is already present
|
||||
const inputField = document.querySelector(inputSelector);
|
||||
if (inputField && !inputField.getAttribute('gb-monitored')) {
|
||||
setupFieldMonitoring(inputField);
|
||||
inputField.setAttribute('gb-monitored', 'true');
|
||||
}
|
||||
}
|
||||
|
||||
// Setup monitoring for a specific input field
|
||||
function setupFieldMonitoring(inputField) {
|
||||
console.log('General Bots: Setting up input field monitoring');
|
||||
|
||||
// Listen for keydown events (Enter key)
|
||||
inputField.addEventListener('keydown', async (event) => {
|
||||
if (event.key === 'Enter' && !event.shiftKey && settings.enableProcessing) {
|
||||
const originalText = inputField.textContent;
|
||||
|
||||
// Only process if there's actual text
|
||||
if (originalText.trim().length > 0) {
|
||||
// Prevent default Enter behavior temporarily
|
||||
event.preventDefault();
|
||||
|
||||
try {
|
||||
// Process message with server
|
||||
const processedText = await processMessage(originalText);
|
||||
|
||||
// Replace text in the input field
|
||||
inputField.textContent = processedText;
|
||||
|
||||
// Store original message for reference
|
||||
const timestamp = Date.now();
|
||||
originalMessages.set(timestamp, {
|
||||
original: originalText,
|
||||
processed: processedText
|
||||
});
|
||||
|
||||
// Simulate Enter press to send the message
|
||||
simulateEnterPress(inputField);
|
||||
|
||||
// Track the message to update it after sending
|
||||
trackSentMessage(timestamp);
|
||||
} catch (error) {
|
||||
console.error('General Bots: Error processing message', error);
|
||||
// Let the original message be sent if there's an error
|
||||
simulateEnterPress(inputField);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Process message with server
|
||||
async function processMessage(text) {
|
||||
console.log('General Bots: Processing message with server');
|
||||
|
||||
try {
|
||||
const response = await fetch(settings.serverUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
text: text,
|
||||
timestamp: Date.now()
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Server responded with status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data.processedText || text;
|
||||
} catch (error) {
|
||||
console.error('General Bots: Failed to process message', error);
|
||||
return text; // Return original text if processing fails
|
||||
}
|
||||
}
|
||||
|
||||
// Simulate Enter key press
|
||||
function simulateEnterPress(element) {
|
||||
const enterEvent = new KeyboardEvent('keydown', {
|
||||
key: 'Enter',
|
||||
code: 'Enter',
|
||||
keyCode: 13,
|
||||
which: 13,
|
||||
bubbles: true
|
||||
});
|
||||
element.dispatchEvent(enterEvent);
|
||||
}
|
||||
|
||||
// Track sent message to update it after sending
|
||||
function trackSentMessage(timestamp) {
|
||||
// Monitor for the message to appear in the chat
|
||||
const observer = new MutationObserver(mutations => {
|
||||
// Look for recently sent messages
|
||||
const messageContainers = document.querySelectorAll('.message-out');
|
||||
if (messageContainers.length > 0) {
|
||||
// Get the last sent message
|
||||
const lastMessage = messageContainers[messageContainers.length - 1];
|
||||
|
||||
// Check if this message has our data
|
||||
if (!lastMessage.getAttribute('gb-processed')) {
|
||||
// Get message info
|
||||
const messageInfo = originalMessages.get(timestamp);
|
||||
if (messageInfo) {
|
||||
// Mark as processed
|
||||
lastMessage.setAttribute('gb-processed', timestamp);
|
||||
|
||||
// Update message text if different from original
|
||||
if (messageInfo.original !== messageInfo.processed) {
|
||||
updateSentMessageDisplay(lastMessage, messageInfo);
|
||||
}
|
||||
|
||||
// Clean up
|
||||
setTimeout(() => {
|
||||
originalMessages.delete(timestamp);
|
||||
}, 60000); // Remove after 1 minute
|
||||
|
||||
// Stop observing
|
||||
observer.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Start observing chat container
|
||||
const chatContainer = document.querySelector('.copyable-area');
|
||||
if (chatContainer) {
|
||||
observer.observe(chatContainer, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
|
||||
// Stop observing after a reasonable timeout
|
||||
setTimeout(() => {
|
||||
observer.disconnect();
|
||||
}, 10000); // 10 seconds timeout
|
||||
}
|
||||
}
|
||||
|
||||
// Update the displayed message after sending
|
||||
function updateSentMessageDisplay(messageElement, messageInfo) {
|
||||
const textElement = messageElement.querySelector('.selectable-text');
|
||||
if (textElement) {
|
||||
// Create a small indicator that the message was processed
|
||||
const indicator = document.createElement('div');
|
||||
indicator.className = 'gb-processed-indicator';
|
||||
indicator.title = `Original: "${messageInfo.original}"`;
|
||||
indicator.textContent = '✓ AI processed';
|
||||
|
||||
// Add the indicator to the message
|
||||
messageElement.appendChild(indicator);
|
||||
|
||||
// Add tooltip behavior
|
||||
messageElement.classList.add('gb-processed-message');
|
||||
}
|
||||
}
|
||||
|
||||
// Listen for messages from popup or background
|
||||
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
if (message.action === 'settingsUpdated') {
|
||||
settings = message.settings;
|
||||
applyContactVisibility();
|
||||
console.log('General Bots: Settings updated', settings);
|
||||
} else if (message.action === 'tabReady') {
|
||||
init();
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
// Initialize on load
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
|
||||
// Also try to initialize now in case the page is already loaded
|
||||
init();
|
BIN
icons/icon128.png
Normal file
BIN
icons/icon128.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 339 B |
BIN
icons/icon16.png
Normal file
BIN
icons/icon16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 339 B |
BIN
icons/icon48.png
Normal file
BIN
icons/icon48.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 339 B |
37
manifest.json
Normal file
37
manifest.json
Normal file
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"manifest_version": 3,
|
||||
"name": "General Bots",
|
||||
"version": "1.0.0",
|
||||
"description": "Browser server-side processing capabilities",
|
||||
"author": "pragmatismo.com.br",
|
||||
"icons": {
|
||||
"16": "icons/icon16.png",
|
||||
"48": "icons/icon48.png",
|
||||
"128": "icons/icon128.png"
|
||||
},
|
||||
"permissions": [
|
||||
"storage",
|
||||
"tabs"
|
||||
],
|
||||
"host_permissions": [
|
||||
"https://web.whatsapp.com/*"
|
||||
],
|
||||
"action": {
|
||||
"default_popup": "popup.html",
|
||||
"default_icon": {
|
||||
"16": "icons/icon16.png",
|
||||
"48": "icons/icon48.png",
|
||||
"128": "icons/icon128.png"
|
||||
}
|
||||
},
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": ["https://web.whatsapp.com/*"],
|
||||
"js": ["content.js"],
|
||||
"css": ["styles.css"]
|
||||
}
|
||||
],
|
||||
"background": {
|
||||
"service_worker": "background.js"
|
||||
}
|
||||
}
|
168
popup.css
Normal file
168
popup.css
Normal file
|
@ -0,0 +1,168 @@
|
|||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 320px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.header img {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.version {
|
||||
color: #888;
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.settings {
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.setting-item {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.setting-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.setting-item label {
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.setting-item input[type="text"] {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ddd;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.toggle {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.toggle span {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 46px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #ccc;
|
||||
transition: .3s;
|
||||
}
|
||||
|
||||
.slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
left: 3px;
|
||||
bottom: 3px;
|
||||
background-color: white;
|
||||
transition: .3s;
|
||||
}
|
||||
|
||||
input:checked + .slider {
|
||||
background-color: #4CAF50;
|
||||
}
|
||||
|
||||
input:focus + .slider {
|
||||
box-shadow: 0 0 1px #4CAF50;
|
||||
}
|
||||
|
||||
input:checked + .slider:before {
|
||||
transform: translateX(22px);
|
||||
}
|
||||
|
||||
.slider.round {
|
||||
border-radius: 24px;
|
||||
}
|
||||
|
||||
.slider.round:before {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 16px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
width: 100%;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
|
||||
.copyright {
|
||||
margin-top: 10px;
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.copyright a {
|
||||
color: #4CAF50;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.copyright a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
47
popup.html
Normal file
47
popup.html
Normal file
|
@ -0,0 +1,47 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>General Bots</title>
|
||||
<link rel="stylesheet" href="popup.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<img src="icons/icon48.png" alt="General Bots Logo">
|
||||
<h1>General Bots</h1>
|
||||
<p class="version">v1.0.0</p>
|
||||
</div>
|
||||
|
||||
<div class="settings">
|
||||
<div class="setting-item">
|
||||
<label for="server-url">Server URL:</label>
|
||||
<input type="text" id="server-url" placeholder="https://your-server.com/api">
|
||||
</div>
|
||||
|
||||
<div class="setting-item toggle">
|
||||
<span>Enable Message Processing</span>
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="enable-processing" checked>
|
||||
<span class="slider round"></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="setting-item toggle">
|
||||
<span>Hide Contact Window</span>
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="hide-contacts">
|
||||
<span class="slider round"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<button id="save-settings">Save Settings</button>
|
||||
<p class="copyright">© pragmatismo.com.br • <a href="https://github.com/pragmatismo-io/GeneralBots" target="_blank">AGPL License</a></p>
|
||||
</div>
|
||||
</div>
|
||||
<script src="popup.js"></script>
|
||||
</body>
|
||||
</html>
|
50
popup.js
Normal file
50
popup.js
Normal file
|
@ -0,0 +1,50 @@
|
|||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Load saved settings
|
||||
chrome.storage.sync.get({
|
||||
serverUrl: 'https://api.pragmatismo.com.br/general-bots/process',
|
||||
enableProcessing: true,
|
||||
hideContacts: false
|
||||
}, function(items) {
|
||||
document.getElementById('server-url').value = items.serverUrl;
|
||||
document.getElementById('enable-processing').checked = items.enableProcessing;
|
||||
document.getElementById('hide-contacts').checked = items.hideContacts;
|
||||
});
|
||||
|
||||
// Save settings
|
||||
document.getElementById('save-settings').addEventListener('click', function() {
|
||||
const serverUrl = document.getElementById('server-url').value;
|
||||
const enableProcessing = document.getElementById('enable-processing').checked;
|
||||
const hideContacts = document.getElementById('hide-contacts').checked;
|
||||
|
||||
chrome.storage.sync.set({
|
||||
serverUrl: serverUrl,
|
||||
enableProcessing: enableProcessing,
|
||||
hideContacts: hideContacts
|
||||
}, function() {
|
||||
// Update status to let user know settings were saved
|
||||
const button = document.getElementById('save-settings');
|
||||
const originalText = button.textContent;
|
||||
button.textContent = 'Settings Saved!';
|
||||
button.disabled = true;
|
||||
|
||||
// Send message to content script to apply changes immediately
|
||||
chrome.tabs.query({url: 'https://web.whatsapp.com/*'}, function(tabs) {
|
||||
if (tabs.length > 0) {
|
||||
chrome.tabs.sendMessage(tabs[0].id, {
|
||||
action: 'settingsUpdated',
|
||||
settings: {
|
||||
serverUrl: serverUrl,
|
||||
enableProcessing: enableProcessing,
|
||||
hideContacts: hideContacts
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
setTimeout(function() {
|
||||
button.textContent = originalText;
|
||||
button.disabled = false;
|
||||
}, 1500);
|
||||
});
|
||||
});
|
||||
});
|
32
styles.css
Normal file
32
styles.css
Normal file
|
@ -0,0 +1,32 @@
|
|||
/* Hide the contacts pane when enabled */
|
||||
.gb-hide-contacts #pane-side {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Expand the chat area when contacts are hidden */
|
||||
.gb-hide-contacts #main {
|
||||
width: 100% !important;
|
||||
left: 0 !important;
|
||||
}
|
||||
|
||||
/* Style for processed messages */
|
||||
.gb-processed-message {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.gb-processed-indicator {
|
||||
position: absolute;
|
||||
bottom: -16px;
|
||||
right: 8px;
|
||||
font-size: 10px;
|
||||
color: #53bdeb;
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
padding: 2px 4px;
|
||||
border-radius: 4px;
|
||||
opacity: 0.7;
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.gb-processed-message:hover .gb-processed-indicator {
|
||||
opacity: 1;
|
||||
}
|
Loading…
Add table
Reference in a new issue