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.text import MIMEText
|
||||||
from email.mime.multipart import MIMEMultipart
|
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
|
from flask_cors import CORS
|
||||||
import jwt
|
import jwt
|
||||||
import requests as req
|
import requests as req
|
||||||
|
|
@ -1526,29 +1526,29 @@ def api_govdomains_stats():
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
# ─── Propaganda: Declassified Document Archive ────────
|
# ─── Unredacted: Declassified Document Archive ────────
|
||||||
_propaganda_cache = None
|
_unredacted_cache = None
|
||||||
_propaganda_mtime = 0
|
_unredacted_mtime = 0
|
||||||
|
|
||||||
def _load_propaganda():
|
def _load_unredacted():
|
||||||
global _propaganda_cache, _propaganda_mtime
|
global _unredacted_cache, _unredacted_mtime
|
||||||
p = DATA_DIR / 'propaganda.json'
|
p = DATA_DIR / 'unredacted.json'
|
||||||
if not p.exists():
|
if not p.exists():
|
||||||
return {'categories': []}
|
return {'categories': []}
|
||||||
mt = p.stat().st_mtime
|
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:
|
with open(p, encoding='utf-8') as f:
|
||||||
_propaganda_cache = json.load(f)
|
_unredacted_cache = json.load(f)
|
||||||
_propaganda_mtime = mt
|
_unredacted_mtime = mt
|
||||||
return _propaganda_cache
|
return _unredacted_cache
|
||||||
|
|
||||||
@app.route('/api/propaganda')
|
@app.route('/api/unredacted')
|
||||||
def get_propaganda():
|
def get_unredacted():
|
||||||
return jsonify(_load_propaganda())
|
return jsonify(_load_unredacted())
|
||||||
|
|
||||||
@app.route('/api/propaganda/categories')
|
@app.route('/api/unredacted/categories')
|
||||||
def get_propaganda_categories():
|
def get_unredacted_categories():
|
||||||
db = _load_propaganda()
|
db = _load_unredacted()
|
||||||
cats = []
|
cats = []
|
||||||
for c in db.get('categories', []):
|
for c in db.get('categories', []):
|
||||||
n_countries = len(c.get('countries', []))
|
n_countries = len(c.get('countries', []))
|
||||||
|
|
@ -1565,18 +1565,18 @@ def get_propaganda_categories():
|
||||||
})
|
})
|
||||||
return jsonify({'categories': cats})
|
return jsonify({'categories': cats})
|
||||||
|
|
||||||
@app.route('/api/propaganda/category/<cat_id>')
|
@app.route('/api/unredacted/category/<cat_id>')
|
||||||
def get_propaganda_category(cat_id):
|
def get_unredacted_category(cat_id):
|
||||||
db = _load_propaganda()
|
db = _load_unredacted()
|
||||||
for c in db.get('categories', []):
|
for c in db.get('categories', []):
|
||||||
if c['id'] == cat_id:
|
if c['id'] == cat_id:
|
||||||
return jsonify(c)
|
return jsonify(c)
|
||||||
abort(404, f'Category {cat_id} not found')
|
abort(404, f'Category {cat_id} not found')
|
||||||
|
|
||||||
@app.route('/api/propaganda/upload', methods=['POST'])
|
@app.route('/api/unredacted/upload', methods=['POST'])
|
||||||
@require_auth
|
@require_auth
|
||||||
def upload_propaganda_doc():
|
def upload_unredacted_doc():
|
||||||
"""Upload a PDF to the propaganda archive. Future admin use."""
|
"""Upload a PDF to the unredacted archive. Future admin use."""
|
||||||
try:
|
try:
|
||||||
if 'file' not in request.files:
|
if 'file' not in request.files:
|
||||||
return jsonify({'error': 'No file uploaded'}), 400
|
return jsonify({'error': 'No file uploaded'}), 400
|
||||||
|
|
@ -1587,8 +1587,7 @@ def upload_propaganda_doc():
|
||||||
return jsonify({'error': 'category and collection required'}), 400
|
return jsonify({'error': 'category and collection required'}), 400
|
||||||
if not f.filename.lower().endswith('.pdf'):
|
if not f.filename.lower().endswith('.pdf'):
|
||||||
return jsonify({'error': 'Only PDF files accepted'}), 400
|
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/unredacted/docs') / cat_id / col_id
|
||||||
save_dir = Path('/var/www/jaeswift-homepage/propaganda/docs') / cat_id / col_id
|
|
||||||
save_dir.mkdir(parents=True, exist_ok=True)
|
save_dir.mkdir(parents=True, exist_ok=True)
|
||||||
dest = save_dir / f.filename
|
dest = save_dir / f.filename
|
||||||
f.save(str(dest))
|
f.save(str(dest))
|
||||||
|
|
@ -1596,5 +1595,18 @@ def upload_propaganda_doc():
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({'error': str(e)}), 500
|
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__':
|
if __name__ == '__main__':
|
||||||
app.run(host='0.0.0.0', port=5000, debug=False)
|
app.run(host='0.0.0.0', port=5000, debug=False)
|
||||||
|
|
|
||||||
|
|
@ -86,8 +86,8 @@
|
||||||
"description": "16,000+ free resources catalogued"
|
"description": "16,000+ free resources catalogued"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "PROPAGANDA",
|
"label": "UNREDACTED",
|
||||||
"url": "/depot/propaganda",
|
"url": "/depot/unredacted",
|
||||||
"description": "Classified documents & briefings"
|
"description": "Classified documents & briefings"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/* ═══════════════════════════════════════════════════════
|
/* ═══════════════════════════════════════════════════════
|
||||||
AWESOME LISTS // Propaganda Resource Database
|
AWESOME LISTS // Unredacted Resource Database
|
||||||
═══════════════════════════════════════════════════════ */
|
═══════════════════════════════════════════════════════ */
|
||||||
|
|
||||||
.al-container {
|
.al-container {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/* ═══════════════════════════════════════════════════════
|
/* ═══════════════════════════════════════════════════════
|
||||||
PROPAGANDA — Declassified Document Archive
|
UNREDACTED — Declassified Document Archive
|
||||||
═══════════════════════════════════════════════════════ */
|
═══════════════════════════════════════════════════════ */
|
||||||
|
|
||||||
/* ─── Stats Bar ──────────────────────────────────────── */
|
/* ─── Stats Bar ──────────────────────────────────────── */
|
||||||
|
|
@ -879,4 +879,208 @@
|
||||||
.prop-grid {
|
.prop-grid {
|
||||||
grid-template-columns: repeat(3, 1fr);
|
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-desc">The largest classified index of free resources on the internet — catalogued by type.</div>
|
||||||
<div class="section-card-arrow">ENTER →</div>
|
<div class="section-card-arrow">ENTER →</div>
|
||||||
</a>
|
</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-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-desc">Recovered publications and documents preserved in digital format. Browse on-site.</div>
|
||||||
<div class="section-card-arrow">ENTER →</div>
|
<div class="section-card-arrow">ENTER →</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,13 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<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.">
|
<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 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 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/style.css">
|
||||||
<link rel="stylesheet" href="/css/section.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>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
@ -36,18 +36,18 @@
|
||||||
<span class="separator">/</span>
|
<span class="separator">/</span>
|
||||||
<a href="/depot">DEPOT</a>
|
<a href="/depot">DEPOT</a>
|
||||||
<span class="separator">/</span>
|
<span class="separator">/</span>
|
||||||
<span class="current">PROPAGANDA</span>
|
<span class="current">UNREDACTED</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section class="section-header" style="padding-top: calc(var(--nav-height) + 1.5rem);">
|
<section class="section-header" style="padding-top: calc(var(--nav-height) + 1.5rem);">
|
||||||
<div class="section-header-label">DEPOT // DECLASSIFIED ARCHIVE</div>
|
<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>
|
<p class="section-header-sub">> Declassified documents, intelligence briefings, and government files. Browse the archive.</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="subpage-content" style="max-width: clamp(1200px, 90vw, 2400px); margin: 0 auto; padding: 0 2rem 3rem;">
|
<section class="subpage-content" style="max-width: clamp(1200px, 90vw, 2400px); margin: 0 auto; padding: 0 2rem 3rem;">
|
||||||
<div id="propagandaRoot">
|
<div id="unredactedRoot">
|
||||||
<div class="prop-loading">INITIALISING PROPAGANDA ARCHIVE...</div>
|
<div class="prop-loading">INITIALISING UNREDACTED ARCHIVE...</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
@ -66,6 +66,6 @@
|
||||||
<script src="/js/wallet-connect.js"></script>
|
<script src="/js/wallet-connect.js"></script>
|
||||||
<script src="/js/nav.js"></script>
|
<script src="/js/nav.js"></script>
|
||||||
<script src="/js/clock.js"></script>
|
<script src="/js/clock.js"></script>
|
||||||
<script src="/js/propaganda.js"></script>
|
<script src="/js/unredacted.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
93
js/nav.js
93
js/nav.js
|
|
@ -87,6 +87,8 @@
|
||||||
initMobileDropdowns();
|
initMobileDropdowns();
|
||||||
|
|
||||||
// Inject shared wrapper for SOL price + wallet, then inject both
|
// Inject shared wrapper for SOL price + wallet, then inject both
|
||||||
|
// Start UNREDACTED block-reveal animation
|
||||||
|
initUnredactedAnimation();
|
||||||
injectSolWalletGroup();
|
injectSolWalletGroup();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('Nav load failed, keeping existing:', err);
|
console.warn('Nav load failed, keeping existing:', err);
|
||||||
|
|
@ -449,6 +451,97 @@
|
||||||
if (dd) dd.classList.add('hidden');
|
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
|
// Run on DOM ready
|
||||||
if (document.readyState === 'loading') {
|
if (document.readyState === 'loading') {
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,23 @@
|
||||||
/* ═══════════════════════════════════════════════════════
|
/* ═══════════════════════════════════════════════════════
|
||||||
PROPAGANDA — Declassified Document Archive
|
UNREDACTED — Declassified Document Archive
|
||||||
SPA Engine with hash routing & PDF.js viewer
|
SPA Engine with hash routing & PDF.js viewer
|
||||||
═══════════════════════════════════════════════════════ */
|
═══════════════════════════════════════════════════════ */
|
||||||
|
|
||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const ROOT = document.getElementById('propagandaRoot');
|
const ROOT = document.getElementById('unredactedRoot');
|
||||||
const PDF_BASE = '/propaganda/docs';
|
const PDF_BASE = '/unredacted/docs';
|
||||||
let DATA = null;
|
let DATA = null;
|
||||||
|
|
||||||
// ─── Data Loading ────────────────────────────────────
|
// ─── Data Loading ────────────────────────────────────
|
||||||
async function loadData() {
|
async function loadData() {
|
||||||
try {
|
try {
|
||||||
const r = await fetch('/api/propaganda');
|
const r = await fetch('/api/unredacted');
|
||||||
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
||||||
DATA = await r.json();
|
DATA = await r.json();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('PROPAGANDA: data load failed', e);
|
console.error('UNREDACTED: data load failed', e);
|
||||||
ROOT.innerHTML = `<div class="prop-empty">
|
ROOT.innerHTML = `<div class="prop-empty">
|
||||||
<div class="prop-empty-icon">⚠️</div>
|
<div class="prop-empty-icon">⚠️</div>
|
||||||
<div class="prop-empty-title">DATA FEED OFFLINE</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 class="prop-stat"><span class="prop-stat-label">DOCUMENTS</span><span class="prop-stat-value red">${stats.docs}</span></div>
|
||||||
</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-section-label">SELECT CATEGORY</div>';
|
||||||
html += '<div class="prop-grid">';
|
|
||||||
|
|
||||||
DATA.categories.forEach(cat => {
|
DATA.categories.forEach(cat => {
|
||||||
const nCountries = countCountries(cat);
|
const nCountries = countCountries(cat);
|
||||||
|
|
@ -144,7 +155,11 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
|
html += '</div>'; // close #urCategorySection
|
||||||
ROOT.innerHTML = html;
|
ROOT.innerHTML = html;
|
||||||
|
|
||||||
|
// ─── Bind search events ────────────────────────────────
|
||||||
|
initSearchListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── View: Category (Country Selector + Collections) ──
|
// ─── View: Category (Country Selector + Collections) ──
|
||||||
|
|
@ -156,7 +171,7 @@
|
||||||
html += `<div class="prop-classification">DECLASSIFIED // ${esc(cat.name)} // ACCESS GRANTED</div>`;
|
html += `<div class="prop-classification">DECLASSIFIED // ${esc(cat.name)} // ACCESS GRANTED</div>`;
|
||||||
|
|
||||||
html += breadcrumb([
|
html += breadcrumb([
|
||||||
{ label: 'PROPAGANDA', hash: '' },
|
{ label: 'UNREDACTED', hash: '' },
|
||||||
{ label: cat.name }
|
{ label: cat.name }
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
@ -251,7 +266,7 @@
|
||||||
html += `<div class="prop-classification">DECLASSIFIED // ${esc(col.name).toUpperCase()} // ACCESS GRANTED</div>`;
|
html += `<div class="prop-classification">DECLASSIFIED // ${esc(col.name).toUpperCase()} // ACCESS GRANTED</div>`;
|
||||||
|
|
||||||
html += breadcrumb([
|
html += breadcrumb([
|
||||||
{ label: 'PROPAGANDA', hash: '' },
|
{ label: 'UNREDACTED', hash: '' },
|
||||||
{ label: cat.name, hash: `category/${catId}` },
|
{ label: cat.name, hash: `category/${catId}` },
|
||||||
{ label: `${country.flag || ''} ${country.name}`, hash: `country/${catId}/${countryCode}` },
|
{ label: `${country.flag || ''} ${country.name}`, hash: `country/${catId}/${countryCode}` },
|
||||||
{ label: col.name }
|
{ label: col.name }
|
||||||
|
|
@ -309,7 +324,7 @@
|
||||||
html += `<div class="prop-classification">DECLASSIFIED // ${esc(doc.title).toUpperCase()}</div>`;
|
html += `<div class="prop-classification">DECLASSIFIED // ${esc(doc.title).toUpperCase()}</div>`;
|
||||||
|
|
||||||
html += breadcrumb([
|
html += breadcrumb([
|
||||||
{ label: 'PROPAGANDA', hash: '' },
|
{ label: 'UNREDACTED', hash: '' },
|
||||||
{ label: cat.name, hash: `category/${catId}` },
|
{ label: cat.name, hash: `category/${catId}` },
|
||||||
{ label: `${country.flag || ''} ${country.name}`, hash: `country/${catId}/${countryCode}` },
|
{ label: `${country.flag || ''} ${country.name}`, hash: `country/${catId}/${countryCode}` },
|
||||||
{ label: col.name, hash: `collection/${catId}/${countryCode}/${colId}` },
|
{ label: col.name, hash: `collection/${catId}/${countryCode}/${colId}` },
|
||||||
|
|
@ -582,7 +597,7 @@
|
||||||
});
|
});
|
||||||
pdfAllText.push({ pageNum: i, text: fullText, items: tc.items, itemOffsets: itemOffsets });
|
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 ────────────────────────────
|
// ─── 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 ────────────────────────────────
|
// ─── Not Found / Empty ────────────────────────────────
|
||||||
function renderNotFound(msg) {
|
function renderNotFound(msg) {
|
||||||
ROOT.innerHTML = `<div class="prop-empty">
|
ROOT.innerHTML = `<div class="prop-empty">
|
||||||
|
|
@ -840,7 +1006,7 @@
|
||||||
|
|
||||||
// ─── Init ─────────────────────────────────────────────
|
// ─── Init ─────────────────────────────────────────────
|
||||||
async function 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();
|
await loadData();
|
||||||
if (DATA) {
|
if (DATA) {
|
||||||
window.addEventListener('hashchange', route);
|
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