Sure! Pl
This commit is contained in:
315
Extension/popup/popup.css
Normal file
315
Extension/popup/popup.css
Normal file
@@ -0,0 +1,315 @@
|
||||
/* EShip Extension Popup Styles */
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
||||
font-size: 14px;
|
||||
color: #2D2D31;
|
||||
background: #FAFAFB;
|
||||
min-width: 320px;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
header {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid #EDEDF0;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #FD366E;
|
||||
}
|
||||
|
||||
/* States */
|
||||
.state {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.state.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Loading */
|
||||
#loading {
|
||||
text-align: center;
|
||||
padding: 40px 0;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 3px solid #EDEDF0;
|
||||
border-top-color: #FD366E;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
margin: 0 auto 12px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Form */
|
||||
h2 {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: #97979B;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.form-group input {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid #EDEDF0;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
background: #fff;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.form-group input:focus {
|
||||
outline: none;
|
||||
border-color: #FD366E;
|
||||
box-shadow: 0 0 0 3px rgba(253, 54, 110, 0.1);
|
||||
}
|
||||
|
||||
/* Error Message */
|
||||
.error {
|
||||
background: #FEE2E2;
|
||||
color: #B91C1C;
|
||||
padding: 10px 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.error.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10px 16px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s, transform 0.1s;
|
||||
}
|
||||
|
||||
.btn:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
width: 100%;
|
||||
background: #FD366E;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #E8305F;
|
||||
}
|
||||
|
||||
.btn-primary:disabled {
|
||||
background: #FDA4B8;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #EDEDF0;
|
||||
color: #2D2D31;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #D8D8DB;
|
||||
}
|
||||
|
||||
.btn-small {
|
||||
padding: 6px 12px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.btn-spinner {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||
border-top-color: white;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
.btn-spinner.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* User Info */
|
||||
.user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px;
|
||||
background: #fff;
|
||||
border: 1px solid #EDEDF0;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.user-icon {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
#user-email {
|
||||
flex: 1;
|
||||
font-size: 13px;
|
||||
color: #97979B;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Tools List */
|
||||
#tools-list {
|
||||
background: #fff;
|
||||
border: 1px solid #EDEDF0;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.tool-item {
|
||||
padding: 12px;
|
||||
border-bottom: 1px solid #EDEDF0;
|
||||
}
|
||||
|
||||
.tool-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.tool-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.tool-name {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Toggle Switch */
|
||||
.toggle {
|
||||
position: relative;
|
||||
width: 44px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.toggle input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.toggle-slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #D8D8DB;
|
||||
transition: 0.3s;
|
||||
border-radius: 24px;
|
||||
}
|
||||
|
||||
.toggle-slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
left: 3px;
|
||||
bottom: 3px;
|
||||
background-color: white;
|
||||
transition: 0.3s;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.toggle input:checked + .toggle-slider {
|
||||
background-color: #FD366E;
|
||||
}
|
||||
|
||||
.toggle input:checked + .toggle-slider:before {
|
||||
transform: translateX(20px);
|
||||
}
|
||||
|
||||
/* Tool Settings */
|
||||
.tool-settings {
|
||||
display: none;
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.tool-settings.visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.setting-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.setting-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.setting-row label {
|
||||
font-size: 12px;
|
||||
color: #97979B;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.setting-row input {
|
||||
flex: 1;
|
||||
padding: 6px 8px;
|
||||
border: 1px solid #EDEDF0;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.setting-row input:focus {
|
||||
outline: none;
|
||||
border-color: #FD366E;
|
||||
}
|
||||
|
||||
/* Actions */
|
||||
.actions {
|
||||
margin-top: 16px;
|
||||
}
|
||||
62
Extension/popup/popup.html
Normal file
62
Extension/popup/popup.html
Normal file
@@ -0,0 +1,62 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=400, initial-scale=1.0">
|
||||
<title>EShip Extension</title>
|
||||
<link rel="stylesheet" href="popup.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1>EShip</h1>
|
||||
</header>
|
||||
|
||||
<!-- Loading State -->
|
||||
<div id="loading" class="state">
|
||||
<div class="spinner"></div>
|
||||
<p>Laden...</p>
|
||||
</div>
|
||||
|
||||
<!-- Login Form (logged out state) -->
|
||||
<div id="login-form" class="state hidden">
|
||||
<h2>Anmelden</h2>
|
||||
<form id="auth-form">
|
||||
<div class="form-group">
|
||||
<label for="email">E-Mail</label>
|
||||
<input type="email" id="email" name="email" required autocomplete="email">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Passwort</label>
|
||||
<input type="password" id="password" name="password" required autocomplete="current-password">
|
||||
</div>
|
||||
<div id="error-message" class="error hidden"></div>
|
||||
<button type="submit" id="login-btn" class="btn btn-primary">
|
||||
<span class="btn-text">Anmelden</span>
|
||||
<span class="btn-spinner hidden"></span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Tools Menu (logged in state) -->
|
||||
<div id="tools-menu" class="state hidden">
|
||||
<div class="user-info">
|
||||
<span class="user-icon">👤</span>
|
||||
<span id="user-email"></span>
|
||||
<button id="logout-btn" class="btn btn-small btn-secondary">Abmelden</button>
|
||||
</div>
|
||||
|
||||
<h2>Tools</h2>
|
||||
<div id="tools-list">
|
||||
<!-- Tools will be rendered here dynamically -->
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button id="open-site-btn" class="btn btn-primary">Website oeffnen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="popup.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
319
Extension/popup/popup.js
Normal file
319
Extension/popup/popup.js
Normal file
@@ -0,0 +1,319 @@
|
||||
// EShip Extension Popup Logic
|
||||
|
||||
// DOM Elements
|
||||
const loadingEl = document.getElementById('loading');
|
||||
const loginFormEl = document.getElementById('login-form');
|
||||
const toolsMenuEl = document.getElementById('tools-menu');
|
||||
const authForm = document.getElementById('auth-form');
|
||||
const emailInput = document.getElementById('email');
|
||||
const passwordInput = document.getElementById('password');
|
||||
const errorMessage = document.getElementById('error-message');
|
||||
const loginBtn = document.getElementById('login-btn');
|
||||
const logoutBtn = document.getElementById('logout-btn');
|
||||
const userEmailEl = document.getElementById('user-email');
|
||||
const toolsListEl = document.getElementById('tools-list');
|
||||
const openSiteBtn = document.getElementById('open-site-btn');
|
||||
|
||||
// Protected site URL (should match config.js)
|
||||
const PROTECTED_SITE_URL = 'http://localhost:5173';
|
||||
|
||||
// State
|
||||
let currentUser = null;
|
||||
let tools = [];
|
||||
|
||||
// Initialize popup
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
|
||||
async function init() {
|
||||
showState('loading');
|
||||
|
||||
try {
|
||||
// Check if service worker is available
|
||||
if (!chrome.runtime || !chrome.runtime.id) {
|
||||
throw new Error('Chrome Runtime nicht verfuegbar');
|
||||
}
|
||||
|
||||
// First, test if service worker is alive
|
||||
console.log('Testing service worker...');
|
||||
const pingResponse = await sendMessage({ action: 'PING' });
|
||||
console.log('PING response:', pingResponse);
|
||||
|
||||
if (!pingResponse) {
|
||||
throw new Error('Service Worker antwortet nicht. Bitte Extension in chrome://extensions neu laden und Service Worker Konsole pruefen.');
|
||||
}
|
||||
|
||||
if (!pingResponse.success) {
|
||||
throw new Error('Service Worker Fehler: ' + (pingResponse.error || 'Unbekannter Fehler'));
|
||||
}
|
||||
|
||||
console.log('Service Worker ist aktiv!');
|
||||
|
||||
console.log('Checking auth status...');
|
||||
|
||||
// Check current auth status
|
||||
const response = await sendMessage({ action: 'CHECK_AUTH' });
|
||||
|
||||
if (!response) {
|
||||
throw new Error('Keine Antwort vom Service Worker');
|
||||
}
|
||||
|
||||
console.log('Auth response:', response);
|
||||
|
||||
// If there's an error, show it but still allow login
|
||||
if (response.error && !response.success) {
|
||||
console.error('Service Worker Error:', response.error);
|
||||
showError('Hinweis: ' + response.error + ' - Login sollte trotzdem funktionieren.');
|
||||
}
|
||||
|
||||
if (response.success && response.authenticated) {
|
||||
currentUser = response.user;
|
||||
await loadTools();
|
||||
showLoggedInState();
|
||||
} else {
|
||||
// Not authenticated - show login form
|
||||
showState('login-form');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Init error:', error);
|
||||
showError('Fehler beim Laden: ' + error.message + '. Bitte Extension in chrome://extensions neu laden.');
|
||||
showState('login-form');
|
||||
}
|
||||
}
|
||||
|
||||
// Show a specific state (loading, login-form, tools-menu)
|
||||
function showState(stateId) {
|
||||
loadingEl.classList.add('hidden');
|
||||
loginFormEl.classList.add('hidden');
|
||||
toolsMenuEl.classList.add('hidden');
|
||||
|
||||
document.getElementById(stateId).classList.remove('hidden');
|
||||
}
|
||||
|
||||
// Show logged in state with user info
|
||||
function showLoggedInState() {
|
||||
userEmailEl.textContent = currentUser.email || currentUser.name || 'Benutzer';
|
||||
renderTools();
|
||||
showState('tools-menu');
|
||||
}
|
||||
|
||||
// Send message to service worker
|
||||
function sendMessage(message) {
|
||||
return new Promise((resolve) => {
|
||||
// Add timeout to prevent hanging
|
||||
const timeout = setTimeout(() => {
|
||||
console.error('Message timeout:', message.action);
|
||||
resolve({
|
||||
success: false,
|
||||
error: 'Service Worker antwortet nicht. Bitte Extension in chrome://extensions neu laden und Service Worker Konsole pruefen.'
|
||||
});
|
||||
}, 2000); // Reduced timeout to 2 seconds for faster feedback
|
||||
|
||||
try {
|
||||
if (!chrome.runtime || !chrome.runtime.id) {
|
||||
clearTimeout(timeout);
|
||||
resolve({ success: false, error: 'Chrome Runtime nicht verfuegbar' });
|
||||
return;
|
||||
}
|
||||
|
||||
chrome.runtime.sendMessage(message, (response) => {
|
||||
clearTimeout(timeout);
|
||||
if (chrome.runtime.lastError) {
|
||||
console.error('Chrome runtime error:', chrome.runtime.lastError);
|
||||
resolve({
|
||||
success: false,
|
||||
error: chrome.runtime.lastError.message || 'Service Worker Fehler'
|
||||
});
|
||||
} else {
|
||||
console.log('Response received:', response);
|
||||
resolve(response || { success: false, error: 'Keine Antwort vom Service Worker' });
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
clearTimeout(timeout);
|
||||
console.error('Send message error:', error);
|
||||
resolve({ success: false, error: error.message || 'Fehler beim Senden der Nachricht' });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Login form submission
|
||||
authForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const email = emailInput.value.trim();
|
||||
const password = passwordInput.value;
|
||||
|
||||
if (!email || !password) {
|
||||
showError('Bitte E-Mail und Passwort eingeben');
|
||||
return;
|
||||
}
|
||||
|
||||
setLoginLoading(true);
|
||||
hideError();
|
||||
|
||||
const response = await sendMessage({
|
||||
action: 'LOGIN',
|
||||
email,
|
||||
password
|
||||
});
|
||||
|
||||
setLoginLoading(false);
|
||||
|
||||
if (response.success) {
|
||||
currentUser = response.user;
|
||||
await loadTools();
|
||||
showLoggedInState();
|
||||
|
||||
// Open protected site after successful login
|
||||
openProtectedSite();
|
||||
} else {
|
||||
showError(response.error || 'Anmeldung fehlgeschlagen');
|
||||
}
|
||||
});
|
||||
|
||||
// Logout button
|
||||
logoutBtn.addEventListener('click', async () => {
|
||||
const response = await sendMessage({ action: 'LOGOUT' });
|
||||
|
||||
if (response.success) {
|
||||
currentUser = null;
|
||||
tools = [];
|
||||
emailInput.value = '';
|
||||
passwordInput.value = '';
|
||||
showState('login-form');
|
||||
}
|
||||
});
|
||||
|
||||
// Open site button
|
||||
openSiteBtn.addEventListener('click', () => {
|
||||
openProtectedSite();
|
||||
});
|
||||
|
||||
// Open protected site in new tab
|
||||
function openProtectedSite() {
|
||||
chrome.tabs.create({ url: PROTECTED_SITE_URL });
|
||||
}
|
||||
|
||||
// Load tools settings
|
||||
async function loadTools() {
|
||||
const response = await sendMessage({ action: 'GET_SETTINGS' });
|
||||
if (response.success) {
|
||||
tools = response.tools;
|
||||
}
|
||||
}
|
||||
|
||||
// Render tools list
|
||||
function renderTools() {
|
||||
toolsListEl.innerHTML = '';
|
||||
|
||||
tools.forEach((tool, index) => {
|
||||
const toolEl = document.createElement('div');
|
||||
toolEl.className = 'tool-item';
|
||||
toolEl.innerHTML = `
|
||||
<div class="tool-header">
|
||||
<span class="tool-name">${escapeHtml(tool.name)}</span>
|
||||
<label class="toggle">
|
||||
<input type="checkbox" data-tool-id="${tool.id}" ${tool.enabled ? 'checked' : ''}>
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="tool-settings ${tool.enabled ? 'visible' : ''}" id="settings-${tool.id}">
|
||||
${renderToolSettings(tool)}
|
||||
</div>
|
||||
`;
|
||||
toolsListEl.appendChild(toolEl);
|
||||
|
||||
// Toggle event listener
|
||||
const toggle = toolEl.querySelector('input[type="checkbox"]');
|
||||
toggle.addEventListener('change', (e) => {
|
||||
handleToolToggle(tool.id, e.target.checked);
|
||||
});
|
||||
|
||||
// Settings change listeners
|
||||
const settingsInputs = toolEl.querySelectorAll('.setting-row input');
|
||||
settingsInputs.forEach(input => {
|
||||
input.addEventListener('change', (e) => {
|
||||
handleSettingChange(tool.id, e.target.dataset.setting, e.target.value);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Render settings inputs for a tool
|
||||
function renderToolSettings(tool) {
|
||||
if (!tool.settings) return '';
|
||||
|
||||
let html = '';
|
||||
for (const [key, value] of Object.entries(tool.settings)) {
|
||||
html += `
|
||||
<div class="setting-row">
|
||||
<label>${escapeHtml(formatSettingName(key))}</label>
|
||||
<input type="text" data-setting="${key}" value="${escapeHtml(value)}">
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
return html;
|
||||
}
|
||||
|
||||
// Format setting key to readable name
|
||||
function formatSettingName(key) {
|
||||
return key
|
||||
.replace(/([A-Z])/g, ' $1')
|
||||
.replace(/^./, str => str.toUpperCase())
|
||||
.replace(/_/g, ' ');
|
||||
}
|
||||
|
||||
// Handle tool toggle
|
||||
async function handleToolToggle(toolId, enabled) {
|
||||
const tool = tools.find(t => t.id === toolId);
|
||||
if (tool) {
|
||||
tool.enabled = enabled;
|
||||
|
||||
// Show/hide settings
|
||||
const settingsEl = document.getElementById(`settings-${toolId}`);
|
||||
if (settingsEl) {
|
||||
settingsEl.classList.toggle('visible', enabled);
|
||||
}
|
||||
|
||||
await saveTools();
|
||||
}
|
||||
}
|
||||
|
||||
// Handle setting value change
|
||||
async function handleSettingChange(toolId, settingKey, value) {
|
||||
const tool = tools.find(t => t.id === toolId);
|
||||
if (tool && tool.settings) {
|
||||
tool.settings[settingKey] = value;
|
||||
await saveTools();
|
||||
}
|
||||
}
|
||||
|
||||
// Save tools to storage
|
||||
async function saveTools() {
|
||||
await sendMessage({ action: 'SAVE_SETTINGS', settings: tools });
|
||||
}
|
||||
|
||||
// Show error message
|
||||
function showError(message) {
|
||||
errorMessage.textContent = message;
|
||||
errorMessage.classList.remove('hidden');
|
||||
}
|
||||
|
||||
// Hide error message
|
||||
function hideError() {
|
||||
errorMessage.classList.add('hidden');
|
||||
}
|
||||
|
||||
// Set login button loading state
|
||||
function setLoginLoading(loading) {
|
||||
loginBtn.disabled = loading;
|
||||
loginBtn.querySelector('.btn-text').classList.toggle('hidden', loading);
|
||||
loginBtn.querySelector('.btn-spinner').classList.toggle('hidden', !loading);
|
||||
}
|
||||
|
||||
// Escape HTML to prevent XSS
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
Reference in New Issue
Block a user