195 lines
6.7 KiB
HTML
195 lines
6.7 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}{{ title }}{% endblock %}
|
|
|
|
{% block style %}
|
|
<link href="{{ url_for("static", filename="css/diff.css") }}" rel="stylesheet"/>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container">
|
|
<nav aria-label="breadcrumb" class="mb-3">
|
|
<ol class="breadcrumb">
|
|
<li class="breadcrumb-item"><a href="{{ url_for('index') }}">Home</a></li>
|
|
<li class="breadcrumb-item active">{{ title }}</li>
|
|
</ol>
|
|
</nav>
|
|
|
|
<div class="d-flex flex-wrap align-items-baseline gap-3 mb-1">
|
|
<h1 class="h4 mb-0">Find links to "{{ title }}"</h1>
|
|
<a href="https://en.wikipedia.org/wiki/{{ title }}" target="_blank" class="text-muted small">{{ title }} ↗</a>
|
|
{% if redirect_to %}
|
|
<span class="text-muted small">→ redirects to <a href="https://en.wikipedia.org/wiki/{{ redirect_to }}" target="_blank">{{ redirect_to }} ↗</a></span>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="d-flex gap-3 mb-3 text-muted small">
|
|
<span>{{ total }} mentions total</span>
|
|
<span>{{ with_link }} already linked{% if total > 0 %} ({{ "{:.0%}".format(with_link / total) }}){% endif %}</span>
|
|
{% if saves_this_session %}
|
|
<span class="text-success">{{ saves_this_session }} added this session</span>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div id="search-progress" class="my-4">
|
|
<div class="d-flex align-items-center gap-2 text-muted">
|
|
<div class="spinner-border spinner-border-sm" role="status">
|
|
<span class="visually-hidden">Searching…</span>
|
|
</div>
|
|
<span id="search-status">Searching…</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="result" hidden>
|
|
<div class="d-flex flex-wrap align-items-baseline gap-2 mb-3">
|
|
<span class="text-muted small">Adding link in</span>
|
|
<a id="result-hit-link" href="#" target="_blank" class="small"><span id="result-hit-title"></span> ↗</a>
|
|
</div>
|
|
<div class="mb-4">
|
|
<table class="diff" id="diff-table"></table>
|
|
</div>
|
|
<form method="POST" class="mb-4">
|
|
<input type="hidden" name="hit" id="hit-input">
|
|
<div class="d-flex gap-2">
|
|
<button type="submit" class="btn btn-success">Save edit</button>
|
|
<a id="skip-link" href="#" class="btn btn-outline-secondary">Skip</a>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<div id="all-done" hidden class="text-center mt-4">
|
|
<p class="text-muted mb-4">No more candidates found for this article.</p>
|
|
<a href="{{ url_for('index') }}" class="btn btn-primary">Search another article</a>
|
|
</div>
|
|
|
|
{% if hits %}
|
|
<details id="candidates-section" class="border rounded p-3 mt-2">
|
|
<summary class="text-muted small" style="cursor:pointer"><span id="candidates-count">{{ hits | length }}</span> candidates</summary>
|
|
<ol class="mt-3 mb-0 small" id="candidates-list">
|
|
{% for hit in hits %}
|
|
<li class="mb-1" data-title="{{ hit.title }}">
|
|
<a href="{{ url_for("article_page", url_title=url_title, title=hit.title) }}">{{ hit.title }}</a>
|
|
</li>
|
|
{% endfor %}
|
|
</ol>
|
|
</details>
|
|
{% endif %}
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block script %}
|
|
<script>
|
|
(function () {
|
|
const hits = {{ hits | map(attribute='title') | list | tojson }};
|
|
const linkTo = {{ title | tojson }};
|
|
const redirectTo = {{ redirect_to | tojson }};
|
|
const apiUrl = {{ url_for('api_valid_hit') | tojson }};
|
|
const pageUrl = new URL(window.location.href);
|
|
const cleanPageUrl = new URL(pageUrl);
|
|
cleanPageUrl.searchParams.delete('title');
|
|
cleanPageUrl.searchParams.delete('after');
|
|
cleanPageUrl.searchParams.delete('skip');
|
|
const storageKey = 'missinglink.skipped.' + linkTo;
|
|
const skippedTitles = [];
|
|
|
|
const elProgress = document.getElementById('search-progress');
|
|
const elStatus = document.getElementById('search-status');
|
|
const elResult = document.getElementById('result');
|
|
const elAllDone = document.getElementById('all-done');
|
|
const elList = document.getElementById('candidates-list');
|
|
|
|
function removeCandidate(title) {
|
|
if (!elList) return;
|
|
const li = elList.querySelector(`li[data-title="${CSS.escape(title)}"]`);
|
|
if (!li) return;
|
|
li.remove();
|
|
const elCount = document.getElementById('candidates-count');
|
|
if (elCount) elCount.textContent = elList.children.length;
|
|
}
|
|
|
|
function loadSkippedTitles() {
|
|
try {
|
|
const stored = JSON.parse(localStorage.getItem(storageKey) || '[]');
|
|
if (Array.isArray(stored)) {
|
|
for (const title of stored) rememberSkipped(title, false);
|
|
}
|
|
} catch (e) {
|
|
try { localStorage.removeItem(storageKey); } catch (e) {}
|
|
}
|
|
|
|
for (const title of {{ skipped_titles | tojson }}) rememberSkipped(title, false);
|
|
saveSkippedTitles();
|
|
}
|
|
|
|
function saveSkippedTitles() {
|
|
try {
|
|
localStorage.setItem(storageKey, JSON.stringify(skippedTitles));
|
|
} catch (e) {}
|
|
}
|
|
|
|
function rememberSkipped(title, save = true) {
|
|
if (!title || skippedTitles.includes(title)) return;
|
|
skippedTitles.push(title);
|
|
if (save) saveSkippedTitles();
|
|
}
|
|
|
|
async function search() {
|
|
loadSkippedTitles();
|
|
for (const hitTitle of skippedTitles) removeCandidate(hitTitle);
|
|
|
|
for (const hitTitle of hits) {
|
|
if (skippedTitles.includes(hitTitle)) continue;
|
|
|
|
elStatus.textContent = `Checking "${hitTitle}"…`;
|
|
|
|
let data;
|
|
try {
|
|
const params = new URLSearchParams({ link_to: linkTo, link_from: hitTitle });
|
|
if (redirectTo) params.append('redirect_to', redirectTo);
|
|
const resp = await fetch(apiUrl + '?' + params);
|
|
if (!resp.ok) continue;
|
|
data = await resp.json();
|
|
} catch (e) {
|
|
continue;
|
|
}
|
|
|
|
if (!data.valid) {
|
|
rememberSkipped(hitTitle);
|
|
removeCandidate(hitTitle);
|
|
continue;
|
|
}
|
|
|
|
elProgress.hidden = true;
|
|
|
|
document.getElementById('result-hit-title').textContent = hitTitle;
|
|
document.getElementById('result-hit-link').href =
|
|
'https://en.wikipedia.org/wiki/' + encodeURIComponent(hitTitle.replace(/ /g, '_'));
|
|
document.getElementById('diff-table').innerHTML = data.diff;
|
|
document.getElementById('hit-input').value = hitTitle;
|
|
|
|
const elSkipLink = document.getElementById('skip-link');
|
|
elSkipLink.href = cleanPageUrl.toString();
|
|
elSkipLink.onclick = function () {
|
|
rememberSkipped(hitTitle);
|
|
};
|
|
|
|
const currentUrl = new URL(pageUrl);
|
|
currentUrl.searchParams.delete('after');
|
|
currentUrl.searchParams.delete('skip');
|
|
currentUrl.searchParams.set('title', hitTitle);
|
|
history.replaceState(null, '', currentUrl.toString());
|
|
|
|
elResult.hidden = false;
|
|
return;
|
|
}
|
|
|
|
elProgress.hidden = true;
|
|
const elCandidates = document.getElementById('candidates-section');
|
|
if (elCandidates) elCandidates.hidden = true;
|
|
elAllDone.hidden = false;
|
|
}
|
|
|
|
search();
|
|
}());
|
|
</script>
|
|
{% endblock %}
|