Illustrations/web/templates/settings/home.html

312 lines
11 KiB
HTML
Raw 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.

{% extends "base.html" %}
{% block body_class %}themed-bg{% endblock %}
{% block content %}
<div class="container">
<h1 class="page-title">Settings</h1>
<!-- Blank Settings box -->
<div class="card" style="padding:20px; margin-bottom:20px;">
<p class="muted small">Add your settings here when youre ready.</p>
<div class="card" style="margin-top:18px; padding:20px;">
<h2 class="page-title">Privacy · Clear My History</h2>
<p class="muted" style="margin:-6px 0 12px;">
Remove your Recent Searches and Recently Viewed illustrations from the Search page.
</p>
<button id="clear-history-btn" class="btn btn-danger">Clear History</button>
</div>
<!-- toast -->
<div id="toast-clear-history"
style="position:fixed; right:16px; bottom:16px; padding:10px 14px; border-radius:10px;
background:#111827; color:#fff; box-shadow:0 6px 20px rgba(0,0,0,.25);
opacity:0; pointer-events:none; transition:opacity .25s;">
History cleared.
</div>
<script>
(function(){
function getCookie(name) {
var value = "; " + document.cookie;
var parts = value.split("; " + name + "=");
if (parts.length === 2) return parts.pop().split(";").shift();
}
var btn = document.getElementById("clear-history-btn");
var toast = document.getElementById("toast-clear-history");
if (!btn) return;
btn.addEventListener("click", function(){
fetch("{% url 'clear_history' %}", {
method: "POST",
headers: { "X-CSRFToken": getCookie("csrftoken") }
})
.then(function(r){ return r.ok ? r.json() : Promise.reject(); })
.then(function(){
if (toast){
toast.style.opacity = "1";
setTimeout(function(){ toast.style.opacity = "0"; }, 1500);
}
})
.catch(function(){ /* silent per spec */ });
});
})();
</script>
<!-- Your future settings content goes here -->
<div class="card" style="padding:20px; margin-bottom:20px;">
<button id="darkToggle" class="btn btn-secondary">Toggle Dark Mode</button>
<div class="card" style="padding:20px; margin-bottom:20px;">
<label style="display:flex;gap:10px;align-items:center;">
<input id="highlightHitsToggle" type="checkbox">
<span>Highlight search hits on entry page</span>
</label>
<p class="small muted" style="margin-top:6px;">When on, your last search terms are lightly highlighted on result pages—but only in the fields you searched.</p>
</div>
</div>
</div>
{% if user.is_authenticated and user.is_superuser %}
<h1 class="page-title">Tools</h1>
<!-- Tools box -->
<div class="card" style="padding:20px; margin-bottom:20px;">
<!-- Scripture Normalizer -->
<div class="tool-block">
<h3>Scripture Normalizer</h3>
<p class="small muted">
Standardizes Bible references (book abbreviations, separators, and repeats book names as needed).
Use preview first; Apply will modify all <code>scripture_raw</code> fields.
</p>
<div class="tool-actions">
<form method="get" action="{% url 'normalize_scripture' %}" class="inline">
<input type="number" min="0" name="limit" placeholder="Preview limit (optional)" class="tool-input">
<button class="btn btn-secondary">Preview (dry-run)</button>
</form>
<form method="post" action="{% url 'normalize_scripture' %}" class="inline">
{% csrf_token %}
<button class="btn btn-danger"
onclick="return confirm('Apply Scripture Normalizer to ALL entries? This will modify scripture_raw fields.');">
Apply to all
</button>
</form>
</div>
</div>
<hr class="tool-sep">
<!-- Source Normalizer -->
<div class="tool-block">
<h3>Source Normalizer</h3>
<p class="small muted">
Converts Watchtower/Awake!/Yearbook/KM references (e.g., “March 15, 2013 WT page 14”) into WOL short-codes
like <code>w13 3/15 p.14</code>. Non-JW sources are left unchanged.
</p>
<div class="tool-actions">
<form method="get" action="{% url 'normalize_source' %}" class="inline">
<input type="number" min="0" name="limit" placeholder="Preview limit (optional)" class="tool-input">
<button class="btn btn-secondary">Preview (dry-run)</button>
</form>
<form method="post" action="{% url 'normalize_source' %}" class="inline">
{% csrf_token %}
<button class="btn btn-danger"
onclick="return confirm('Apply Source Normalizer to ALL entries? This cannot be undone. Make a backup first.');">
Apply to all
</button>
</form>
</div>
</div>
<hr class="tool-sep">
<!-- Subject Normalizer -->
<div class="tool-block">
<h3>Subject Normalizer</h3>
<p class="small muted">
Normalize the <strong>Subject</strong> field so individual subjects are comma-separated.
Preserves multi-word subjects; lightly fixes separators like “;” and spaced dashes.
Uses your <code>subjects.txt</code> catalog when available.
</p>
<div class="tool-actions">
<form method="get" action="{% url 'normalize_subjects' %}" class="inline">
<input type="number" min="0" name="limit" placeholder="Preview limit (optional)" class="tool-input">
<button class="btn btn-secondary">Dry-run Preview</button>
</form>
<form method="post" action="{% url 'normalize_subjects' %}" class="inline">
{% csrf_token %}
<button class="btn btn-danger"
onclick="return confirm('Apply subject normalization to all entries? This cannot be undone. Make a backup first.')">
Apply to all
</button>
</form>
</div>
</div>
</div>
{% endif %}
{% if user.is_superuser %}
<div class="card" style="margin-top:18px; padding:20px;">
<h2 class="page-title">Release Announcement</h2>
<p class="muted" style="margin:-6px 0 12px;">
Publish a message to show once to each user on their next search page load.
</p>
<form method="post" action="{% url 'announcement_tools' %}" class="vertical-stack" style="gap:10px;">
{% csrf_token %}
{% if announcement_form %}
{{ announcement_form.as_p }}
{% else %}
{# If this page isn't rendered via announcement_tools view, render a fresh form #}
<label>Title (optional)</label>
<input type="text" name="title" class="tool-input">
<label>Message</label>
<textarea name="message" rows="6" class="tool-input" placeholder="Whats new in this release…"></textarea>
<label style="display:flex; gap:6px; align-items:center; margin-top:8px;">
+ <input type="checkbox" name="is_active" checked> Active
+ </label>
{% endif %}
<button class="btn btn-primary">Publish</button>
</form>
{% if announcements_recent %}
<h3 style="margin-top:16px;">Recent</h3>
<table class="table">
<thead>
<tr><th>ID</th><th>Title</th><th>Active?</th><th>Window</th><th>Created</th></tr>
</thead>
<tbody>
{% for a in announcements_recent %}
<tr>
<td>#{{ a.id }}</td>
<td>{{ a.title|default:"(untitled)" }}</td>
<td>{{ a.is_current|yesno:"✅,—" }}</td>
<td>
{{ a.start_at|date:"Y-m-d H:i" }}
{% if a.end_at %} → {{ a.end_at|date:"Y-m-d H:i" }}{% endif %}
</td>
<td>{{ a.created_at|date:"Y-m-d H:i" }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</div>
{% endif %}
{% if user.is_superuser %}
<div class="card" style="margin-top:18px; padding:20px;">
<h2 class="page-title">Security · Login Attempts (7 days)</h2>
<p class="muted" style="margin:-6px 0 12px;">
View successful and failed login attempts from the last week.
</p>
<a class="btn btn-secondary" href="{% url 'login_attempts' %}">Open Login Attempts</a>
</div>
{% endif %}
{% if user.is_superuser %}
<div class="card" style="margin-top:18px;">
<h2 class="page-title">Initial Setup</h2>
<p class="muted" style="margin:-6px 0 12px;">
Superuser-only utilities for first-time population and maintenance.
</p>
<div style="display:flex; flex-wrap:wrap; gap:10px;">
<a class="btn btn-primary" href="{% url 'import_wizard' %}">
Import CSV
</a>
<a class="btn btn-danger" href="{% url 'delete_all_entries' %}">
Delete Database
</a>
</div>
<div class="small muted" style="margin-top:10px;">
CSV format (columns, in order): <code>Subject, Illustration, Application, Scripture, Source, Talk Title, Talk Number, Code, Date, Date Edited</code>.
The Import CSV page supports a dry-run preview before applying changes.
</div>
</div>
{% endif %}
</div>
<style>
.tool-block { margin-bottom:16px; }
.tool-block h3 {
margin:0 0 6px;
font-size:18px;
color:#0f172a;
}
.tool-actions {
display:flex;
flex-wrap:wrap;
gap:10px;
margin-top:8px;
}
.tool-input {
border:1px solid var(--border, #d1d5db);
border-radius:8px;
padding:6px 8px;
font-size:14px;
margin-right:6px;
}
.tool-sep {
border:none;
border-top:1px solid var(--border, #e5e7eb);
margin:18px 0;
}
</style>
<script>
document.addEventListener("DOMContentLoaded", function () {
const toggle = document.getElementById("darkToggle");
if (!toggle) return;
toggle.addEventListener("click", () => {
document.body.classList.toggle("dark-mode");
localStorage.setItem("darkMode", document.body.classList.contains("dark-mode"));
});
// Restore preference on load
if (localStorage.getItem("darkMode") === "true") {
document.body.classList.add("dark-mode");
}
});
</script>
<script>
document.addEventListener("DOMContentLoaded", async () => {
const toggle = document.getElementById("highlightHitsToggle");
if (!toggle) return;
// Load current prefs
try {
const res = await fetch("/api/get-prefs/");
const data = await res.json();
if (data && typeof data.highlight_search_hits !== "undefined") {
toggle.checked = !!data.highlight_search_hits;
}
} catch (e) { /* ignore */ }
// Save on change
toggle.addEventListener("change", async () => {
const form = new FormData();
form.append("enabled", toggle.checked ? "true" : "false");
try {
await fetch("/api/set-highlight-hits/", { method: "POST", body: form, headers: { "X-CSRFToken": getCookie("csrftoken") }});
} catch (e) {
alert("Could not save the setting. Please try again.");
}
});
// Standard Django CSRF helper
function getCookie(name) {
const m = document.cookie.match("(^|;)\\s*" + name + "\\s*=\\s*([^;]+)");
return m ? m.pop() : "";
}
});
</script>
{% endblock %}