update wordfilter func
This commit is contained in:
@@ -2831,4 +2831,12 @@ CREATE INDEX IF NOT EXISTS idx_dm_att_expires ON public.dm_attachments(expires
|
|||||||
ALTER TABLE private_messages
|
ALTER TABLE private_messages
|
||||||
ADD COLUMN IF NOT EXISTS edited_at timestamp with time zone DEFAULT NULL;
|
ADD COLUMN IF NOT EXISTS edited_at timestamp with time zone DEFAULT NULL;
|
||||||
|
|
||||||
|
-- Wordfilter Table (Migration 009)
|
||||||
|
CREATE TABLE IF NOT EXISTS public.wordfilter (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
word VARCHAR(255) UNIQUE NOT NULL,
|
||||||
|
replacement VARCHAR(255) NOT NULL,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
\unrestrict RMNKNzVQLV2ZcwmM3bmhglTot5nRoju9FmRyi3eUMfNy6iJUBfHRIgXnbrpJikG
|
\unrestrict RMNKNzVQLV2ZcwmM3bmhglTot5nRoju9FmRyi3eUMfNy6iJUBfHRIgXnbrpJikG
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
<!-- Rule Add Section -->
|
<!-- Rule Add Section -->
|
||||||
<div class="wordfilter-form-container" style="background: rgba(0,0,0,0.2); padding: 20px; border-radius: 6px; margin-bottom: 30px; border: 1px solid rgba(255,255,255,0.05);">
|
<div class="wordfilter-form-container" style="background: rgba(0,0,0,0.2); padding: 20px; border-radius: 6px; margin-bottom: 30px; border: 1px solid rgba(255,255,255,0.05);">
|
||||||
<h4 style="color: var(--accent); margin-top: 0; margin-bottom: 15px;">Create Wordfilter Rule</h4>
|
<h4 style="color: var(--accent); margin-top: 0; margin-bottom: 15px;">Create Wordfilter Rule</h4>
|
||||||
<form id="wordfilter-form" onsubmit="event.preventDefault(); addRule(this);" style="display: flex; gap: 15px; align-items: flex-end; flex-wrap: wrap;">
|
<form id="wordfilter-form" onsubmit="event.preventDefault(); window.wordfilterAdmin.addRule(this);" style="display: flex; gap: 15px; align-items: flex-end; flex-wrap: wrap;">
|
||||||
<div style="flex: 1; min-width: 200px;">
|
<div style="flex: 1; min-width: 200px;">
|
||||||
<label for="word" style="display: block; margin-bottom: 6px; font-size: 0.9em; color: #ccc;">Target Word</label>
|
<label for="word" style="display: block; margin-bottom: 6px; font-size: 0.9em; color: #ccc;">Target Word</label>
|
||||||
<input type="text" id="word" name="word" required placeholder="Word to match..." style="width: 100%; background: rgba(0,0,0,0.3); border: 1px solid rgba(255,255,255,0.1); color: #fff; padding: 10px; border-radius: 4px; font-size: 1em;">
|
<input type="text" id="word" name="word" required placeholder="Word to match..." style="width: 100%; background: rgba(0,0,0,0.3); border: 1px solid rgba(255,255,255,0.1); color: #fff; padding: 10px; border-radius: 4px; font-size: 1em;">
|
||||||
@@ -28,121 +28,129 @@
|
|||||||
<button type="submit" class="btn-upload" style="width: auto; padding: 11px 25px; margin-bottom: 1px;">Add Rule</button>
|
<button type="submit" class="btn-upload" style="width: auto; padding: 11px 25px; margin-bottom: 1px;">Add Rule</button>
|
||||||
</form>
|
</form>
|
||||||
<span id="form-status" style="margin-top: 10px; display: block; font-weight: bold; font-size: 0.9em;"></span>
|
<span id="form-status" style="margin-top: 10px; display: block; font-weight: bold; font-size: 0.9em;"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Active Rules List -->
|
||||||
|
<div class="wordfilter-table-container" style="background: rgba(0,0,0,0.1); border-radius: 6px; padding: 5px; border: 1px solid rgba(255,255,255,0.05);">
|
||||||
|
<table class="responsive-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Original Word</th>
|
||||||
|
<th>Replacement</th>
|
||||||
|
<th>Created</th>
|
||||||
|
<th style="text-align: right;">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="filter-list">
|
||||||
|
<!-- Dynamically loaded -->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div id="no-rules-msg" style="text-align: center; padding: 40px; color: #aaa; display: none;">
|
||||||
|
No wordfilter rules active. Add one above!
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
const escapeHtml = (str) => {
|
||||||
|
if (!str) return '';
|
||||||
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
||||||
|
};
|
||||||
|
|
||||||
<!-- Active Rules List -->
|
const loadRules = async () => {
|
||||||
<div class="wordfilter-table-container" style="background: rgba(0,0,0,0.1); border-radius: 6px; padding: 5px; border: 1px solid rgba(255,255,255,0.05);">
|
try {
|
||||||
<table class="responsive-table">
|
const res = await fetch('/api/v2/admin/wordfilter');
|
||||||
<thead>
|
const data = await res.json();
|
||||||
<tr>
|
if (data.success) {
|
||||||
<th>Original Word</th>
|
const tbody = document.getElementById('filter-list');
|
||||||
<th>Replacement</th>
|
const noRulesMsg = document.getElementById('no-rules-msg');
|
||||||
<th>Created</th>
|
if (!tbody) return;
|
||||||
<th style="text-align: right;">Actions</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="filter-list">
|
|
||||||
<!-- Dynamically loaded -->
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<div id="no-rules-msg" style="text-align: center; padding: 40px; color: #aaa; display: none;">
|
|
||||||
No wordfilter rules active. Add one above!
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
if (!data.filters || data.filters.length === 0) {
|
||||||
const loadRules = async () => {
|
tbody.innerHTML = '';
|
||||||
try {
|
noRulesMsg.style.display = 'block';
|
||||||
const res = await fetch('/api/v2/admin/wordfilter');
|
return;
|
||||||
const data = await res.json();
|
}
|
||||||
if (data.success) {
|
|
||||||
const tbody = document.getElementById('filter-list');
|
|
||||||
const noRulesMsg = document.getElementById('no-rules-msg');
|
|
||||||
if (!tbody) return;
|
|
||||||
|
|
||||||
if (!data.filters || data.filters.length === 0) {
|
noRulesMsg.style.display = 'none';
|
||||||
tbody.innerHTML = '';
|
tbody.innerHTML = data.filters.map(f =>
|
||||||
noRulesMsg.style.display = 'block';
|
'<tr>' +
|
||||||
return;
|
'<td data-label="Original Word" style="font-weight: bold; color: var(--accent);">' + escapeHtml(f.word) + '</td>' +
|
||||||
|
'<td data-label="Replacement" style="font-family: monospace; color: #fff;">' + escapeHtml(f.replacement) + '</td>' +
|
||||||
|
'<td data-label="Created">' + (f.created_at ? new Date(f.created_at).toLocaleString() : '—') + '</td>' +
|
||||||
|
'<td data-label="Actions" style="text-align: right;">' +
|
||||||
|
'<button onclick="window.wordfilterAdmin.deleteRule(' + f.id + ')" class="btn-remove" style="padding: 5px 12px; font-size: 0.85em; border-radius: 4px; border: 0; cursor: pointer;">Delete</button>' +
|
||||||
|
'</td>' +
|
||||||
|
'</tr>'
|
||||||
|
).join('');
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
noRulesMsg.style.display = 'none';
|
console.error('Failed to load rules:', e);
|
||||||
tbody.innerHTML = data.filters.map(f =>
|
|
||||||
'<tr>' +
|
|
||||||
'<td data-label="Original Word" style="font-weight: bold; color: var(--accent);">' + escapeHtml(f.word) + '</td>' +
|
|
||||||
'<td data-label="Replacement" style="font-family: monospace; color: #fff;">' + escapeHtml(f.replacement) + '</td>' +
|
|
||||||
'<td data-label="Created">' + (f.created_at ? new Date(f.created_at).toLocaleString() : '—') + '</td>' +
|
|
||||||
'<td data-label="Actions" style="text-align: right;">' +
|
|
||||||
'<button onclick="deleteRule(' + f.id + ')" class="btn-remove" style="padding: 5px 12px; font-size: 0.85em; border-radius: 4px; border: 0; cursor: pointer;">Delete</button>' +
|
|
||||||
'</td>' +
|
|
||||||
'</tr>'
|
|
||||||
).join('');
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
};
|
||||||
console.error('Failed to load rules:', e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const addRule = async (form) => {
|
const addRule = async (form) => {
|
||||||
const status = document.getElementById('form-status');
|
const status = document.getElementById('form-status');
|
||||||
status.textContent = 'Saving...';
|
status.textContent = 'Saving...';
|
||||||
status.style.color = 'var(--accent)';
|
status.style.color = 'var(--accent)';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/v2/admin/wordfilter', {
|
const res = await fetch('/api/v2/admin/wordfilter', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
'X-CSRF-Token': window.f0ckSession?.csrf_token
|
'X-CSRF-Token': window.f0ckSession?.csrf_token
|
||||||
},
|
},
|
||||||
body: new URLSearchParams(new FormData(form))
|
body: new URLSearchParams(new FormData(form))
|
||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
status.textContent = 'Rule added successfully!';
|
status.textContent = 'Rule added successfully!';
|
||||||
status.style.color = '#28a745';
|
status.style.color = '#28a745';
|
||||||
form.reset();
|
form.reset();
|
||||||
loadRules();
|
loadRules();
|
||||||
setTimeout(() => status.textContent = '', 3000);
|
setTimeout(() => status.textContent = '', 3000);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(data.msg || 'Save failed');
|
throw new Error(data.msg || 'Save failed');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
status.textContent = 'Error: ' + e.message;
|
||||||
|
status.style.color = '#d9534f';
|
||||||
}
|
}
|
||||||
} catch (e) {
|
};
|
||||||
status.textContent = 'Error: ' + e.message;
|
|
||||||
status.style.color = '#d9534f';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteRule = async (id) => {
|
const deleteRule = async (id) => {
|
||||||
if (!confirm('Are you sure you want to delete this wordfilter rule?')) return;
|
if (!confirm('Are you sure you want to delete this wordfilter rule?')) return;
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/v2/admin/wordfilter/delete', {
|
const res = await fetch('/api/v2/admin/wordfilter/delete', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-CSRF-Token': window.f0ckSession?.csrf_token
|
'X-CSRF-Token': window.f0ckSession?.csrf_token
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ id })
|
body: JSON.stringify({ id })
|
||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
loadRules();
|
loadRules();
|
||||||
} else {
|
} else {
|
||||||
alert('Delete failed: ' + data.msg);
|
alert('Delete failed: ' + data.msg);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
alert('Error deleting: ' + e.message);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
};
|
||||||
alert('Error deleting: ' + e.message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function escapeHtml(str) {
|
window.wordfilterAdmin = {
|
||||||
if (!str) return '';
|
loadRules,
|
||||||
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
addRule,
|
||||||
}
|
deleteRule
|
||||||
|
};
|
||||||
|
|
||||||
// Initialize view
|
// Initialize view
|
||||||
loadRules();
|
loadRules();
|
||||||
</script>
|
})();
|
||||||
|
</script>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user