Files
2025-10-02 03:12:52 -07:00

533 lines
16 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Site Content Editor - Admin Panel</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: #f5f5f5;
padding: 20px;
}
.container {
max-width: 1400px;
margin: 0 auto;
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
padding: 30px;
}
h1 {
margin-bottom: 10px;
color: #333;
}
.subtitle {
color: #666;
margin-bottom: 30px;
}
.toolbar {
display: flex;
gap: 10px;
margin-bottom: 20px;
flex-wrap: wrap;
}
button {
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.2s;
}
button:hover {
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
}
.btn-primary {
background: #4CAF50;
color: white;
}
.btn-secondary {
background: #2196F3;
color: white;
}
.btn-danger {
background: #f44336;
color: white;
}
.btn-warning {
background: #ff9800;
color: white;
}
.locale-selector {
display: flex;
gap: 10px;
margin-bottom: 20px;
align-items: center;
}
.locale-selector label {
font-weight: 600;
}
select {
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.content-grid {
display: grid;
gap: 15px;
}
.content-item {
border: 1px solid #e0e0e0;
border-radius: 4px;
padding: 15px;
background: #fafafa;
}
.content-item-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.content-key {
font-family: 'Courier New', monospace;
font-size: 13px;
color: #1976D2;
font-weight: 600;
}
.content-item input, .content-item textarea {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
font-family: inherit;
}
.content-item textarea {
min-height: 80px;
resize: vertical;
}
.btn-delete {
background: #f44336;
color: white;
padding: 6px 12px;
font-size: 12px;
}
.new-item-form {
background: #e3f2fd;
border: 2px dashed #2196F3;
padding: 20px;
border-radius: 4px;
margin-bottom: 20px;
display: none;
}
.new-item-form.active {
display: block;
}
.form-row {
display: grid;
grid-template-columns: 1fr 2fr;
gap: 10px;
margin-bottom: 10px;
}
.loading {
text-align: center;
padding: 40px;
color: #666;
}
.success-message {
background: #4CAF50;
color: white;
padding: 12px;
border-radius: 4px;
margin-bottom: 20px;
display: none;
}
.success-message.active {
display: block;
}
.error-message {
background: #f44336;
color: white;
padding: 12px;
border-radius: 4px;
margin-bottom: 20px;
display: none;
}
.error-message.active {
display: block;
}
.stats {
background: #f5f5f5;
padding: 15px;
border-radius: 4px;
margin-bottom: 20px;
display: flex;
gap: 30px;
}
.stat-item {
display: flex;
flex-direction: column;
}
.stat-label {
font-size: 12px;
color: #666;
text-transform: uppercase;
}
.stat-value {
font-size: 24px;
font-weight: 600;
color: #333;
}
</style>
</head>
<body>
<div class="container">
<h1>🎨 Site Content Editor</h1>
<p class="subtitle">Manage modal content, rules, and site text</p>
<div class="success-message" id="successMessage"></div>
<div class="error-message" id="errorMessage"></div>
<div class="stats">
<div class="stat-item">
<span class="stat-label">Total Items</span>
<span class="stat-value" id="totalItems">0</span>
</div>
<div class="stat-item">
<span class="stat-label">Current Locale</span>
<span class="stat-value" id="currentLocale">en</span>
</div>
</div>
<div class="toolbar">
<div class="locale-selector">
<label for="localeSelect">Locale:</label>
<select id="localeSelect">
<option value="en">English (en)</option>
<option value="zh">Chinese (zh)</option>
</select>
</div>
<button class="btn-primary" onclick="loadContent()">🔄 Reload</button>
<button class="btn-secondary" onclick="toggleNewItemForm()"> Add New Item</button>
<button class="btn-warning" onclick="initializeDefaults()">🔧 Initialize Defaults</button>
<button class="btn-primary" onclick="saveAllChanges()">💾 Save All Changes</button>
</div>
<div class="new-item-form" id="newItemForm">
<h3>Add New Content Item</h3>
<div class="form-row">
<input type="text" id="newKey" placeholder="Key (e.g., modal.rules.item.5)">
<textarea id="newValue" placeholder="Value"></textarea>
</div>
<button class="btn-primary" onclick="addNewItem()">Add Item</button>
<button class="btn-secondary" onclick="toggleNewItemForm()">Cancel</button>
</div>
<div id="contentContainer" class="loading">
Loading content...
</div>
</div>
<script>
let currentContent = [];
let currentLocale = 'en';
let changedItems = new Set();
// Get JWT token from cookie
function getAuthToken() {
const cookies = document.cookie.split(';');
for (let cookie of cookies) {
const [name, value] = cookie.trim().split('=');
if (name === 'j') {
return value;
}
}
return null;
}
// Show success message
function showSuccess(message) {
const el = document.getElementById('successMessage');
el.textContent = message;
el.classList.add('active');
setTimeout(() => el.classList.remove('active'), 3000);
}
// Show error message
function showError(message) {
const el = document.getElementById('errorMessage');
el.textContent = message;
el.classList.add('active');
setTimeout(() => el.classList.remove('active'), 5000);
}
// Toggle new item form
function toggleNewItemForm() {
const form = document.getElementById('newItemForm');
form.classList.toggle('active');
}
// Load content from API
async function loadContent() {
const locale = document.getElementById('localeSelect').value;
currentLocale = locale;
try {
const token = getAuthToken();
const response = await fetch('/api/admin/site-content', {
headers: {
'Cookie': `j=${token}`
},
credentials: 'include'
});
if (!response.ok) {
throw new Error('Failed to load content');
}
const data = await response.json();
currentContent = data.content.filter(item => item.locale === locale);
renderContent();
updateStats();
showSuccess(`Loaded ${currentContent.length} items for locale: ${locale}`);
} catch (error) {
showError('Error loading content: ' + error.message);
console.error('Error:', error);
}
}
// Render content items
function renderContent() {
const container = document.getElementById('contentContainer');
if (currentContent.length === 0) {
container.innerHTML = '<div class="loading">No content found. Click "Initialize Defaults" to create default content.</div>';
return;
}
container.innerHTML = '';
container.className = 'content-grid';
currentContent.forEach((item, index) => {
const itemEl = document.createElement('div');
itemEl.className = 'content-item';
itemEl.innerHTML = `
<div class="content-item-header">
<span class="content-key">${escapeHtml(item.key)}</span>
<button class="btn-delete" onclick="deleteItem('${escapeHtml(item.key)}')">🗑 Delete</button>
</div>
${item.value.length > 100
? `<textarea data-key="${escapeHtml(item.key)}" onchange="markChanged('${escapeHtml(item.key)}')">${escapeHtml(item.value)}</textarea>`
: `<input type="text" data-key="${escapeHtml(item.key)}" value="${escapeHtml(item.value)}" onchange="markChanged('${escapeHtml(item.key)}')">`
}
`;
container.appendChild(itemEl);
});
}
// Mark item as changed
function markChanged(key) {
changedItems.add(key);
}
// Update statistics
function updateStats() {
document.getElementById('totalItems').textContent = currentContent.length;
document.getElementById('currentLocale').textContent = currentLocale;
}
// Add new item
async function addNewItem() {
const key = document.getElementById('newKey').value.trim();
const value = document.getElementById('newValue').value.trim();
if (!key || !value) {
showError('Both key and value are required');
return;
}
try {
const token = getAuthToken();
const response = await fetch('/api/admin/site-content', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Cookie': `j=${token}`
},
credentials: 'include',
body: JSON.stringify({ key, value, locale: currentLocale })
});
if (!response.ok) {
throw new Error('Failed to add item');
}
document.getElementById('newKey').value = '';
document.getElementById('newValue').value = '';
toggleNewItemForm();
loadContent();
showSuccess('Item added successfully');
} catch (error) {
showError('Error adding item: ' + error.message);
console.error('Error:', error);
}
}
// Delete item
async function deleteItem(key) {
if (!confirm(`Delete "${key}"?`)) return;
try {
const token = getAuthToken();
const response = await fetch(`/api/admin/site-content/${encodeURIComponent(key)}`, {
method: 'DELETE',
headers: {
'Cookie': `j=${token}`
},
credentials: 'include'
});
if (!response.ok) {
throw new Error('Failed to delete item');
}
loadContent();
showSuccess('Item deleted successfully');
} catch (error) {
showError('Error deleting item: ' + error.message);
console.error('Error:', error);
}
}
// Save all changes
async function saveAllChanges() {
const items = [];
const inputs = document.querySelectorAll('[data-key]');
inputs.forEach(input => {
const key = input.getAttribute('data-key');
if (changedItems.has(key)) {
items.push({
key: key,
value: input.value,
locale: currentLocale
});
}
});
if (items.length === 0) {
showError('No changes to save');
return;
}
try {
const token = getAuthToken();
const response = await fetch('/api/admin/site-content/bulk', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Cookie': `j=${token}`
},
credentials: 'include',
body: JSON.stringify({ items })
});
if (!response.ok) {
throw new Error('Failed to save changes');
}
changedItems.clear();
loadContent();
showSuccess(`Saved ${items.length} items successfully`);
} catch (error) {
showError('Error saving changes: ' + error.message);
console.error('Error:', error);
}
}
// Initialize default content
async function initializeDefaults() {
if (!confirm('Initialize default content? This will create/update default modal content.')) return;
try {
const token = getAuthToken();
const response = await fetch('/api/admin/site-content/initialize', {
method: 'POST',
headers: {
'Cookie': `j=${token}`
},
credentials: 'include'
});
if (!response.ok) {
throw new Error('Failed to initialize defaults');
}
const data = await response.json();
loadContent();
showSuccess(`Initialized ${data.initialized} default items`);
} catch (error) {
showError('Error initializing defaults: ' + error.message);
console.error('Error:', error);
}
}
// Escape HTML
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// Event listeners
document.getElementById('localeSelect').addEventListener('change', loadContent);
// Initial load
loadContent();
</script>
</body>
</html>