Properly implement duplicate wiki detection

Fixes #18.
This commit is contained in:
Florine W. Dekker 2020-04-13 23:09:02 +02:00
parent a5f6d8625b
commit bd702ae657
Signed by: FWDekker
GPG Key ID: B1B567AF58D6EE0F
4 changed files with 94 additions and 46 deletions

View File

@ -1,6 +1,6 @@
{
"name": "interlanguage-checker",
"version": "1.4.3",
"version": "1.5.0",
"description": "Check the consistency of MediaWiki interlanguage links in a simple overview.",
"author": "Felix W. Dekker",
"browser": "bundle.js",

View File

@ -168,24 +168,6 @@ export class MessageHandler {
* @property pages {Page[]} the pages in the network
*/
export class InterlangTable extends Component {
/**
* Determines whether the given source links to the given destination, potentially through a redirect.
*
* @param source {Page} the source page of which to check the links
* @param destination {Page} the destination that could be linked to
* @param redirects {Redirect[]} the redirects to follow
* @returns {"present"|"absent"|"redirect"} the status of the link
*/
_checkIfLinksTo(source, destination, redirects) {
if (source.langLinks.some(it => it.equals(destination.link)))
return "present";
if (source.langLinks.some(link => redirects.some(it => it.equals(new Redirect(link, destination.link)))))
return "redirect";
return "absent";
}
/**
* Returns an appropriate label for the given page.
*
@ -222,21 +204,19 @@ export class InterlangTable extends Component {
/**
* Generates the head of the table generated by `#toTable`.
*
* @param pages {Page[]} a list of all pages
* @param network {InterlangNetwork} the network to generate the head for
* @return {VNode} the head of the table generated by `#toTable`
*/
_generateTableHead(pages) {
const uniquePages = pages.filter((page, i) => pages.findIndex(it => it.urlEquals(page)) === i);
_generateTableHead(network) {
const topRow = html`
<tr>
<th class="sourceLabel" rowspan="2">Source</th>
<th colspan="${uniquePages.length}">Destination</th>
<th colspan="${network.pages.length}">Destination</th>
</tr>
`;
const bottomRow = html`
<tr>
${uniquePages.map(page => html`<th>${this._generateLabel(pages, page)}</th>`)}
${network.pages.map(page => html`<th>${this._generateLabel(network.pages, page)}</th>`)}
</tr>
`;
@ -246,22 +226,15 @@ export class InterlangTable extends Component {
/**
* Generates the body of the table generated by `#toTable`.
*
* @param pages {Page[]} a list of all pages
* @param redirects {Redirect[]} the redirects in the network
* @param network {InterlangNetwork} the network to generate the body for
* @return {HTMLElement} the body of the table generated by `#toTable`
*/
_generateTableBody(pages, redirects) {
const uniquePages = pages.filter((page, i) => pages.findIndex(it => it.urlEquals(page)) === i);
const rows = uniquePages.map(srcPage => {
const label = html`<th class="sourceLabel">${this._generateLabel(pages, srcPage)}</th>`;
const cells = uniquePages.map(dstPage => {
_generateTableBody(network) {
const rows = network.pages.map(srcPage => {
const label = html`<th class="sourceLabel">${this._generateLabel(network.pages, srcPage)}</th>`;
const cells = network.pages.map(dstPage => {
let type, icon, title;
const status = pages.filter(it => it.urlEquals(dstPage))
.reduce((status, it) => {
const newStatus = this._checkIfLinksTo(srcPage, it, redirects);
return newStatus === "absent" ? status : newStatus;
}, "absent");
const status = network.checkIfLinksTo(srcPage, dstPage);
switch (status) {
case "present":
[type, icon, title] = ["correct", "check", "Present"];
@ -293,16 +266,14 @@ export class InterlangTable extends Component {
* Renders the the table describing the interlanguage network.
*
* @param props {Object} the element's rendering properties
* @param props.network {InterlangNetwork} the network of pages to render
* @return {VNode} the generated table
*/
render(props) {
const pages = props.pages.sort((a, b) => a.link.toString().localeCompare(b.link.toString()));
const redirects = props.redirects;
return html`
<table id="${props.id}">
${this._generateTableHead(pages)}
${this._generateTableBody(pages, redirects)}
${this._generateTableHead(props.network)}
${this._generateTableBody(props.network)}
</table>
`;
}

View File

@ -1,6 +1,6 @@
import {html, render} from "htm/preact";
import {InterlangTable, MessageHandler, ValidatableInput} from "./DOM";
import {discoverNetwork, MediaWiki, MediaWikiManager} from "./MediaWiki";
import {discoverNetwork, InterlangNetwork, MediaWiki, MediaWikiManager} from "./MediaWiki";
import {$, doAfterLoad} from "./Shared";
@ -71,12 +71,13 @@ doAfterLoad(async () => {
// Discover
discoverNetwork(mwm, pageInput.getValue(), progressHandler.handle.bind(progressHandler))
.then(({pages, redirects}) => {
.then(({pages, redirects}) => new InterlangNetwork(pages, redirects))
.then(network => {
progressHandler.handle("Creating table");
const form = $("#networkTableForm");
form.textContent = "";
render(html`<${InterlangTable} id="networkTable" pages=${pages} redirects=${redirects} />`, form);
render(html`<${InterlangTable} id="networkTable" network=${network} />`, form);
progressHandler.handle();
})

View File

@ -162,6 +162,82 @@ export class Page {
}
}
/**
* A network of pages linking to each other.
*
* @property pages {Page[]} the pages linking to each other, sorted alphabetically
* @properties redirects {Redirect[]} the redirects in the network
*/
export class InterlangNetwork {
/**
* Constructs a new `InterlangNetwork`.
*
* @param pages {Page[]} the pages linking to each other
* @param redirects {Redirect[]} the redirects in the network
*/
constructor(pages, redirects) {
this.pages = pages.sort((a, b) => a.link.toString().localeCompare(b.link.toString()));
this.redirects = redirects;
this._coerceWikis();
}
/**
* Changes the state of this network such that there are no duplicate wikis.
*
* In particular, if there are two interwikis with the same URL but a different name, then one name will be chosen
* and all instances of the other name will be removed from the wiki.
*
* @private
*/
_coerceWikis() {
const uniquePages = [];
const duplicatePages = [];
this.pages.forEach((page, i) => {
if (this.pages.findIndex(it => it.urlEquals(page)) === i)
uniquePages.push(page);
else
duplicatePages.push(page);
});
this.pages = uniquePages;
const renameLink = ((link, {from, to}) => {
if (link.lang === from)
return new InterlangLink(to, link.title);
return link;
});
duplicatePages
.map(page => ({
from: page.link.lang,
to: uniquePages.find(it => it.urlEquals(page)).link.lang
}))
.forEach(rename => {
this.redirects = this.redirects.map(redirect =>
new Redirect(renameLink(redirect.from, rename), renameLink(redirect.to, rename)));
this.pages.forEach(page => page.langLinks = page.langLinks.map(it => renameLink(it, rename)));
});
}
/**
* Determines whether the given source links to the given destination, potentially through a redirect.
*
* @param source {Page} the source page of which to check the links
* @param destination {Page} the destination that could be linked to
* @returns {"present"|"absent"|"redirect"} the status of the link
*/
checkIfLinksTo(source, destination) {
if (source.langLinks.some(it => it.equals(destination.link)))
return "present";
if (source.langLinks.some(link => this.redirects.some(it => it.equals(new Redirect(link, destination.link)))))
return "redirect";
return "absent";
}
}
/**
* Interacts with the API in an asynchronous manner.