parent
abf499c9cf
commit
cc39bf2535
Binary file not shown.
14
package.json
14
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "interlanguage-checker",
|
||||
"version": "1.13.6",
|
||||
"version": "1.13.7",
|
||||
"description": "Check the consistency of MediaWiki interlanguage links in a simple overview.",
|
||||
"author": "Florine W. Dekker",
|
||||
"browser": "dist/bundle.js",
|
||||
|
@ -16,17 +16,17 @@
|
|||
"deploy": "grunt deploy"
|
||||
},
|
||||
"devDependencies": {
|
||||
"grunt": "^1.4.1",
|
||||
"grunt": "^1.5.3",
|
||||
"grunt-cli": "^1.4.3",
|
||||
"grunt-contrib-clean": "^2.0.0",
|
||||
"grunt-contrib-clean": "^2.0.1",
|
||||
"grunt-contrib-copy": "^1.0.0",
|
||||
"grunt-contrib-watch": "^1.1.0",
|
||||
"grunt-focus": "^1.0.0",
|
||||
"grunt-text-replace": "^0.4.0",
|
||||
"grunt-webpack": "^5.0.0",
|
||||
"ts-loader": "^9.2.6",
|
||||
"typescript": "^4.5.5",
|
||||
"webpack": "^5.69.1",
|
||||
"webpack-cli": "^4.9.2"
|
||||
"ts-loader": "^9.4.1",
|
||||
"typescript": "^4.9.3",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-cli": "^5.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,12 +20,12 @@ summary {
|
|||
/***
|
||||
* Table
|
||||
**/
|
||||
#networkTableForm {
|
||||
#network-table-form {
|
||||
/* Center table */
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#networkTable {
|
||||
#network-table {
|
||||
/* Center table */
|
||||
margin: 0 auto;
|
||||
|
||||
|
@ -36,27 +36,32 @@ summary {
|
|||
|
||||
|
||||
/* Text alignment */
|
||||
#networkTable th.sourceLabel, #networkTable td.sourceLabel {
|
||||
#network-table th.sourceLabel,
|
||||
#network-table td.sourceLabel {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#networkTable th:not(.sourceLabel), #networkTable td:not(.sourceLabel) {
|
||||
#network-table th:not(.sourceLabel),
|
||||
#network-table td:not(.sourceLabel) {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
/* Borders */
|
||||
#networkTable th, #networkTable td {
|
||||
#network-table th,
|
||||
#network-table td {
|
||||
border-right: 1px solid var(--table-border-color);
|
||||
border-bottom: 1px solid var(--table-border-color);
|
||||
}
|
||||
|
||||
#networkTable tr:last-child {
|
||||
#network-table tr:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
#networkTable th:first-child, #networkTable td:first-child,
|
||||
#networkTable th:last-child, #networkTable td:last-child {
|
||||
#network-table th:first-child,
|
||||
#network-table td:first-child,
|
||||
#network-table th:last-child,
|
||||
#network-table td:last-child {
|
||||
/* Undo Milligram padding because it looks bad with column borders */
|
||||
padding-left: 1.5rem;
|
||||
padding-right: 1.5rem;
|
||||
|
@ -64,26 +69,31 @@ summary {
|
|||
|
||||
|
||||
/* Table colors */
|
||||
#networkTable tbody tr:nth-child(odd) {
|
||||
#network-table tbody tr:nth-child(odd) {
|
||||
background-color: var(--table-row-color);
|
||||
}
|
||||
|
||||
#networkTable th a i {
|
||||
#network-table th a i {
|
||||
font-size: 0.9em;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
#networkTable a {
|
||||
#network-table a {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#network-table a::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
/* Shared colors */
|
||||
.redLink a {
|
||||
.red-link a {
|
||||
color: var(--fandom-redlink);
|
||||
}
|
||||
|
||||
span.success, i.success {
|
||||
span.success,
|
||||
i.success {
|
||||
color: var(--success-color);
|
||||
}
|
||||
|
||||
|
@ -92,7 +102,8 @@ div.success {
|
|||
background-color: var(--success-bg-color);
|
||||
}
|
||||
|
||||
span.error, i.error {
|
||||
span.error,
|
||||
i.error {
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
||||
|
@ -101,7 +112,8 @@ div.error {
|
|||
background-color: var(--error-bg-color);
|
||||
}
|
||||
|
||||
span.warning, i.warning {
|
||||
span.warning,
|
||||
i.warning {
|
||||
color: var(--warning-color);
|
||||
}
|
||||
|
||||
|
@ -110,7 +122,8 @@ div.warning {
|
|||
background-color: var(--warning-bg-color);
|
||||
}
|
||||
|
||||
span.info, i.info {
|
||||
span.info,
|
||||
i.info {
|
||||
color: var(--info-color);
|
||||
}
|
||||
|
||||
|
@ -123,20 +136,23 @@ div.info {
|
|||
/***
|
||||
* Messages, errors, etc.
|
||||
**/
|
||||
#errors, #messages {
|
||||
#errors,
|
||||
#messages {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.errorOuter, .messageOuter {
|
||||
.error-outer,
|
||||
.message-outer {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.errorInner {
|
||||
.error-inner {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.errorInner, .messageInner {
|
||||
.error-inner,
|
||||
.message-inner {
|
||||
padding: 1em;
|
||||
|
||||
border-width: 1px;
|
||||
|
|
|
@ -8,18 +8,25 @@
|
|||
<meta name="description" content="Check the consistency of MediaWiki interlanguage links in a simple overview." />
|
||||
<meta name="theme-color" content="#0033cc" />
|
||||
|
||||
<meta name="fwd:auto:show-main" />
|
||||
<meta name="fwd:nav:target" content="#nav" />
|
||||
<meta name="fwd:nav:highlight-path" content="/Tools/Interlanguage Checker/" />
|
||||
<meta name="fwd:footer:target" content="#footer" />
|
||||
<meta name="fwd:footer:vcs-url" content="https://git.fwdekker.com/tools/interlanguage-checker/" />
|
||||
<meta name="fwd:footer:version" content="v%%VERSION_NUMBER%%" />
|
||||
<meta name="fwd:validation:load-forms" />
|
||||
|
||||
<title>Interlanguage Checker | FWDekker</title>
|
||||
|
||||
<link rel="stylesheet" href="https://static.fwdekker.com/fonts/roboto/roboto.css" />
|
||||
<link rel="stylesheet" href="https://static.fwdekker.com/fonts/fork-awesome/1.x.x/fork-awesome.css" />
|
||||
<link rel="stylesheet" href="https://static.fwdekker.com/lib/template/2.x.x/template.css" />
|
||||
<link rel="stylesheet" href="https://static.fwdekker.com/lib/template/3.x.x/template.css?v=%%VERSION_NUMBER%%" />
|
||||
<!--suppress HtmlUnknownTarget -->
|
||||
<link rel="stylesheet" href="main.css?v=%%VERSION_NUMBER%%" />
|
||||
<script async src="https://stats.fwdekker.com/count.js"
|
||||
data-goatcounter="https://stats.fwdekker.com/count"></script>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<noscript class="fwd-js-notice">
|
||||
<img src="https://stats.fwdekker.com/count?p=/tools/interlanguage-checker/" alt="Counting pixel" />
|
||||
|
||||
<p>
|
||||
|
@ -28,92 +35,92 @@
|
|||
instructions on how to enable JavaScript in your web browser</a>.
|
||||
</p>
|
||||
</noscript>
|
||||
<nav id="nav"></nav>
|
||||
<main class="hidden">
|
||||
<div id="nav"></div>
|
||||
<div id="contents">
|
||||
<div id="header"></div>
|
||||
|
||||
<!-- About -->
|
||||
<div role="document">
|
||||
<section class="container">
|
||||
<div class="row">
|
||||
<div class="column">
|
||||
<details open id="about">
|
||||
<summary><b>About</b></summary>
|
||||
<span>
|
||||
<a href="https://community.fandom.com/wiki/Help:Interlanguage_link">⎋ Interlanguage links</a>
|
||||
allow wikis to tell users where to find translations of articles.
|
||||
Without the
|
||||
<a href="https://www.mediawiki.org/wiki/Extension:Interlanguage">⎋ interlanguage extension</a>,
|
||||
each translation is responsible for maintaining its own outgoing links.
|
||||
As the number of translations grows, the network of links becomes more <b>complex</b>, and the
|
||||
number of errors grows.<br />
|
||||
<br />
|
||||
The <b>Interlanguage Checker</b> traverses the network of interlanguage links starting from a
|
||||
given article and shows you that network in a table.
|
||||
If there are missing or incorrect links, you can quickly spot them and <b>fix</b>
|
||||
them.<br />
|
||||
<br />
|
||||
To use the tool, you should enter the link to the
|
||||
<a href="https://www.mediawiki.org/wiki/API:Main_page">⎋ API of the wiki</a> you want to
|
||||
check.
|
||||
For <b>Wikimedia</b> wikis, this is <code>https://<example.org>/w/api.php</code>.
|
||||
For <b>Fandom</b> wikis, this is <code>https://<wiki>.fandom.com/api.php</code>.<br />
|
||||
<br />
|
||||
If the application <b>refuses to connect</b> to the API and you are certain the URL is correct,
|
||||
make sure that you allow scripts to be executed from the API you have entered by checking the
|
||||
configuration of your <b>tracking blockers</b>.
|
||||
These <b>external scripts</b> are necessary to provide support to older wikis that rely on
|
||||
<a href="https://en.wikipedia.org/wiki/JSONP">⎋ JSONP requests</a> to interact with the
|
||||
API.<br />
|
||||
<br />
|
||||
If you need <b>help</b>, have a question, or found a bug, please
|
||||
<a href="https://git.fwdekker.com/FWDekker/interlanguage-checker/issues/new">⎋ open an issue</a>
|
||||
or <a href="https://fallout.fandom.com/wiki/User_talk:FDekker">⎋ leave a talk message</a>.
|
||||
</span>
|
||||
</details>
|
||||
<p></p>
|
||||
</div>
|
||||
</div>
|
||||
<header class="fwd-header">
|
||||
<hgroup>
|
||||
<h1><a href=".">Interlanguage Checker</a></h1>
|
||||
<h2>Check the consistency of MediaWiki interlanguage links in a simple overview.</h2>
|
||||
</hgroup>
|
||||
</header>
|
||||
|
||||
<details open id="about">
|
||||
<summary><b>About</b></summary>
|
||||
<p>
|
||||
<a href="https://community.fandom.com/wiki/Help:Interlanguage_link" target="_blank">
|
||||
Interlanguage links</a>
|
||||
allow wikis to tell users where to find translations of articles.
|
||||
Without the
|
||||
<a href="https://www.mediawiki.org/wiki/Extension:Interlanguage" target="_blank">
|
||||
interlanguage extension</a>,
|
||||
each translation is responsible for maintaining its own outgoing links.
|
||||
As the number of translations grows, the network of links becomes more <b>complex</b>, and the
|
||||
number of errors grows.
|
||||
</p>
|
||||
<p>
|
||||
The <b>Interlanguage Checker</b> traverses the network of interlanguage links starting from a
|
||||
given article and shows you that network in a table.
|
||||
If there are missing or incorrect links, you can quickly spot them and <b>fix</b> them.
|
||||
</p>
|
||||
<p>
|
||||
To use the tool, you should enter the link to the
|
||||
<a href="https://www.mediawiki.org/wiki/API:Main_page" target="_blank">API of the wiki</a> you want
|
||||
to check.
|
||||
For <b>Wikimedia</b> wikis, this is <code>https://<example.org>/w/api.php</code>.
|
||||
For <b>Fandom</b> wikis, this is <code>https://<wiki>.fandom.com/api.php</code>.
|
||||
</p>
|
||||
<p>
|
||||
If the application <b>refuses to connect</b> to the API and you are certain the URL is correct,
|
||||
make sure that you allow scripts to be executed from the API you have entered by checking the
|
||||
configuration of your <b>tracking blockers</b>.
|
||||
These <b>external scripts</b> are necessary to provide support to older wikis that rely on
|
||||
<a href="https://en.wikipedia.org/wiki/JSONP" target="_blank">JSONP requests</a> to interact with
|
||||
the API.
|
||||
</p>
|
||||
<p>
|
||||
If you need <b>help</b>, have a question, or found a bug, please
|
||||
<a href="https://git.fwdekker.com/FWDekker/interlanguage-checker/issues/new" target="_blank">
|
||||
open an issue</a>
|
||||
or
|
||||
<a href="https://fallout.fandom.com/wiki/User_talk:FDekker" target="_blank">
|
||||
leave a talk message</a>.
|
||||
</p>
|
||||
</details>
|
||||
|
||||
<form id="inputs">
|
||||
<label for="url">API</label>
|
||||
<input id="url" type="url" placeholder="https://fallout.fandom.com/api.php" autocomplete="url"
|
||||
autofocus />
|
||||
<small id="url-hint" data-hint-for="url" data-hint="The URL to the wiki's api.php."></small>
|
||||
|
||||
<label for="article">Article</label>
|
||||
<input id="article" type="text" placeholder="Master" />
|
||||
<small id="article-hint" data-hint-for="article" data-hint="The title of the article to check."></small>
|
||||
|
||||
<button id="submit">Check</button>
|
||||
</form>
|
||||
|
||||
<hr />
|
||||
<article class="status-card hidden" data-status-for="inputs">
|
||||
<output></output>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<!-- Input -->
|
||||
<section class="container">
|
||||
<div class="row">
|
||||
<div class="column">
|
||||
<form>
|
||||
<label for="url">
|
||||
API
|
||||
<i class="fa fa-question-circle-o" title="The URL to the wiki's api.php"></i>
|
||||
</label>
|
||||
<input id="url" type="url" autofocus />
|
||||
|
||||
<label for="article">
|
||||
Article
|
||||
<i class="fa fa-question-circle-o" title="The title of the article to check"></i>
|
||||
</label>
|
||||
<input id="article" type="text" />
|
||||
<br />
|
||||
<button id="check" type="button">Check</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Output -->
|
||||
<section> <!-- No `container` class, to allow use of whole width -->
|
||||
<hr />
|
||||
<div id="errors"></div>
|
||||
<div id="messages"></div>
|
||||
<hr />
|
||||
<form id="networkTableForm">
|
||||
<table id="networkTable"></table>
|
||||
<form id="network-table-form">
|
||||
<table id="network-table"></table>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="container">
|
||||
<footer id="footer"></footer>
|
||||
</section>
|
||||
</div>
|
||||
<div id="footer"></div>
|
||||
</main>
|
||||
|
||||
<script src="https://static.fwdekker.com/lib/template/2.x.x/template.js"></script>
|
||||
<script src="https://static.fwdekker.com/lib/template/3.x.x/template.js?v=%%VERSION_NUMBER%%"></script>
|
||||
<!--suppress HtmlUnknownTarget -->
|
||||
<script src="bundle.js?v=%%VERSION_NUMBER%%"></script>
|
||||
</body>
|
||||
|
|
|
@ -1,471 +0,0 @@
|
|||
// @ts-ignore
|
||||
const {stringToHtml} = window.fwdekker;
|
||||
import {InterlangNetwork, LinkVerdict, Page, PageVerdict} from "./MediaWiki";
|
||||
|
||||
|
||||
/**
|
||||
* An input that can be validated.
|
||||
*/
|
||||
export class ValidatableInput {
|
||||
/**
|
||||
* The validatable input.
|
||||
*/
|
||||
readonly input: HTMLInputElement;
|
||||
/**
|
||||
* Returns an empty string if the given input string is valid, and a string explaining why it is invalid otherwise.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private readonly isValid: (input: string) => string;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new validatable input.
|
||||
*
|
||||
* @param input the input that is validatable
|
||||
* @param isValid returns an empty string if the given input string is valid, and a string explaining why it is is
|
||||
* invalid otherwise
|
||||
*/
|
||||
constructor(input: HTMLInputElement, isValid: ((input: string) => string)) {
|
||||
this.input = input;
|
||||
this.isValid = isValid;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the value of the underlying input element.
|
||||
*
|
||||
* @return the value of the underlying input element
|
||||
*/
|
||||
getValue(): string {
|
||||
return this.input.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the underlying input element.
|
||||
*
|
||||
* @param value the value to set
|
||||
*/
|
||||
setValue(value: string): void {
|
||||
this.input.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the input.
|
||||
*
|
||||
* @return an empty string if the input string is valid, and a string explaining why it is is invalid otherwise
|
||||
*/
|
||||
validate(): string {
|
||||
const validity = this.isValid(this.input.value);
|
||||
|
||||
if (validity.length === 0) this.showSuccess();
|
||||
else this.showError();
|
||||
|
||||
return validity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the input as neither valid nor invalid.
|
||||
*/
|
||||
showBlank(): void {
|
||||
this.input.dataset["entered"] = "false";
|
||||
this.input.setCustomValidity("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the input as invalid and moves focus to it.
|
||||
*/
|
||||
showError(): void {
|
||||
this.input.dataset["entered"] = "true";
|
||||
this.input.setCustomValidity("Incorrect");
|
||||
this.input.focus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the input as valid.
|
||||
*/
|
||||
showSuccess(): void {
|
||||
this.input.dataset["entered"] = "true";
|
||||
this.input.setCustomValidity("");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The types of error that can be displayed by an `ErrorHandler`.
|
||||
*/
|
||||
export type ErrorLevel = "warning" | "error" | null;
|
||||
|
||||
/**
|
||||
* Interacts with the DOM to delegate errors to the user.
|
||||
*/
|
||||
export class ErrorHandler {
|
||||
/**
|
||||
* The outer div that wraps around all displayed errors.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private readonly outerDiv: HTMLDivElement;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new error handler, inserting relevant new elements into the DOM.
|
||||
*
|
||||
* @param parent the element to insert elements into
|
||||
*/
|
||||
constructor(parent: HTMLElement) {
|
||||
this.outerDiv = document.createElement("div");
|
||||
this.outerDiv.classList.add("errorOuter", "hidden");
|
||||
parent.appendChild(this.outerDiv);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handles the displaying of the given error.
|
||||
*
|
||||
* @param level the level of message to display, determines the style of the text
|
||||
* @param message the message to display
|
||||
* @return this `ErrorHandler`
|
||||
*/
|
||||
handle(level: ErrorLevel, message: string): ErrorHandler {
|
||||
this.outerDiv.classList.remove("hidden");
|
||||
|
||||
const errorInner = document.createElement("div");
|
||||
errorInner.classList.add("errorInner");
|
||||
if (level !== null) errorInner.classList.add(level);
|
||||
errorInner.innerText = message;
|
||||
this.outerDiv.appendChild(errorInner);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all errors from the DOM.
|
||||
*
|
||||
* @return this `ErrorHandler`
|
||||
*/
|
||||
clear(): ErrorHandler {
|
||||
this.outerDiv.classList.add("hidden");
|
||||
this.outerDiv.innerHTML = "";
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The types of message that can be displayed by a `MessageHandler`.
|
||||
*/
|
||||
export type MessageLevel = "complete" | "progress" | "warning" | "error" | "neutral" | null;
|
||||
|
||||
/**
|
||||
* Interacts with the DOM to delegate messages to the user.
|
||||
*/
|
||||
export class MessageHandler {
|
||||
/**
|
||||
* The outer div, wrapping around all of the handler's elements.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private readonly outerDiv: HTMLDivElement;
|
||||
/**
|
||||
* The inner div, wrapping around the icon, spacing, and current message.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private readonly innerDiv: HTMLDivElement;
|
||||
/**
|
||||
* A loading icon, optionally displayed before the current message.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private readonly loadingIcon: HTMLElement;
|
||||
/**
|
||||
* Spacing between the loading icon and the current message.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private readonly spacing: HTMLSpanElement;
|
||||
/**
|
||||
* The span containing the current message.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private readonly textSpan: HTMLSpanElement;
|
||||
|
||||
/**
|
||||
* The callback to be executed whenever a message is handler by this handler.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private callback: ((level: MessageLevel, message: string) => void) | undefined;
|
||||
/**
|
||||
* The currently displayed message level.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private currentLevel: MessageLevel | undefined;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new `MessageHandler`, inserting relevant new elements into the DOM to interact with.
|
||||
*
|
||||
* @param parent the element to insert elements into
|
||||
*/
|
||||
constructor(parent: HTMLElement) {
|
||||
this.outerDiv = document.createElement("div");
|
||||
this.outerDiv.classList.add("messageOuter", "hidden");
|
||||
parent.appendChild(this.outerDiv);
|
||||
|
||||
this.innerDiv = document.createElement("div");
|
||||
this.innerDiv.classList.add("messageInner");
|
||||
this.outerDiv.appendChild(this.innerDiv);
|
||||
|
||||
this.loadingIcon = document.createElement("i");
|
||||
this.loadingIcon.classList.add("fa", "fa-spinner", "fa-spin");
|
||||
this.innerDiv.appendChild(this.loadingIcon);
|
||||
|
||||
this.spacing = document.createElement("span");
|
||||
this.spacing.innerHTML = " ";
|
||||
this.innerDiv.appendChild(this.spacing);
|
||||
|
||||
this.textSpan = document.createElement("span");
|
||||
this.innerDiv.appendChild(this.textSpan);
|
||||
|
||||
this.callback = undefined;
|
||||
this.currentLevel = undefined;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handles the displaying of the given message.
|
||||
*
|
||||
* If no message is given, the current message and the loading icon are hidden. To display an empty message next to
|
||||
* the loading icon, give an empty string.
|
||||
*
|
||||
* @param level the level of message to display, or `null` if the entire message handler should be hidden
|
||||
* @param message the message to display
|
||||
* @return this `MessageHandler`
|
||||
*/
|
||||
handle(level: MessageLevel, message: string): MessageHandler {
|
||||
this.displayLevel(level);
|
||||
if (level === undefined) return this; // No need to handle the rest
|
||||
|
||||
if (this.callback !== undefined) this.callback(level, message);
|
||||
this.textSpan.innerHTML = message;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the message handler's contents and hides it.
|
||||
*
|
||||
* @return this `MessageHandler`
|
||||
*/
|
||||
clear(): MessageHandler {
|
||||
return this.handle(null, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the callback to be executed whenever a message is handler by this handler.
|
||||
*
|
||||
* @param callback the function to execute whenever a message is handled
|
||||
* @return this `MessageHandler`
|
||||
*/
|
||||
setCallback(callback: ((level: MessageLevel, message: string) => void)): MessageHandler {
|
||||
this.callback = callback;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Changes the appearance of the message handler to that of the given level.
|
||||
*
|
||||
* @param level the level to change appearance to
|
||||
* @return this `MessageHandler`
|
||||
* @private
|
||||
*/
|
||||
private displayLevel(level: MessageLevel): MessageHandler {
|
||||
if (level === this.currentLevel) return this;
|
||||
this.currentLevel = level;
|
||||
|
||||
this.outerDiv.classList.remove("hidden");
|
||||
this.innerDiv.classList.remove("success", "warning", "error");
|
||||
|
||||
switch (level) {
|
||||
case "complete":
|
||||
this.innerDiv.classList.add("success");
|
||||
this.toggleLoadingIcon(false);
|
||||
break;
|
||||
case "progress":
|
||||
this.toggleLoadingIcon(true);
|
||||
break;
|
||||
case "warning":
|
||||
this.innerDiv.classList.add("warning");
|
||||
this.toggleLoadingIcon(false);
|
||||
break;
|
||||
case "error":
|
||||
this.innerDiv.classList.add("error");
|
||||
this.toggleLoadingIcon(false);
|
||||
break;
|
||||
case "neutral":
|
||||
this.toggleLoadingIcon(false);
|
||||
break;
|
||||
default:
|
||||
this.outerDiv.classList.add("hidden");
|
||||
break;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns the loading icon on or off.
|
||||
*
|
||||
* @param state `true` if and only if the loading icon should be on
|
||||
* @return this `MessageHandler`
|
||||
* @private
|
||||
*/
|
||||
private toggleLoadingIcon(state: boolean): MessageHandler {
|
||||
if (state) {
|
||||
this.loadingIcon.classList.remove("hidden");
|
||||
this.spacing.classList.remove("hidden");
|
||||
} else {
|
||||
this.loadingIcon.classList.add("hidden");
|
||||
this.spacing.classList.add("hidden");
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A network of interlanguage links.
|
||||
*/
|
||||
export class InterlangTable {
|
||||
/**
|
||||
* Generates an icon element with the given title and additional classes.
|
||||
*
|
||||
* @param icon the name of the icon to display, or `null` if an empty span should be returned
|
||||
* @param title the title of the icon, used for the `title` attribute
|
||||
* @param classes the additional classes to apply to the icon
|
||||
* @return an icon element with the given title and additional classes
|
||||
* @private
|
||||
*/
|
||||
private static createIcon(icon: string | null, title: string, classes: string[]): string {
|
||||
if (icon === null) return `<span></span>`;
|
||||
|
||||
return `<i class="fa fa-${icon} ${(classes || []).join(" ")}" title="${title}"></i>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an appropriate label for the given page.
|
||||
*
|
||||
* The label contains a link to the page and a few buttons to help the user interact with that page. The label's
|
||||
* appearance and contents depend both on the properties of the page (e.g. whether it exists and whether it's a
|
||||
* redirect page) and on the other pages in this network (e.g. whether it's the only page in its language).
|
||||
*
|
||||
* @param pages a list of all pages
|
||||
* @param page the page to generate a label of
|
||||
* @return an appropriate label with icons for the given page
|
||||
* @private
|
||||
*/
|
||||
private generateLabel(pages: Page[], page: Page): string {
|
||||
const labelText = pages.some(it => it.link.lang === page.link.lang && !it.link.equals(page.link))
|
||||
? page.link.toString()
|
||||
: page.link.lang;
|
||||
|
||||
return "" +
|
||||
`<span class="${page.exists ? "" : "redLink"}">` +
|
||||
/**/`<a href="${page.url}" target="_blank" title="${page.link}">${labelText}</a>` +
|
||||
/**/`<span> </span>` +
|
||||
/**/`<a href="${page.url}?action=edit" target="_blank" title="Edit"><i class="fa fa-pencil"></i></a>` +
|
||||
/**/`<span> </span>` +
|
||||
/**/`<a title="Copy"><i class="fa fa-clipboard copyIcon" data-clipboarddata="${page.link}"></i></a>` +
|
||||
`</span>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the head of the table generated by `#toTable`.
|
||||
*
|
||||
* @param network the network to generate the head for
|
||||
* @return the head of the table generated by `#toTable`
|
||||
* @private
|
||||
*/
|
||||
private generateTableHead(network: InterlangNetwork): string {
|
||||
return "" +
|
||||
`<thead>` +
|
||||
/**/`<tr>` +
|
||||
/****/`<th rowspan="2"></th>` +
|
||||
/****/`<th class="sourceLabel" rowspan="2">Source</th>` +
|
||||
/****/`<th colspan="${network.pages.length}">Destination</th>` +
|
||||
/**/`</tr>` +
|
||||
/**/`<tr>${network.pages.map(page => `<th>${this.generateLabel(network.pages, page)}</th>`)}</tr>` +
|
||||
`</thead>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the body of the table generated by `#toTable`.
|
||||
*
|
||||
* @param network the network to generate the body for
|
||||
* @return the body of the table generated by `#toTable`
|
||||
* @private
|
||||
*/
|
||||
private generateTableBody(network: InterlangNetwork): string {
|
||||
const rows = network.pages.map(srcPage => {
|
||||
const {self: selfVerdict, links: linkVerdicts} = network.getPageVerdict(srcPage);
|
||||
|
||||
const icons = selfVerdict
|
||||
.map(state => {
|
||||
const props = PageVerdict.props[state];
|
||||
return InterlangTable.createIcon(props.icon, props.message, props.style);
|
||||
})
|
||||
.map(it => `${it}<span> </span>`);
|
||||
const label = this.generateLabel(network.pages, srcPage);
|
||||
const cells = network.pages.map(dstPage => {
|
||||
const linkState = linkVerdicts.get(dstPage.link)!;
|
||||
const props = LinkVerdict.props[linkState];
|
||||
return InterlangTable.createIcon(props.icon, props.message, props.style);
|
||||
});
|
||||
|
||||
return "" +
|
||||
`<tr>` +
|
||||
/**/`<th>${icons}</th>` +
|
||||
/**/`<th class="sourceLabel">${label}</th>` +
|
||||
/**/cells.map(it => `<td>${it}</td>`) +
|
||||
`</tr>`;
|
||||
});
|
||||
|
||||
return `<tbody>${rows}</tbody>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the the table describing the interlanguage network.
|
||||
*
|
||||
* @param id the ID to assign to the table element
|
||||
* @param network the network of pages to render
|
||||
* @return the generated table
|
||||
*/
|
||||
render(id: string, network: InterlangNetwork): HTMLElement {
|
||||
const table = stringToHtml(
|
||||
`<table id="${id}">` +
|
||||
/**/this.generateTableHead(network) +
|
||||
/**/this.generateTableBody(network) +
|
||||
`</table>`,
|
||||
"table"
|
||||
) as HTMLElement;
|
||||
|
||||
// Add event handlers
|
||||
table.querySelectorAll(".copyIcon").forEach(icon => {
|
||||
if (!(icon instanceof HTMLElement)) return;
|
||||
|
||||
icon.addEventListener("click", () => {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
navigator.clipboard.writeText(`[[${icon.dataset.clipboarddata}]]`);
|
||||
|
||||
icon.classList.replace("fa-clipboard", "fa-check");
|
||||
setTimeout(() => icon.classList.replace("fa-check", "fa-clipboard"), 1000);
|
||||
});
|
||||
});
|
||||
|
||||
return table;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
const {stringToHtml} = (window as any).fwdekker;
|
||||
import {InterlangNetwork, LinkVerdict, Page, PageVerdict} from "./MediaWiki";
|
||||
|
||||
|
||||
/**
|
||||
* A network of interlanguage links.
|
||||
*/
|
||||
export class InterlangTable {
|
||||
/**
|
||||
* Generates an icon element with the given title and additional classes.
|
||||
*
|
||||
* @param icon the name of the icon to display, or `null` if an empty span should be returned
|
||||
* @param title the title of the icon, used for the `title` attribute
|
||||
* @param classes the additional classes to apply to the icon
|
||||
* @return an icon element with the given title and additional classes
|
||||
* @private
|
||||
*/
|
||||
private static createIcon(icon: string | null, title: string, classes: string[]): string {
|
||||
if (icon === null) return `<span></span>`;
|
||||
|
||||
return `<i class="fa fa-${icon} ${(classes || []).join(" ")}" title="${title}"></i>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an appropriate label for the given page.
|
||||
*
|
||||
* The label contains a link to the page and a few buttons to help the user interact with that page. The label's
|
||||
* appearance and contents depend both on the properties of the page (e.g. whether it exists and whether it's a
|
||||
* redirect page) and on the other pages in this network (e.g. whether it's the only page in its language).
|
||||
*
|
||||
* @param pages a list of all pages
|
||||
* @param page the page to generate a label of
|
||||
* @return an appropriate label with icons for the given page
|
||||
* @private
|
||||
*/
|
||||
private generateLabel(pages: Page[], page: Page): string {
|
||||
const labelText = pages.some(it => it.link.lang === page.link.lang && !it.link.equals(page.link))
|
||||
? page.link.toString()
|
||||
: page.link.lang;
|
||||
|
||||
return "" +
|
||||
`<span class="${page.exists ? "" : "red-link"}">` +
|
||||
/**/`<a href="${page.url}" target="_blank" title="${page.link}">${labelText}</a>` +
|
||||
/**/`<span> </span>` +
|
||||
/**/`<a href="${page.url}?action=edit" target="_blank" title="Edit"><i class="fa fa-pencil"></i></a>` +
|
||||
/**/`<span> </span>` +
|
||||
/**/`<a title="Copy"><i class="fa fa-clipboard copy-icon" data-clipboarddata="${page.link}"></i></a>` +
|
||||
`</span>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the head of the table generated by `#toTable`.
|
||||
*
|
||||
* @param network the network to generate the head for
|
||||
* @return the head of the table generated by `#toTable`
|
||||
* @private
|
||||
*/
|
||||
private generateTableHead(network: InterlangNetwork): string {
|
||||
return "" +
|
||||
`<thead>` +
|
||||
/**/`<tr>` +
|
||||
/****/`<th rowspan="2"></th>` +
|
||||
/****/`<th class="source-label" rowspan="2">Source</th>` +
|
||||
/****/`<th colspan="${network.pages.length}">Destination</th>` +
|
||||
/**/`</tr>` +
|
||||
/**/`<tr>${network.pages.map(page => `<th>${this.generateLabel(network.pages, page)}</th>`)}</tr>` +
|
||||
`</thead>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the body of the table generated by `#toTable`.
|
||||
*
|
||||
* @param network the network to generate the body for
|
||||
* @return the body of the table generated by `#toTable`
|
||||
* @private
|
||||
*/
|
||||
private generateTableBody(network: InterlangNetwork): string {
|
||||
const rows = network.pages.map(srcPage => {
|
||||
const {self: selfVerdict, links: linkVerdicts} = network.getPageVerdict(srcPage);
|
||||
|
||||
const icons = selfVerdict
|
||||
.map(state => {
|
||||
const props = PageVerdict.props[state];
|
||||
return InterlangTable.createIcon(props.icon, props.message, props.style);
|
||||
})
|
||||
.map(it => `${it}<span> </span>`);
|
||||
const label = this.generateLabel(network.pages, srcPage);
|
||||
const cells = network.pages.map(dstPage => {
|
||||
const linkState = linkVerdicts.get(dstPage.link)!;
|
||||
const props = LinkVerdict.props[linkState];
|
||||
return InterlangTable.createIcon(props.icon, props.message, props.style);
|
||||
});
|
||||
|
||||
return "" +
|
||||
`<tr>` +
|
||||
/**/`<th>${icons}</th>` +
|
||||
/**/`<th class="source-label">${label}</th>` +
|
||||
/**/cells.map(it => `<td>${it}</td>`) +
|
||||
`</tr>`;
|
||||
});
|
||||
|
||||
return `<tbody>${rows}</tbody>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the table describing the interlanguage network.
|
||||
*
|
||||
* @param id the ID to assign to the table element
|
||||
* @param network the network of pages to render
|
||||
* @return the generated table
|
||||
*/
|
||||
render(id: string, network: InterlangNetwork): HTMLElement {
|
||||
const table = stringToHtml(
|
||||
`<table id="${id}">` +
|
||||
/**/this.generateTableHead(network) +
|
||||
/**/this.generateTableBody(network) +
|
||||
`</table>`,
|
||||
"table"
|
||||
) as HTMLElement;
|
||||
|
||||
// Add event handlers
|
||||
table.querySelectorAll(".copy-icon").forEach(icon => {
|
||||
if (!(icon instanceof HTMLElement)) return;
|
||||
|
||||
icon.addEventListener("click", () => {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
navigator.clipboard.writeText(`[[${icon.dataset.clipboarddata}]]`);
|
||||
|
||||
icon.classList.replace("fa-clipboard", "fa-check");
|
||||
setTimeout(() => icon.classList.replace("fa-check", "fa-clipboard"), 1000);
|
||||
});
|
||||
});
|
||||
|
||||
return table;
|
||||
}
|
||||
}
|
|
@ -1,28 +1,16 @@
|
|||
// @ts-ignore
|
||||
const {$, doAfterLoad, footer, header, nav} = window.fwdekker;
|
||||
import {ErrorHandler, InterlangTable, MessageHandler, ValidatableInput} from "./DOM";
|
||||
const {$, doAfterLoad} = (window as any).fwdekker;
|
||||
const {
|
||||
clearFormValidity, showInputInvalid, showMessageBusy, showMessageError, showMessageInfo, showMessageType
|
||||
} = (window as any).fwdekker.validation;
|
||||
|
||||
import {InterlangTable} from "./InterlangTable";
|
||||
import {discoverNetwork, InterlangNetwork, MediaWiki, MediaWikiManager, NetworkVerdict} from "./MediaWiki";
|
||||
|
||||
|
||||
// Contains global functions for debugging
|
||||
// @ts-ignore
|
||||
window.ilc = {};
|
||||
(window as any).ilc = {};
|
||||
|
||||
|
||||
// Set up template
|
||||
doAfterLoad(() => {
|
||||
$("#nav").appendChild(nav("/Tools/Interlanguage Checker/"));
|
||||
$("#header").appendChild(header({
|
||||
title: "Interlanguage Checker",
|
||||
description: "Check the consistency of MediaWiki interlanguage links in a simple overview"
|
||||
}));
|
||||
$("#footer").appendChild(footer({
|
||||
vcsURL: "https://git.fwdekker.com/tools/interlanguage-checker/",
|
||||
version: "v%%VERSION_NUMBER%%"
|
||||
}));
|
||||
$("main").classList.remove("hidden");
|
||||
});
|
||||
|
||||
// Handle "About" toggle
|
||||
doAfterLoad(() => {
|
||||
const about = $("#about");
|
||||
|
@ -31,138 +19,109 @@ doAfterLoad(() => {
|
|||
about.addEventListener("toggle", () => localStorage.setItem(key, "" + !!about.open));
|
||||
|
||||
const storedState = localStorage.getItem(key);
|
||||
if (storedState === null) about.open = true;
|
||||
else about.open = storedState === "true";
|
||||
about.open = storedState === null || storedState === "true";
|
||||
});
|
||||
|
||||
// Handle input
|
||||
doAfterLoad(async () => {
|
||||
const urlInput = new ValidatableInput($("#url"), (value) => {
|
||||
if (value.trim() === "")
|
||||
return "API URL must not be empty.";
|
||||
|
||||
try {
|
||||
new URL(value); // Throws exception if invalid
|
||||
return "";
|
||||
} catch (error) {
|
||||
try {
|
||||
// noinspection HttpUrlsUsage
|
||||
const url = `http://${value}`;
|
||||
new URL(url); // Throws exception if invalid
|
||||
$("#url").value = url;
|
||||
return "";
|
||||
} catch {
|
||||
if (!(error instanceof Error))
|
||||
throw Error(`Error while processing connection error:\n${error}`);
|
||||
|
||||
return error.message;
|
||||
}
|
||||
}
|
||||
});
|
||||
const articleInput = new ValidatableInput($("#article"), (value) => {
|
||||
return value.trim() === "" ? "Article must not be empty" : "";
|
||||
});
|
||||
const checkButton = $("#check");
|
||||
|
||||
const errorHandler = new ErrorHandler($("#errors"));
|
||||
const messageHandler = new MessageHandler($("#messages"))
|
||||
.setCallback((level, message) => {
|
||||
if (level === "error") console.error(message);
|
||||
});
|
||||
|
||||
const form = $("#inputs");
|
||||
const urlInput = $("#url");
|
||||
const articleInput = $("#article");
|
||||
|
||||
// Form submission
|
||||
let previousUrl: string | undefined;
|
||||
let mwm: MediaWikiManager | undefined;
|
||||
|
||||
const submit = async () => {
|
||||
localStorage.setItem("/tools/interlanguage-checker//api-url", urlInput.getValue());
|
||||
localStorage.setItem("/tools/interlanguage-checker//api-url", urlInput.value);
|
||||
|
||||
// Clean up
|
||||
urlInput.showBlank();
|
||||
articleInput.showBlank();
|
||||
errorHandler.clear();
|
||||
messageHandler.clear();
|
||||
|
||||
const oldTable = $("#networkTable");
|
||||
if (oldTable !== null)
|
||||
oldTable.parentNode.removeChild(oldTable);
|
||||
clearFormValidity(form);
|
||||
$("#network-table")?.remove();
|
||||
|
||||
// Validate
|
||||
const urlValidity = urlInput.validate();
|
||||
if (urlValidity !== "") return messageHandler.handle("error", urlValidity);
|
||||
const urlValue = urlInput.value;
|
||||
const articleValue = articleInput.value;
|
||||
|
||||
const articleValidity = articleInput.validate();
|
||||
if (articleValidity !== "") return messageHandler.handle("error", articleValidity);
|
||||
if (urlValue.trim() === "")
|
||||
return showInputInvalid(urlInput, "Enter the API URL.");
|
||||
|
||||
try {
|
||||
new URL(urlValue); // Throws exception if invalid
|
||||
} catch (error) {
|
||||
try {
|
||||
const url = `http://${urlValue}`;
|
||||
new URL(url); // Throws exception if invalid
|
||||
$("#url").value = url;
|
||||
return "";
|
||||
} catch {
|
||||
return showInputInvalid(urlInput, (error as Error).message);
|
||||
}
|
||||
}
|
||||
|
||||
if (articleValue.trim() === "")
|
||||
return showInputInvalid(articleInput, "Enter the name of the article to check.");
|
||||
|
||||
// Initialize
|
||||
if (urlInput.getValue() !== previousUrl) {
|
||||
messageHandler.handle("progress", `Initializing <code>${urlInput.getValue()}</code>`);
|
||||
if (urlValue !== previousUrl) {
|
||||
showMessageBusy(form, `Initializing <code>${urlValue}</code>`);
|
||||
|
||||
try {
|
||||
const mw = await new MediaWiki(urlInput.getValue()).init();
|
||||
const mw = await new MediaWiki(urlValue).init();
|
||||
mwm = await new MediaWikiManager().init(mw);
|
||||
} catch (error) {
|
||||
if (!(error instanceof Error))
|
||||
throw Error(`Error while processing initialization error:\n${error}`);
|
||||
|
||||
messageHandler.handle("error", error.message);
|
||||
return;
|
||||
return showMessageError(form, (error as Error).message);
|
||||
}
|
||||
|
||||
previousUrl = urlInput.getValue();
|
||||
previousUrl = urlValue;
|
||||
}
|
||||
|
||||
// Discover
|
||||
discoverNetwork(
|
||||
mwm!,
|
||||
articleInput.getValue(),
|
||||
(level, message) => errorHandler.handle(level, message),
|
||||
it => messageHandler.handle("progress", it)
|
||||
articleInput.value,
|
||||
(type, message) => showMessageType(form, message, type),
|
||||
it => showMessageType(form, it, "busy")
|
||||
)
|
||||
.then(it => new InterlangNetwork(it.pages, it.redirects))
|
||||
.then(network => {
|
||||
messageHandler.handle("progress", "Creating table");
|
||||
showMessageType(form, "Creating table", "busy");
|
||||
|
||||
const form = $("#networkTableForm");
|
||||
form.textContent = "";
|
||||
form.appendChild((new InterlangTable()).render("networkTable", network));
|
||||
const tableForm = $("#network-table-form");
|
||||
tableForm.textContent = "";
|
||||
tableForm.appendChild((new InterlangTable()).render("network-table", network));
|
||||
|
||||
const props = NetworkVerdict.props[network.getNetworkVerdict()];
|
||||
messageHandler.handle(props.style, props.message);
|
||||
showMessageType(form, props.message, props.type);
|
||||
})
|
||||
.catch(error => messageHandler.handle("error", error));
|
||||
.catch(error => showMessageError(form, error));
|
||||
};
|
||||
|
||||
urlInput.input.addEventListener("keypress", (event) => {
|
||||
if (event.key.toLowerCase() === "enter") submit();
|
||||
form.addEventListener("submit", (event: SubmitEvent) => {
|
||||
event.preventDefault();
|
||||
submit();
|
||||
});
|
||||
articleInput.input.addEventListener("keypress", (event) => {
|
||||
if (event.key.toLowerCase() === "enter") submit();
|
||||
});
|
||||
checkButton.addEventListener("click", () => submit());
|
||||
|
||||
urlInput.input.focus() // Default focus
|
||||
urlInput.focus(); // Default focus
|
||||
|
||||
// Read inputs from cookies
|
||||
const apiUrl = localStorage.getItem("/tools/interlanguage-checker//api-url");
|
||||
if (apiUrl !== null && apiUrl.trim() !== "") {
|
||||
urlInput.setValue(apiUrl);
|
||||
articleInput.input.focus();
|
||||
urlInput.value = apiUrl;
|
||||
articleInput.focus();
|
||||
}
|
||||
|
||||
// Read inputs from URL
|
||||
const currentParams = new URLSearchParams(window.location.search);
|
||||
if (currentParams.has("api")) {
|
||||
urlInput.setValue(currentParams.get("api")!);
|
||||
articleInput.input.focus();
|
||||
urlInput.value = currentParams.get("api")!;
|
||||
articleInput.focus();
|
||||
}
|
||||
if (currentParams.has("article")) articleInput.setValue(currentParams.get("article")!);
|
||||
if (currentParams.has("article")) articleInput.value = currentParams.get("article")!;
|
||||
if (currentParams.has("api") && currentParams.has("article")) await submit();
|
||||
|
||||
// Set global debug function
|
||||
// @ts-ignore
|
||||
window.ilc.getCurrentInputAsUrl = () =>
|
||||
(window as any).ilc.getCurrentInputAsUrl = () =>
|
||||
location.href.split("?")[0] +
|
||||
`?api=${encodeURI(urlInput.getValue())}` +
|
||||
`&article=${encodeURI(articleInput.getValue())}`;
|
||||
`?api=${encodeURI(urlInput.value)}` +
|
||||
`&article=${encodeURI(articleInput.value)}`;
|
||||
});
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import {MessageLevel} from "./DOM";
|
||||
import {couldNotConnectMessage, mergeMaps, mergeSets} from "./Shared";
|
||||
|
||||
|
||||
|
@ -621,7 +620,7 @@ export class MediaWikiManager {
|
|||
export const discoverNetwork = async function(
|
||||
mwm: MediaWikiManager,
|
||||
title: string,
|
||||
errorCb: (level: "error" | "warning" | null, message: string) => void,
|
||||
errorCb: (type: "error" | "warning" | null, message: string) => void,
|
||||
progressCb: (message: string) => void
|
||||
): Promise<{ pages: Page[], redirects: Redirect[] }> {
|
||||
const pages = [];
|
||||
|
@ -630,7 +629,7 @@ export const discoverNetwork = async function(
|
|||
const history: InterlangLink[] = [];
|
||||
const queue: InterlangLink[] = [new InterlangLink(mwm.baseLang, title)];
|
||||
while (queue.length > 0) {
|
||||
progressCb("Checking <code>" + queue[queue.length - 1] + "</code>");
|
||||
progressCb("Checking <code>" + queue[queue.length - 1] + "</code>.");
|
||||
|
||||
let next = queue.pop()!;
|
||||
if (history.some(it => it.equals(next)))
|
||||
|
@ -644,7 +643,10 @@ export const discoverNetwork = async function(
|
|||
if (history.length === 1)
|
||||
throw new Error(couldNotConnectMessage);
|
||||
else {
|
||||
errorCb("warning", `Could not connect to the wiki for language '${next.lang}'. Maybe the wiki no longer exists?`);
|
||||
errorCb(
|
||||
"warning",
|
||||
`Could not connect to the wiki for language '${next.lang}'. Maybe the wiki no longer exists?`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
@ -765,17 +767,19 @@ export namespace NetworkVerdict {
|
|||
export const props = {
|
||||
"perfect": {
|
||||
message: "A perfect network! 🙂",
|
||||
style: "complete" as MessageLevel
|
||||
type: "success"
|
||||
},
|
||||
"flawed": {
|
||||
message: "The network is complete but flawed 😕<br />" +
|
||||
message:
|
||||
"The network is complete but flawed 😕<br />" +
|
||||
"Hover over an icon in the left column for more information.",
|
||||
style: "warning" as MessageLevel
|
||||
type: "warning"
|
||||
},
|
||||
"broken": {
|
||||
message: "The network is broken 😞<br />" +
|
||||
message:
|
||||
"The network is broken 😞<br />" +
|
||||
"Hover over an icon in the left column for more information.",
|
||||
style: "warning" as MessageLevel
|
||||
type: "warning"
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
* The message that is displayed when the application fails to connect to the API.
|
||||
*/
|
||||
export const couldNotConnectMessage: string =
|
||||
"Could not to connect to API. Is the URL correct? Are you using a script blocker? " +
|
||||
"Could not connect to API. Is the URL correct? Are you using a script blocker? " +
|
||||
"See the <b>About</b> section for more information.";
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue