feat: rename PROPAGANDA to UNREDACTED, add nav block-reveal animation and global search
This commit is contained in:
parent
e6b36c1ca8
commit
8bb4c6f727
10 changed files with 562 additions and 50 deletions
62
api/app.py
62
api/app.py
|
|
@ -7,7 +7,7 @@ from pathlib import Path
|
|||
from email.mime.text import MIMEText
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
|
||||
from flask import Flask, request, jsonify, abort, send_file
|
||||
from flask import Flask, request, jsonify, abort, send_file, redirect
|
||||
from flask_cors import CORS
|
||||
import jwt
|
||||
import requests as req
|
||||
|
|
@ -1526,29 +1526,29 @@ def api_govdomains_stats():
|
|||
})
|
||||
|
||||
|
||||
# ─── Propaganda: Declassified Document Archive ────────
|
||||
_propaganda_cache = None
|
||||
_propaganda_mtime = 0
|
||||
# ─── Unredacted: Declassified Document Archive ────────
|
||||
_unredacted_cache = None
|
||||
_unredacted_mtime = 0
|
||||
|
||||
def _load_propaganda():
|
||||
global _propaganda_cache, _propaganda_mtime
|
||||
p = DATA_DIR / 'propaganda.json'
|
||||
def _load_unredacted():
|
||||
global _unredacted_cache, _unredacted_mtime
|
||||
p = DATA_DIR / 'unredacted.json'
|
||||
if not p.exists():
|
||||
return {'categories': []}
|
||||
mt = p.stat().st_mtime
|
||||
if _propaganda_cache is None or mt != _propaganda_mtime:
|
||||
if _unredacted_cache is None or mt != _unredacted_mtime:
|
||||
with open(p, encoding='utf-8') as f:
|
||||
_propaganda_cache = json.load(f)
|
||||
_propaganda_mtime = mt
|
||||
return _propaganda_cache
|
||||
_unredacted_cache = json.load(f)
|
||||
_unredacted_mtime = mt
|
||||
return _unredacted_cache
|
||||
|
||||
@app.route('/api/propaganda')
|
||||
def get_propaganda():
|
||||
return jsonify(_load_propaganda())
|
||||
@app.route('/api/unredacted')
|
||||
def get_unredacted():
|
||||
return jsonify(_load_unredacted())
|
||||
|
||||
@app.route('/api/propaganda/categories')
|
||||
def get_propaganda_categories():
|
||||
db = _load_propaganda()
|
||||
@app.route('/api/unredacted/categories')
|
||||
def get_unredacted_categories():
|
||||
db = _load_unredacted()
|
||||
cats = []
|
||||
for c in db.get('categories', []):
|
||||
n_countries = len(c.get('countries', []))
|
||||
|
|
@ -1565,18 +1565,18 @@ def get_propaganda_categories():
|
|||
})
|
||||
return jsonify({'categories': cats})
|
||||
|
||||
@app.route('/api/propaganda/category/<cat_id>')
|
||||
def get_propaganda_category(cat_id):
|
||||
db = _load_propaganda()
|
||||
@app.route('/api/unredacted/category/<cat_id>')
|
||||
def get_unredacted_category(cat_id):
|
||||
db = _load_unredacted()
|
||||
for c in db.get('categories', []):
|
||||
if c['id'] == cat_id:
|
||||
return jsonify(c)
|
||||
abort(404, f'Category {cat_id} not found')
|
||||
|
||||
@app.route('/api/propaganda/upload', methods=['POST'])
|
||||
@app.route('/api/unredacted/upload', methods=['POST'])
|
||||
@require_auth
|
||||
def upload_propaganda_doc():
|
||||
"""Upload a PDF to the propaganda archive. Future admin use."""
|
||||
def upload_unredacted_doc():
|
||||
"""Upload a PDF to the unredacted archive. Future admin use."""
|
||||
try:
|
||||
if 'file' not in request.files:
|
||||
return jsonify({'error': 'No file uploaded'}), 400
|
||||
|
|
@ -1587,8 +1587,7 @@ def upload_propaganda_doc():
|
|||
return jsonify({'error': 'category and collection required'}), 400
|
||||
if not f.filename.lower().endswith('.pdf'):
|
||||
return jsonify({'error': 'Only PDF files accepted'}), 400
|
||||
# Save file — this saves locally; on VPS would save to /propaganda/docs/
|
||||
save_dir = Path('/var/www/jaeswift-homepage/propaganda/docs') / cat_id / col_id
|
||||
save_dir = Path('/var/www/jaeswift-homepage/unredacted/docs') / cat_id / col_id
|
||||
save_dir.mkdir(parents=True, exist_ok=True)
|
||||
dest = save_dir / f.filename
|
||||
f.save(str(dest))
|
||||
|
|
@ -1596,5 +1595,18 @@ def upload_propaganda_doc():
|
|||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
# ─── Legacy redirects: /api/propaganda → /api/unredacted ────────
|
||||
@app.route('/api/propaganda')
|
||||
def redirect_propaganda():
|
||||
return redirect('/api/unredacted', code=301)
|
||||
|
||||
@app.route('/api/propaganda/categories')
|
||||
def redirect_propaganda_categories():
|
||||
return redirect('/api/unredacted/categories', code=301)
|
||||
|
||||
@app.route('/api/propaganda/category/<cat_id>')
|
||||
def redirect_propaganda_category(cat_id):
|
||||
return redirect(f'/api/unredacted/category/{cat_id}', code=301)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0', port=5000, debug=False)
|
||||
|
|
|
|||
|
|
@ -86,8 +86,8 @@
|
|||
"description": "16,000+ free resources catalogued"
|
||||
},
|
||||
{
|
||||
"label": "PROPAGANDA",
|
||||
"url": "/depot/propaganda",
|
||||
"label": "UNREDACTED",
|
||||
"url": "/depot/unredacted",
|
||||
"description": "Classified documents & briefings"
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/* ═══════════════════════════════════════════════════════
|
||||
AWESOME LISTS // Propaganda Resource Database
|
||||
AWESOME LISTS // Unredacted Resource Database
|
||||
═══════════════════════════════════════════════════════ */
|
||||
|
||||
.al-container {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/* ═══════════════════════════════════════════════════════
|
||||
PROPAGANDA — Declassified Document Archive
|
||||
UNREDACTED — Declassified Document Archive
|
||||
═══════════════════════════════════════════════════════ */
|
||||
|
||||
/* ─── Stats Bar ──────────────────────────────────────── */
|
||||
|
|
@ -879,4 +879,208 @@
|
|||
.prop-grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════
|
||||
GLOBAL ARCHIVE SEARCH
|
||||
═══════════════════════════════════════════════════════ */
|
||||
|
||||
.ur-search-container {
|
||||
margin: 1.5rem 0 2rem;
|
||||
}
|
||||
|
||||
.ur-search-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #0a0a0a;
|
||||
border: 1px solid #00cc33;
|
||||
border-radius: 2px;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
box-shadow: 0 0 8px rgba(0, 204, 51, 0.15), inset 0 0 20px rgba(0, 0, 0, 0.5);
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.ur-search-bar:focus-within {
|
||||
border-color: #00ff41;
|
||||
box-shadow: 0 0 15px rgba(0, 204, 51, 0.3), inset 0 0 20px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.ur-search-icon {
|
||||
color: #00cc33;
|
||||
font-size: 1.2rem;
|
||||
padding: 0.75rem 0 0.75rem 1rem;
|
||||
opacity: 0.7;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.ur-search-input {
|
||||
flex: 1;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: #e0e0e0;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.85rem;
|
||||
letter-spacing: 0.08em;
|
||||
padding: 0.75rem 0.75rem;
|
||||
outline: none;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.ur-search-input::placeholder {
|
||||
color: #555;
|
||||
letter-spacing: 0.1em;
|
||||
}
|
||||
|
||||
.ur-search-clear {
|
||||
color: #888;
|
||||
font-size: 1rem;
|
||||
padding: 0.75rem 1rem;
|
||||
cursor: pointer;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.ur-search-clear:hover {
|
||||
color: #ff4444;
|
||||
}
|
||||
|
||||
.ur-search-status {
|
||||
margin-top: 0.75rem;
|
||||
min-height: 1.2rem;
|
||||
}
|
||||
|
||||
.ur-search-count {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 0.12em;
|
||||
color: #00cc33;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.ur-search-none {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 0.1em;
|
||||
color: #ff6b35;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.ur-search-results {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.ur-results-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.ur-result-card {
|
||||
background: linear-gradient(135deg, rgba(0, 204, 51, 0.04), rgba(0, 0, 0, 0.4));
|
||||
border: 1px solid rgba(0, 204, 51, 0.15);
|
||||
border-radius: 2px;
|
||||
padding: 1rem 1.2rem;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s, background 0.2s, transform 0.15s;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ur-result-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0; left: 0;
|
||||
width: 3px;
|
||||
height: 100%;
|
||||
background: #00cc33;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.ur-result-card:hover {
|
||||
border-color: rgba(0, 204, 51, 0.4);
|
||||
background: linear-gradient(135deg, rgba(0, 204, 51, 0.08), rgba(0, 0, 0, 0.5));
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.ur-result-card:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.ur-result-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.6rem;
|
||||
margin-bottom: 0.6rem;
|
||||
}
|
||||
|
||||
.ur-result-icon {
|
||||
font-size: 1.1rem;
|
||||
flex-shrink: 0;
|
||||
margin-top: 0.1rem;
|
||||
}
|
||||
|
||||
.ur-result-title {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.82rem;
|
||||
font-weight: 600;
|
||||
color: #e0e0e0;
|
||||
letter-spacing: 0.04em;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.ur-result-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.4rem;
|
||||
margin-bottom: 0.6rem;
|
||||
}
|
||||
|
||||
.ur-result-badge {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.6rem;
|
||||
letter-spacing: 0.08em;
|
||||
padding: 0.2rem 0.5rem;
|
||||
border-radius: 1px;
|
||||
text-transform: uppercase;
|
||||
background: rgba(0, 204, 51, 0.1);
|
||||
color: #00cc33;
|
||||
border: 1px solid rgba(0, 204, 51, 0.2);
|
||||
}
|
||||
|
||||
.ur-result-badge-country {
|
||||
background: rgba(255, 193, 7, 0.08);
|
||||
color: #ffc107;
|
||||
border-color: rgba(255, 193, 7, 0.2);
|
||||
}
|
||||
|
||||
.ur-result-badge-col {
|
||||
background: rgba(100, 149, 237, 0.08);
|
||||
color: #6495ed;
|
||||
border-color: rgba(100, 149, 237, 0.2);
|
||||
}
|
||||
|
||||
.ur-result-desc {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.72rem;
|
||||
color: #888;
|
||||
line-height: 1.5;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
mark.ur-highlight {
|
||||
background: rgba(0, 204, 51, 0.25);
|
||||
color: #00ff41;
|
||||
padding: 0 2px;
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.ur-results-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.ur-search-input {
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
}
|
||||
|
|
@ -42,9 +42,9 @@
|
|||
<div class="section-card-desc">The largest classified index of free resources on the internet — catalogued by type.</div>
|
||||
<div class="section-card-arrow">ENTER →</div>
|
||||
</a>
|
||||
<a href="/depot/propaganda" class="section-card">
|
||||
<a href="/depot/unredacted" class="section-card">
|
||||
<div class="section-card-icon">▧</div>
|
||||
<div class="section-card-title">PROPAGANDA</div>
|
||||
<div class="section-card-title">UNREDACTED</div>
|
||||
<div class="section-card-desc">Recovered publications and documents preserved in digital format. Browse on-site.</div>
|
||||
<div class="section-card-arrow">ENTER →</div>
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>JAESWIFT // PROPAGANDA — Declassified Document Archive</title>
|
||||
<title>JAESWIFT // UNREDACTED — Declassified Document Archive</title>
|
||||
<meta name="description" content="Declassified document archive. Browse categorised intelligence files, military reports, and government documents with built-in PDF viewer.">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=JetBrains+Mono:wght@300;400;500;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/section.css">
|
||||
<link rel="stylesheet" href="/css/propaganda.css">
|
||||
<link rel="stylesheet" href="/css/unredacted.css">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -36,18 +36,18 @@
|
|||
<span class="separator">/</span>
|
||||
<a href="/depot">DEPOT</a>
|
||||
<span class="separator">/</span>
|
||||
<span class="current">PROPAGANDA</span>
|
||||
<span class="current">UNREDACTED</span>
|
||||
</div>
|
||||
|
||||
<section class="section-header" style="padding-top: calc(var(--nav-height) + 1.5rem);">
|
||||
<div class="section-header-label">DEPOT // DECLASSIFIED ARCHIVE</div>
|
||||
<h1 class="section-header-title">PROPAGANDA</h1>
|
||||
<h1 class="section-header-title">UNREDACTED</h1>
|
||||
<p class="section-header-sub">> Declassified documents, intelligence briefings, and government files. Browse the archive.</p>
|
||||
</section>
|
||||
|
||||
<section class="subpage-content" style="max-width: clamp(1200px, 90vw, 2400px); margin: 0 auto; padding: 0 2rem 3rem;">
|
||||
<div id="propagandaRoot">
|
||||
<div class="prop-loading">INITIALISING PROPAGANDA ARCHIVE...</div>
|
||||
<div id="unredactedRoot">
|
||||
<div class="prop-loading">INITIALISING UNREDACTED ARCHIVE...</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
|
@ -66,6 +66,6 @@
|
|||
<script src="/js/wallet-connect.js"></script>
|
||||
<script src="/js/nav.js"></script>
|
||||
<script src="/js/clock.js"></script>
|
||||
<script src="/js/propaganda.js"></script>
|
||||
<script src="/js/unredacted.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
93
js/nav.js
93
js/nav.js
|
|
@ -87,6 +87,8 @@
|
|||
initMobileDropdowns();
|
||||
|
||||
// Inject shared wrapper for SOL price + wallet, then inject both
|
||||
// Start UNREDACTED block-reveal animation
|
||||
initUnredactedAnimation();
|
||||
injectSolWalletGroup();
|
||||
} catch (err) {
|
||||
console.warn('Nav load failed, keeping existing:', err);
|
||||
|
|
@ -449,6 +451,97 @@
|
|||
if (dd) dd.classList.add('hidden');
|
||||
}
|
||||
|
||||
// ─── UNREDACTED Block-Reveal Animation ──────────────────────
|
||||
function initUnredactedAnimation() {
|
||||
// Find the UNREDACTED dropdown link
|
||||
const allLinks = document.querySelectorAll('.dropdown-link');
|
||||
let targetLink = null;
|
||||
for (const link of allLinks) {
|
||||
// Check the first text node or childNode text for UNREDACTED
|
||||
const txt = link.childNodes[0];
|
||||
if (txt && txt.nodeType === 3 && txt.textContent.trim() === 'UNREDACTED') {
|
||||
targetLink = link;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!targetLink) return;
|
||||
|
||||
const WORD = 'UNREDACTED';
|
||||
const LEN = WORD.length;
|
||||
const FRAME_MS = 90;
|
||||
const HOLD_REVEALED = 2000;
|
||||
const HOLD_REDACTED = 1000;
|
||||
const BLOCK = '\u2588'; // █
|
||||
|
||||
// Wrap the text node in a span so we can animate just the label text
|
||||
const originalText = targetLink.childNodes[0];
|
||||
const animSpan = document.createElement('span');
|
||||
animSpan.className = 'unredacted-anim';
|
||||
animSpan.textContent = WORD;
|
||||
targetLink.replaceChild(animSpan, originalText);
|
||||
|
||||
// Add minimal CSS for the animation
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
.unredacted-anim { font-family: inherit; }
|
||||
.unredacted-anim .ur-block { color: #00cc33; opacity: 0.35; }
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
function renderFrame(revealCount) {
|
||||
// revealCount = number of letters revealed from the left
|
||||
let html = '';
|
||||
for (let i = 0; i < LEN; i++) {
|
||||
if (i < revealCount) {
|
||||
html += WORD[i];
|
||||
} else {
|
||||
html += '<span class="ur-block">' + BLOCK + '</span>';
|
||||
}
|
||||
}
|
||||
animSpan.innerHTML = html;
|
||||
}
|
||||
|
||||
let frame = 0;
|
||||
let direction = 1; // 1 = revealing, -1 = redacting
|
||||
let holdTimer = null;
|
||||
|
||||
function tick() {
|
||||
if (direction === 1) {
|
||||
// Forward reveal
|
||||
renderFrame(frame);
|
||||
frame++;
|
||||
if (frame > LEN) {
|
||||
// Fully revealed — hold
|
||||
animSpan.textContent = WORD;
|
||||
direction = 0;
|
||||
holdTimer = setTimeout(() => {
|
||||
direction = -1;
|
||||
frame = LEN;
|
||||
holdTimer = null;
|
||||
}, HOLD_REVEALED);
|
||||
}
|
||||
} else if (direction === -1) {
|
||||
// Reverse redact
|
||||
frame--;
|
||||
renderFrame(frame);
|
||||
if (frame <= 0) {
|
||||
// Fully redacted — hold
|
||||
direction = 0;
|
||||
holdTimer = setTimeout(() => {
|
||||
direction = 1;
|
||||
frame = 0;
|
||||
holdTimer = null;
|
||||
}, HOLD_REDACTED);
|
||||
}
|
||||
}
|
||||
// direction === 0 means we're holding, do nothing
|
||||
}
|
||||
|
||||
// Start fully redacted
|
||||
renderFrame(0);
|
||||
setInterval(tick, FRAME_MS);
|
||||
}
|
||||
|
||||
// Run on DOM ready
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
|
|
|
|||
|
|
@ -1,23 +1,23 @@
|
|||
/* ═══════════════════════════════════════════════════════
|
||||
PROPAGANDA — Declassified Document Archive
|
||||
UNREDACTED — Declassified Document Archive
|
||||
SPA Engine with hash routing & PDF.js viewer
|
||||
═══════════════════════════════════════════════════════ */
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
const ROOT = document.getElementById('propagandaRoot');
|
||||
const PDF_BASE = '/propaganda/docs';
|
||||
const ROOT = document.getElementById('unredactedRoot');
|
||||
const PDF_BASE = '/unredacted/docs';
|
||||
let DATA = null;
|
||||
|
||||
// ─── Data Loading ────────────────────────────────────
|
||||
async function loadData() {
|
||||
try {
|
||||
const r = await fetch('/api/propaganda');
|
||||
const r = await fetch('/api/unredacted');
|
||||
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
||||
DATA = await r.json();
|
||||
} catch (e) {
|
||||
console.error('PROPAGANDA: data load failed', e);
|
||||
console.error('UNREDACTED: data load failed', e);
|
||||
ROOT.innerHTML = `<div class="prop-empty">
|
||||
<div class="prop-empty-icon">⚠️</div>
|
||||
<div class="prop-empty-title">DATA FEED OFFLINE</div>
|
||||
|
|
@ -121,10 +121,21 @@
|
|||
<div class="prop-stat"><span class="prop-stat-label">DOCUMENTS</span><span class="prop-stat-value red">${stats.docs}</span></div>
|
||||
</div>`;
|
||||
|
||||
html += breadcrumb([{ label: 'PROPAGANDA' }]);
|
||||
html += breadcrumb([{ label: 'UNREDACTED' }]);
|
||||
|
||||
// ─── Global Search Bar ────────────────────────────────
|
||||
html += `<div class="ur-search-container">
|
||||
<div class="ur-search-bar">
|
||||
<span class="ur-search-icon">⌕</span>
|
||||
<input type="text" id="urSearchInput" class="ur-search-input" placeholder="SEARCH CLASSIFIED ARCHIVES..." autocomplete="off" spellcheck="false">
|
||||
<span class="ur-search-clear" id="urSearchClear" style="display:none;">✕</span>
|
||||
</div>
|
||||
<div class="ur-search-status" id="urSearchStatus"></div>
|
||||
<div class="ur-search-results" id="urSearchResults"></div>
|
||||
</div>`;
|
||||
|
||||
html += '<div id="urCategorySection">';
|
||||
html += '<div class="prop-section-label">SELECT CATEGORY</div>';
|
||||
html += '<div class="prop-grid">';
|
||||
|
||||
DATA.categories.forEach(cat => {
|
||||
const nCountries = countCountries(cat);
|
||||
|
|
@ -144,7 +155,11 @@
|
|||
});
|
||||
|
||||
html += '</div>';
|
||||
html += '</div>'; // close #urCategorySection
|
||||
ROOT.innerHTML = html;
|
||||
|
||||
// ─── Bind search events ────────────────────────────────
|
||||
initSearchListeners();
|
||||
}
|
||||
|
||||
// ─── View: Category (Country Selector + Collections) ──
|
||||
|
|
@ -156,7 +171,7 @@
|
|||
html += `<div class="prop-classification">DECLASSIFIED // ${esc(cat.name)} // ACCESS GRANTED</div>`;
|
||||
|
||||
html += breadcrumb([
|
||||
{ label: 'PROPAGANDA', hash: '' },
|
||||
{ label: 'UNREDACTED', hash: '' },
|
||||
{ label: cat.name }
|
||||
]);
|
||||
|
||||
|
|
@ -251,7 +266,7 @@
|
|||
html += `<div class="prop-classification">DECLASSIFIED // ${esc(col.name).toUpperCase()} // ACCESS GRANTED</div>`;
|
||||
|
||||
html += breadcrumb([
|
||||
{ label: 'PROPAGANDA', hash: '' },
|
||||
{ label: 'UNREDACTED', hash: '' },
|
||||
{ label: cat.name, hash: `category/${catId}` },
|
||||
{ label: `${country.flag || ''} ${country.name}`, hash: `country/${catId}/${countryCode}` },
|
||||
{ label: col.name }
|
||||
|
|
@ -309,7 +324,7 @@
|
|||
html += `<div class="prop-classification">DECLASSIFIED // ${esc(doc.title).toUpperCase()}</div>`;
|
||||
|
||||
html += breadcrumb([
|
||||
{ label: 'PROPAGANDA', hash: '' },
|
||||
{ label: 'UNREDACTED', hash: '' },
|
||||
{ label: cat.name, hash: `category/${catId}` },
|
||||
{ label: `${country.flag || ''} ${country.name}`, hash: `country/${catId}/${countryCode}` },
|
||||
{ label: col.name, hash: `collection/${catId}/${countryCode}/${colId}` },
|
||||
|
|
@ -582,7 +597,7 @@
|
|||
});
|
||||
pdfAllText.push({ pageNum: i, text: fullText, items: tc.items, itemOffsets: itemOffsets });
|
||||
}
|
||||
console.log('PROPAGANDA: Extracted text from ' + pdfAllText.length + ' pages');
|
||||
console.log('UNREDACTED: Extracted text from ' + pdfAllText.length + ' pages');
|
||||
}
|
||||
|
||||
// ─── Search Controls Init ────────────────────────────
|
||||
|
|
@ -781,6 +796,157 @@
|
|||
}
|
||||
|
||||
|
||||
// ─── Global Archive Search ─────────────────────────────
|
||||
let searchDebounceTimer = null;
|
||||
|
||||
function initSearchListeners() {
|
||||
const input = document.getElementById('urSearchInput');
|
||||
const clearBtn = document.getElementById('urSearchClear');
|
||||
if (!input) return;
|
||||
|
||||
input.addEventListener('input', () => {
|
||||
clearTimeout(searchDebounceTimer);
|
||||
const val = input.value.trim();
|
||||
clearBtn.style.display = val.length > 0 ? 'inline-block' : 'none';
|
||||
searchDebounceTimer = setTimeout(() => {
|
||||
globalArchiveSearch(val);
|
||||
}, 300);
|
||||
});
|
||||
|
||||
clearBtn.addEventListener('click', () => {
|
||||
input.value = '';
|
||||
clearBtn.style.display = 'none';
|
||||
globalArchiveSearch('');
|
||||
input.focus();
|
||||
});
|
||||
|
||||
input.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
input.value = '';
|
||||
clearBtn.style.display = 'none';
|
||||
globalArchiveSearch('');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function globalArchiveSearch(query) {
|
||||
const resultsEl = document.getElementById('urSearchResults');
|
||||
const statusEl = document.getElementById('urSearchStatus');
|
||||
const catSection = document.getElementById('urCategorySection');
|
||||
if (!resultsEl || !statusEl) return;
|
||||
|
||||
if (!query || query.length === 0) {
|
||||
resultsEl.innerHTML = '';
|
||||
statusEl.innerHTML = '';
|
||||
if (catSection) catSection.style.display = '';
|
||||
return;
|
||||
}
|
||||
|
||||
const q = query.toLowerCase();
|
||||
const results = [];
|
||||
|
||||
if (!DATA || !DATA.categories) return;
|
||||
|
||||
DATA.categories.forEach(cat => {
|
||||
(cat.countries || []).forEach(cn => {
|
||||
(cn.collections || []).forEach(col => {
|
||||
(col.documents || []).forEach(doc => {
|
||||
const fields = [
|
||||
doc.title || '',
|
||||
doc.description || '',
|
||||
col.name || '',
|
||||
cat.name || '',
|
||||
cn.name || '',
|
||||
doc.year ? String(doc.year) : '',
|
||||
doc.classification || ''
|
||||
];
|
||||
const combined = fields.join(' ').toLowerCase();
|
||||
if (combined.includes(q)) {
|
||||
results.push({
|
||||
doc,
|
||||
catName: cat.name,
|
||||
catId: cat.id,
|
||||
catIcon: cat.icon || '📁',
|
||||
countryName: cn.name,
|
||||
countryCode: cn.code,
|
||||
countryFlag: cn.flag || '',
|
||||
colName: col.name,
|
||||
colId: col.id
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Also match collection-level (even if no docs match)
|
||||
const colFields = [col.name || '', col.description || '', cat.name || '', cn.name || ''].join(' ').toLowerCase();
|
||||
if (colFields.includes(q) && !results.find(r => r.colId === col.id && r.catId === cat.id && r.countryCode === cn.code && !r.doc.id)) {
|
||||
// Check if we already added docs from this collection
|
||||
const hasDocResults = results.some(r => r.colId === col.id && r.catId === cat.id && r.countryCode === cn.code);
|
||||
if (!hasDocResults) {
|
||||
results.push({
|
||||
doc: { title: col.name, description: col.description || 'Collection in ' + cat.name, id: '__col__' },
|
||||
catName: cat.name,
|
||||
catId: cat.id,
|
||||
catIcon: cat.icon || '📁',
|
||||
countryName: cn.name,
|
||||
countryCode: cn.code,
|
||||
countryFlag: cn.flag || '',
|
||||
colName: col.name,
|
||||
colId: col.id,
|
||||
isCollection: true
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Hide categories when searching
|
||||
if (catSection) catSection.style.display = 'none';
|
||||
|
||||
if (results.length === 0) {
|
||||
statusEl.innerHTML = '<span class="ur-search-none">NO MATCHING DOCUMENTS // REFINE SEARCH PARAMETERS</span>';
|
||||
resultsEl.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
|
||||
statusEl.innerHTML = `<span class="ur-search-count">FOUND ${results.length} MATCHING DOCUMENT${results.length === 1 ? '' : 'S'}</span>`;
|
||||
|
||||
let html = '<div class="ur-results-grid">';
|
||||
results.forEach(r => {
|
||||
const title = highlightMatch(esc(r.doc.title || 'Untitled'), q);
|
||||
const desc = r.doc.description ? highlightMatch(esc(truncateText(r.doc.description, 160)), q) : '';
|
||||
const hash = r.isCollection
|
||||
? `country/${r.catId}/${r.countryCode}`
|
||||
: `doc/${r.catId}/${r.countryCode}/${r.colId}/${r.doc.id}`;
|
||||
|
||||
html += `<div class="ur-result-card" onclick="location.hash='${hash}'">
|
||||
<div class="ur-result-header">
|
||||
<span class="ur-result-icon">${r.catIcon}</span>
|
||||
<span class="ur-result-title">${title}</span>
|
||||
</div>
|
||||
<div class="ur-result-meta">
|
||||
<span class="ur-result-badge">${esc(r.catName)}</span>
|
||||
<span class="ur-result-badge ur-result-badge-country">${r.countryFlag} ${esc(r.countryName)}</span>
|
||||
<span class="ur-result-badge ur-result-badge-col">${esc(r.colName)}</span>
|
||||
</div>
|
||||
${desc ? `<div class="ur-result-desc">${desc}</div>` : ''}
|
||||
</div>`;
|
||||
});
|
||||
html += '</div>';
|
||||
resultsEl.innerHTML = html;
|
||||
}
|
||||
|
||||
function highlightMatch(text, query) {
|
||||
if (!query) return text;
|
||||
const regex = new RegExp('(' + query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + ')', 'gi');
|
||||
return text.replace(regex, '<mark class="ur-highlight">$1</mark>');
|
||||
}
|
||||
|
||||
function truncateText(text, maxLen) {
|
||||
if (text.length <= maxLen) return text;
|
||||
return text.substring(0, maxLen).replace(/\s+\S*$/, '') + '...';
|
||||
}
|
||||
|
||||
// ─── Not Found / Empty ────────────────────────────────
|
||||
function renderNotFound(msg) {
|
||||
ROOT.innerHTML = `<div class="prop-empty">
|
||||
|
|
@ -840,7 +1006,7 @@
|
|||
|
||||
// ─── Init ─────────────────────────────────────────────
|
||||
async function init() {
|
||||
ROOT.innerHTML = '<div class="prop-loading">INITIALISING PROPAGANDA ARCHIVE...</div>';
|
||||
ROOT.innerHTML = '<div class="prop-loading">INITIALISING UNREDACTED ARCHIVE...</div>';
|
||||
await loadData();
|
||||
if (DATA) {
|
||||
window.addEventListener('hashchange', route);
|
||||
37
redownload_missing.sh
Normal file
37
redownload_missing.sh
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#!/bin/bash
|
||||
# Re-download missing UK MOD UFO PDFs
|
||||
MASTER_DIR="/var/www/jaeswift-homepage/propaganda/docs/ufo-uap/uk-mod-complete"
|
||||
BASE_DIR="/var/www/jaeswift-homepage/propaganda/docs/ufo-uap"
|
||||
|
||||
echo "[$(date)] Re-downloading 53 missing files"
|
||||
|
||||
wget --tries=5 \
|
||||
--wait=1 \
|
||||
--random-wait \
|
||||
--timeout=60 \
|
||||
--directory-prefix="$MASTER_DIR" \
|
||||
--no-check-certificate \
|
||||
--user-agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" \
|
||||
"https://documents.theblackvault.com/documents/ufos/UK/aug-2009-highlights-guide.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-1948.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-1958-1.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-1959.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-1960.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-1961.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-1962.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-1963.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-1964.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-1965.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-1970.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-1972.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-1974.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-1975.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-1976.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-1984-1.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-1985-1.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-1986-1-2.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-1987-1.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-1997-1-1.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-1999-1-1.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-2005-1.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-2006-1.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-2018-1-1.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-2019-1.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-2020-1.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-2021-1-1.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-2022-1-1.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-2023-1-1.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-2024-1-1.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-2025-1-1.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-2026-1-1.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-2027-1-1.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-2028-1-1.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-2030-1-1.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-2031-1.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-2032-1-1.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-2033-1.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-2034-1.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-2035-1.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-2036-1-1.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-2037-1-1.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-2038-1-1.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-2039-1-1.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-2040-1-1.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-2041-1-1.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-2043-1.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-2044-1-1.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-2046-1-1.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-2047-1.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-2048-1-1.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-2049-1-1.pdf" "https://documents.theblackvault.com/documents/ufos/UK/defe-24-2050-1-1.pdf"
|
||||
|
||||
echo "[$(date)] Re-download complete"
|
||||
|
||||
# Recreate ALL symlinks
|
||||
for f in "$MASTER_DIR"/defe-24-*.pdf; do
|
||||
[ -f "$f" ] && ln -sf "$f" "$BASE_DIR/uk-mod-ufo-desk/$(basename $f)"
|
||||
done
|
||||
|
||||
for f in "$MASTER_DIR"/defe-31-*.pdf; do
|
||||
[ -f "$f" ] && ln -sf "$f" "$BASE_DIR/di55-ufo-archive/$(basename $f)"
|
||||
done
|
||||
|
||||
[ -f "$MASTER_DIR/defe-24-1948.pdf" ] && ln -sf "$MASTER_DIR/defe-24-1948.pdf" "$BASE_DIR/rendlesham-forest/defe-24-1948.pdf"
|
||||
|
||||
for f in aug-2009-highlights-guide.pdf highlights-guide-09-08-11.pdf mar-2011-highlights-guide.pdf uk2009.pdf; do
|
||||
[ -f "$MASTER_DIR/$f" ] && ln -sf "$MASTER_DIR/$f" "$BASE_DIR/highlights-and-guides/$f"
|
||||
done
|
||||
|
||||
echo "[$(date)] Symlinks recreated"
|
||||
DOWN=$(ls -1 "$MASTER_DIR"/*.pdf 2>/dev/null | wc -l)
|
||||
SIZE=$(du -sh "$MASTER_DIR" | cut -f1)
|
||||
echo "[$(date)] Total: ${DOWN}/112 files (${SIZE})"
|
||||
Loading…
Add table
Reference in a new issue