Separate Link from Page

This commit is contained in:
Florine W. Dekker 2020-04-11 19:14:46 +02:00
parent 4ea257780d
commit 8345c69c60
Signed by: FWDekker
GPG Key ID: B1B567AF58D6EE0F
2 changed files with 79 additions and 54 deletions

View File

@ -1,6 +1,7 @@
:root {
--error-color: red;
--success-color: green;
--fandom-redlink: #ba0000;
}
@ -67,6 +68,14 @@
font-weight: normal;
}
#networkTable a {
cursor: pointer;
}
.redLink a {
color: var(--fandom-redlink);
}
/***
* Loading icon
@ -120,7 +129,3 @@ input[data-entered=true]:invalid {
border-color: var(--error-color);
color: var(--error-color);
}
.redLink a {
color: #ba0000;
}

View File

@ -5,6 +5,9 @@ import {html} from "htm/preact";
/**
* A data class for combining a language and page title to identify a page.
*
* This is only an _identifier_ of a page, not the page itself. For information on the page such as the links it
* contains, whether it's a redirect, etc., see the `Page` class.
*
* @property lang {string} the language of the wiki this page is of
* @property title {string} the title of the page
*/
@ -105,6 +108,31 @@ export class InterwikiMap {
}
}
/**
* Describes a page, i.e. what you get if you follow an `InterlangLink`.
*
* @property url {URL} the full URL at which this page is located
* @property link {InterlangLink} the interlanguage link describing the location of the page
* @property linksTo {InterlangLink[]} the interlanguage links contained in this page
* @property exists {boolean} `true` if and only if this page exists
*/
export class Page {
/**
* Constructs a new `Page`.
*
* @param url {URL} the full URL at which this page is located
* @param link {InterlangLink} the interlanguage link describing the location of the page
* @param linksTo {InterlangLink[]} the interlanguage links contained in this page
* @param exists {boolean} `true` if and only if this page exists
*/
constructor(url, link, linksTo, exists) {
this.url = url;
this.link = link;
this.linksTo = linksTo;
this.exists = exists;
}
}
/**
* Interacts with the API in an asynchronous manner.
@ -276,66 +304,58 @@ export class MediaWikiManager {
}
/**
* Returns the article paths of all `MediaWiki`s known to this manager.
* Returns the URL to the given article.
*
* @returns {Object.<string, string>} the article paths of all languages known to this manager
* @param link {InterlangLink} the link to return the URL of
* @returns {URL} the URL to the given article
*/
getArticlePaths() {
return Object.keys(this.mws).reduce((paths, lang) => {
paths[lang] = this._iwMap.getUrl(lang).slice(0, -2);
return paths;
}, {});
getArticlePath(link) {
return new URL(this._iwMap.getUrl(link.lang).replace("$1", link.title));
}
}
/**
* A network of interlanguage links.
*
* @property pages {Page[]} the pages in the network}
*/
export class InterlangNetwork {
/**
* Constructs a new `InterlangNetwork`.
*
* @param mappings {Object.<string, InterlangLink[]>} a mapping from source page to all pages it links to
* @param missing {InterlangLink[]} list of articles that do not exist
* @param articlePaths = {Object.<string, string>} the article paths of each language
* @param pages {Page[]} the pages in the network
*/
constructor(mappings, missing, articlePaths) {
this._mappings = mappings;
this._missing = missing;
this._articlePaths = articlePaths;
constructor(pages) {
this.pages = pages.sort((a, b) => b.link.toString() - a.link.toString());
}
/**
* Returns an appropriate label for the given link.
* Returns an appropriate label for the given page.
*
* The label itself is an `a` `HTMLElement` linking to the editor for the given link.
* The text in the label is just the language of the link if the given link is the only link with that language
* in this network, or the entire link otherwise.
* 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 linkStr {string} the link to generate a label of
* @return {VNode} an appropriate label with icons for the given link
* @param page {Page} the page to generate a label of
* @return {VNode} an appropriate label with icons for the given page
*/
_generateLabel(linkStr) {
const link = InterlangLink.fromString(linkStr);
const url = this._articlePaths[link.lang] + link.title;
const isMissing = this._missing.some(it => it.equals(link));
const labelText =
Object.keys(this._mappings).filter(it => link.lang === InterlangLink.fromString(it).lang).length > 1
? linkStr
: link.lang;
_generateLabel(page) {
const labelText = this.pages.some(it => it.link.lang === page.link.lang && !it.link.equals(page.link))
? page.link.toString()
: page.link.lang;
const onClickCopy = (event) => {
navigator.clipboard.writeText(`[[${linkStr}]]`);
navigator.clipboard.writeText(`[[${page.link}]]`);
event.target.classList.replace("fa-clipboard", "fa-check");
setTimeout(() => event.target.classList.replace("fa-check", "fa-clipboard"), 1000);
};
return html`
<span class="${isMissing ? "redLink" : ""}">
<a href="${url}" target="_blank" title="${linkStr}">${labelText}</a>
<span class="${page.exists ? "" : "redLink"}">
<a href="${page.url}" target="_blank" title="${page.link}">${labelText}</a>
<span> </span>
<a href="${url}?action=edit" target="_blank" title="Edit"><i class="fa fa-pencil"></i></a>
<a href="${page.url}?action=edit" target="_blank" title="Edit"><i class="fa fa-pencil"></i></a>
<a title="Copy"><i class="fa fa-clipboard" onclick="${onClickCopy}"></i></a>
</span>
`;
@ -350,13 +370,13 @@ export class InterlangNetwork {
const topRow = html`
<tr>
<th class="sourceLabel" rowspan="2">Source</th>
<th colspan="${Object.keys(this._mappings).length}">Destination</th>
<th colspan="${this.pages.length}">Destination</th>
</tr>
`;
const bottomRow = html`
<tr>
${Object.keys(this._mappings).sort().map(key => html`<th>${this._generateLabel(key)}</th>`)}
</tr>
${this.pages.map(page => html`<th>${this._generateLabel(page)}</th>`)}
</tr>
`;
return html`<thead>${topRow}${bottomRow}</thead>`;
@ -368,16 +388,15 @@ export class InterlangNetwork {
* @return {HTMLElement} the body of the table generated by `#toTable`
*/
_generateTableBody() {
const rows = Object.keys(this._mappings).sort().map(srcKey => {
const label = html`<th class="sourceLabel">${this._generateLabel(srcKey)}</th>`;
const rows = this.pages.map(srcPage => {
const label = html`<th class="sourceLabel">${this._generateLabel(srcPage)}</th>`;
const cells = Object.keys(this._mappings).sort().map(dstKey => {
if (srcKey !== dstKey) {
const isPresent = this._mappings[srcKey].some(it => it.equals(InterlangLink.fromString(dstKey)));
return html`<td class="${isPresent ? "correct" : "incorrect"}">${isPresent ? "✓" : "✗"}</td>`;
} else {
const cells = this.pages.map(dstPage => {
if (srcPage.link.equals(dstPage.link))
return html`<td></td>`;
}
const isPresent = srcPage.linksTo.some(it => it.equals(dstPage.link));
return html`<td class="${isPresent ? "correct" : "incorrect"}">${isPresent ? "✓" : "✗"}</td>`;
});
return html`<tr>${label}${cells}</tr>`;
@ -406,8 +425,7 @@ export class InterlangNetwork {
* @returns {Promise<InterlangNetwork>} a network of interlanguage links
*/
static async discoverNetwork(mwm, title, progressCb) {
const mappings = {};
const missing = [];
const pages = [];
const history = [];
const queue = [new InterlangLink(mwm.baseLang, title)];
@ -423,19 +441,21 @@ export class InterlangNetwork {
.getLangLinks([next.title])
.then(langlinks => langlinks[next.title])
.then(langlinks => {
mappings[next.toString()] = [];
const url = mwm.getArticlePath(next);
if (langlinks === undefined) {
missing.push(next);
pages.push(new Page(url, next, [], false));
return;
}
if (langlinks.length === 0)
if (langlinks.length === 0) {
pages.push(new Page(url, next, [], true));
return;
}
mappings[next.toString()] = langlinks;
pages.push(new Page(url, next, langlinks, true));
queue.push(...langlinks);
});
}
return new InterlangNetwork(mappings, missing, mwm.getArticlePaths());
return new InterlangNetwork(pages);
}
}