Skip to content

Commit

Permalink
feat: 迁移AutoLink
Browse files Browse the repository at this point in the history
  • Loading branch information
ZoruaFox committed Feb 20, 2024
1 parent 4189e44 commit 7efc969
Show file tree
Hide file tree
Showing 6 changed files with 240 additions and 0 deletions.
14 changes: 14 additions & 0 deletions src/AutoLink/AutoLink.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {type TargetElements, getTargetElements} from './modules/util/getTargetElements';
import {IS_IN_TARGET_SPECIAL_PAGE} from './modules/constant';
import {getBody} from 'ext.gadget.Util';
import {processElement} from './modules/processElement';

void getBody().then(function autoLink($body: JQuery<HTMLBodyElement>): void {
if (!IS_IN_TARGET_SPECIAL_PAGE) {
return; // Disabled in the other special pages
}

const targetElements: TargetElements = getTargetElements($body);

processElement(targetElements);
});
9 changes: 9 additions & 0 deletions src/AutoLink/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* SPDX-License-Identifier: CC-BY-SA-4.0
* _addText: '{{Gadget Header|license=CC-BY-SA-4.0}}'
*
* @base {@link https://meta.wikimedia.org/wiki/MediaWiki:Gadget-autolink.js}
* @base {@link https://en.wikipedia.org/wiki/Wikipedia:WikiProject_User_scripts/Scripts/Autolink}
* @source {@link https://github.com/TopRealm/YsArxiv-Gadgets/tree/master/src/AutoLink}
* @license CC-BY-SA-4.0 {@link https://www.qiuwenbaike.cn/wiki/H:CC-BY-SA-4.0}
*/
6 changes: 6 additions & 0 deletions src/AutoLink/definition.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"enable": true,
"description": "<span id=\"Gadget-AutoLink\"></span> 自动为[[Help:编辑摘要|编辑摘要]]中的[[Help:模板|模板]]、[[Help:链接|链接]]语法添加链接",
"section": "browser",
"dependencies": ["ext.gadget.Util", "mediawiki.util"]
}
39 changes: 39 additions & 0 deletions src/AutoLink/modules/constant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
const WG_ACTION: MediaWikiConfigMapWgAction = mw.config.get('wgAction');
const WG_CANONICAL_NAMESPACE: string = mw.config.get('wgCanonicalNamespace');
const WG_CANONICAL_SPECIAL_PAGE_NAME: string = mw.config.get('wgCanonicalSpecialPageName').toString();

const IS_DIFF_ACTION: boolean = !!mw.util.getParamValue('diff');
// Special crono pages where this script is enabled
const IS_TARGET_SPECIAL_PAGE: boolean = [
'Contributions',
'Log',
'Newpages',
'Recentchanges',
'Recentchangeslinked',
'Watchlist',
].includes(WG_CANONICAL_SPECIAL_PAGE_NAME);
const IS_IN_TARGET_SPECIAL_PAGE: boolean = WG_CANONICAL_NAMESPACE === 'Special' && IS_TARGET_SPECIAL_PAGE;
const IS_WG_EDIT_OR_SUBMIT_ACTION: boolean = ['edit', 'submit'].includes(WG_ACTION);
const IS_WG_HISTORY_ACTION: boolean = WG_ACTION === 'history';

const REGEX_INTERNAL_URL: RegExp = /([^=])([\s"'])((?:\/?w\/index\.php\?|\/?wiki\/)[\w!#%&()+./:=?@\\~-]+)\2/g;
// External links (no wikicode)
const REGEX_IMPORT_SCRIPT: RegExp =
/([Ii]mport[Ss]cript(?:<span class="br0">)?\((?:<\/span><span class="st0">)?)('|")([^\n<>[\]{|}]+?)(\2(?:<\/span><span class="br0">)?\)(?:<\/span>)?)/g;
// ImportScript
const REGEX_TL: RegExp = /([^{]{{2}\s*[Tt]l\|)([^\n:<>[\]{|}]+)/g;
// For {{tl}}
const REGEX_URL: RegExp =
/((?:[^"[]|[^=]")(?:<span class="diffchange">)?)((?:https?|ftps?):\/\/[\w!#%&()+./:=?@\\~-]+?)(?=(?:<\/span>)?[\s"')\]|}])/g;

export {
IS_DIFF_ACTION,
IS_IN_TARGET_SPECIAL_PAGE,
IS_TARGET_SPECIAL_PAGE,
IS_WG_EDIT_OR_SUBMIT_ACTION,
IS_WG_HISTORY_ACTION,
REGEX_INTERNAL_URL,
REGEX_IMPORT_SCRIPT,
REGEX_TL,
REGEX_URL,
};
142 changes: 142 additions & 0 deletions src/AutoLink/modules/processElement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/**
* Autolink [[wikilinks]], [external links] and {{templates}}
*/
import {
IS_DIFF_ACTION,
IS_TARGET_SPECIAL_PAGE,
IS_WG_EDIT_OR_SUBMIT_ACTION,
IS_WG_HISTORY_ACTION,
REGEX_IMPORT_SCRIPT,
REGEX_INTERNAL_URL,
REGEX_TL,
REGEX_URL,
} from './constant';
import type {TargetElements} from './util/getTargetElements';

const processElement = ({color, targetElements}: TargetElements): void => {
let isActivateHTML: boolean = false;
/* Regex (default for diffs) */
// Regex for diffs
let regexURLinWikicodeWithoutLabel: RegExp =
/((?:[^[]|^)\[\s*(?:<\/span>)?\s*(?:<span class="diffchange">)?\s*)((?:https?|ftps?):\/\/[\w!#%&()+./:=?@\\~-]+)((?:<\/span>)?(?:<span class="diffchange">)?)([\w!#%&()+./:=?@\\~-]*)((?:<\/span>)?(?:<span class="diffchange">)?)([\w!#%&()+./:=?@\\~-]*)([^\n\]]*])/gm;
// External links (no wikicode)
let regexSubstinWikicodeWithoutLabel: string = `$1<a class="external autolink" style="color:${color}" href="$2$4$6">$2</a>$3<a class="external autolink" style="color:${color}" href="$2$4$6">$4</a>$5<a class="external autolink" style="color:${color}" href="$2$4$6">$6</a>$7`;
// External links in diff pages, wikicode without label
let regexURLinWikicodeWithLabel: RegExp = regexURLinWikicodeWithoutLabel;
// External links in diff pages, wikicode with label
let regexSubstinWikicodeWithLabel: string = regexSubstinWikicodeWithoutLabel;
// Other pages included in diff pages
let regexOtherPages: RegExp =
/((?:[^{]|^){{2}\s*(?:<\/span>)?\s*(?:<span class="diffchange">)?\s*(?:(?:[Ss][Uu][Bb][Ss][Tt]|[Mm][Ss][Gg]|[Mm][Ss][Gg][Nn][Ww])\s*:)?\s*(?:<\/span>)?\s*(?:<span class="diffchange">)?\s*)((?:[Ss]peciale?|[Qq](?:iuwen|[Ww])|[Ww][Pp]|[Tt]emplate|[Uu]ser)?\s*(?: ?[Tt]alk)?\s*:[^\n:<>[\]{|}]+)((?:<\/span>)?(?:<span class="diffchange">)?)([^\n:<>[\]{|}]*)((?:<\/span>)?(?:<span class="diffchange">)?)([^\n:<>[\]{|}]*)(\||}{2})/gm;
let regexSubstinOtherPages: string = `$1<a class="autolink" style="color:${color}" href="/wiki/$2$4$6">$2</a>$3<a class="autolink" style="color:${color}" href="/wiki/$2$4$6">$4</a>$5<a class="autolink" style="color:${color}" href="/wiki/$2$4$6">$6</a>$7`;
// Templates in diff pages
let regexTemplate: RegExp =
/((?:[^{]|^){{2}\s*(?:<\/span>)?\s*(?:<span class="diffchange">)?\s*(?:(?:[Ss][Uu][Bb][Ss][Tt]|[Mm][Ss][Gg]|[Mm][Ss][Gg][Nn][Ww])\s*:)?\s*(?:<\/span>)?\s*(?:<span class="diffchange">)?)([^\n:<>[\]{|}]+)((?:<\/span>)?(?:<span class="diffchange">)?)([^\n:<>[\]{|}]*)((?:<\/span>)?(?:<span class="diffchange">)?)([^\n:<>[\]{|}]*)(\||}{2}|:)/gm;
let regexSubstinTemplate: string = `$1<a class="autolink" style="color:${color}" href="/wiki/Template:$2$4$6">$2</a>$3<a class="autolink" style="color:${color}" href="/wiki/Template:$2$4$6">$4</a>$5<a class="autolink" style="color:${color}" href="/wiki/Template:$2$4$6">$6</a>$7`;
// Wikilinks in diff pages
let regexWikilink1: RegExp =
/(\[{2}\s*(?:<\/span>)?\s*(?:<span class="diffchange">)?\s*)([^\n<>[\]{|}]+)((?:<\/span>)?(?:<span class="diffchange">)?)([^\n<>[\]{|}]*)((?:<\/span>)?(?:<span class="diffchange">)?)([^\n<>[\]{|}]*)((?:[^\n\]]|][^\]])*]{2})/g;
let regexSubstinWikilink1: string = `$1<a class="autolink" style="color:${color}" href="/wiki/$2$4$6">$2</a>$3<a class="autolink" style="color:${color}" href="/wiki/$2$4$6">$4</a>$5<a class="autolink" style="color:${color}" href="/wiki/$2$4$6">$6</a>$7`;
let regexWikilink2: RegExp = regexWikilink1;
let regexSubstinWikilink2: string = regexSubstinWikilink1;
// Regex for comments or code sections
if (!IS_DIFF_ACTION) {
// Activate some HTML (inline) and wikicode for bold and italic
isActivateHTML = true;
// External links in comments or code sections, wikicode without label
regexURLinWikicodeWithoutLabel = /([^[]|^)\[\s*((?:https?|ftps?):\/\/[\w!#%&()+./:=?@\\~-]+)\s*]/gm;
regexSubstinWikicodeWithoutLabel = `$1<a class="external autolink" style="color:${color}" href="$2">$2</a>`;
// External links in comments or code sections, wikicode with label (the URL will not be visible)
regexURLinWikicodeWithLabel = /([^[]|^)\[\s*((?:https?|ftps?):\/\/[\w!#%&()+./:=?@\\~-]+)\s+([^\n]+?)\s*]/gm;
regexSubstinWikicodeWithLabel = `$1<a class="external autolink" style="color:${color}" href="$2">$3</a>`;
// Other pages included in comments or code sections
regexOtherPages =
/((?:[^{]|^){{2}\s*(?:(?:[Ss][Uu][Bb][Ss][Tt]|[Mm][Ss][Gg]|[Mm][Ss][Gg][Nn][Ww])\s*:)?\s*)((?:[Ss]pecial|[Qq](?:iuwen|Q)|[Ww][Pp]|[Tt]emplate|[Uu]ser)?\s*(?: ?[Tt]alk)?\s*:[^\n:<>[\]{|}]+)(\||}{2})/gm;
regexSubstinOtherPages = `$1<a class="autolink" style="color:${color}" href="/wiki/$2">$2</a>$3`;
// Templates in comments or code sections
regexTemplate =
/((?:[^{]|^){{2}\s*(?:(?:[Ss][Uu][Bb][Ss][Tt]|[Mm][Ss][Gg]|[Mm][Ss][Gg][Nn][Ww])\s*:)?\s*)([^\n:<>[\]{|}]+)(\||}{2}|:)/gm;
regexSubstinTemplate = `$1<a class="autolink" style="color:${color}" href="/wiki/Template:$2">$2</a>$3`;
// Wikilinks in code sections, with label
regexWikilink1 = /\[{2}\s*([^\n<>[\]{|}]+?)\s*\|\s*(.+?)\s*]{2}/g;
regexSubstinWikilink1 = `<a class="autolink" style="color:${color}" href="/wiki/$1">$2</a>`;
// Wikilinks in code sections, without label
regexWikilink2 = /\[{2}\s*([^\n<>[\]{|}]+?)\s*\|?\s*]{2}/g;
regexSubstinWikilink2 = `<a class="autolink" style="color:${color}" href="/wiki/$1">$1</a>`;
}

for (const element of targetElements) {
const {innerHTML: originHtml} = element;
let html: string = originHtml;

html = html.replace(/&lt;/g, '&shy;<&shy;');
html = html.replace(/&gt;/g, '&shy;>&shy;');
// &amp;lt; to &amp;shy;<&amp;shy; and &amp;gt; to &amp;shy;>&amp;shy; (&amp;shy; is a marker)
html = html.replace(/&amp;/g, '&');
// &amp;amp; to &
/* --- */
html = html.replace(REGEX_TL, `$1<a class="autolink" style="color:${color}" href="/wiki/Template:$2">$2</a>`);
// For {{tl}}: make his argument into link
html = html.replace(REGEX_URL, `$1<a class="external autolink" style="color:${color}" href="$2">$2</a>`);
// Parse inactive external links (no wikicode)
html = html.replace(regexURLinWikicodeWithoutLabel, regexSubstinWikicodeWithoutLabel);
// Make external links in wikicode without label into links
html = html.replace(regexURLinWikicodeWithLabel, regexSubstinWikicodeWithLabel);
// Make external links in wikicode with label into links
html = html.replace(regexOtherPages, regexSubstinOtherPages);
// Make other pages included code into links
html = html.replace(regexTemplate, regexSubstinTemplate);
html = html.replace(/href="\/wiki\/Template:#/g, 'href="/wiki/Help:');
// Make template code into links
html = html.replace(regexWikilink1, regexSubstinWikilink1);
html = html.replace(regexWikilink2, regexSubstinWikilink2);
// Make wikilink code into links
html = html.replace(
REGEX_INTERNAL_URL,
`$1$2<a class="external autolink" style="color:${color}" href="$3">$3</a>$2`
);
// Parse inactive external links (no wikicode)
html = html.replace(
REGEX_IMPORT_SCRIPT,
`$1$2<a class="autolink" style="color:${color}" href="/wiki/$3">$3</a>$4`
);
// Parse ImportScript
html = html.replace(/&shy;<&shy;/g, '&lt;');
html = html.replace(/&shy;>&shy;/g, '&gt;');
// &amp;shy;<&amp;shy; to &amp;lt; and &amp;shy;>&amp;shy; to &amp;gt; (revert)
if (isActivateHTML) {
html = html.replace(
/&lt;(span|b|i|strong|small|tt|del|s|u|sub|sup)&gt;(.*?)&lt;\/(\1)&gt;/g,
'<$1>$2</$3>'
);
html = html.replace(/([^']|^)'{3}(.+?)'{3}([^']|$)/gm, '$1<b>$2</b>$3');
html = html.replace(/([^']|^)'{2}(.+?)'{2}([^']|$)/gm, '$1<i>$2</i>$3');
if (IS_WG_EDIT_OR_SUBMIT_ACTION || IS_WG_HISTORY_ACTION || IS_TARGET_SPECIAL_PAGE) {
html = html.replace(
/<i>(.*?)<\/i>/g,
'<span title="italic" style="border:1px solid #c0c0c0;padding:2px">$1</span>'
);
}
// I'm in a comment field (italic)
}
if (IS_DIFF_ACTION) {
html = html.replace(/<a [^>]+><\/a>/g, ''); // Clean
html = html.replace(
/([^[]|^)\[\s*(<a [^>]+>)(?:https?|ftps?):\/\/[\w!#%&()+./:=?@\\~-]+(<\/a>)\s+([^\n\]]+)]/gm,
'$1$2$4$3'
);
html = html.replace(
/([^[]|^)\[\s*(<a [^>]+>)((?:https?|ftps?):\/\/[\w!#%&()+./:=?@\\~-]+)(<\/a>)\s*]/gm,
'$1$2$3$4'
);
}

if (html !== originHtml) {
requestAnimationFrame((): void => {
element.innerHTML = html; // Write it back
});
}
}
};

export {processElement};
30 changes: 30 additions & 0 deletions src/AutoLink/modules/util/getTargetElements.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {IS_DIFF_ACTION, IS_TARGET_SPECIAL_PAGE, IS_WG_EDIT_OR_SUBMIT_ACTION, IS_WG_HISTORY_ACTION} from '../constant';

interface TargetElements {
color: string;
targetElements: HTMLElement[];
}

const getTargetElements = ($body: JQuery<HTMLBodyElement>): TargetElements => {
let color: string = ''; // links color (coloured links)
let targetElements: HTMLElement[] = [];

if (IS_DIFF_ACTION) {
// in diff pages
color = 'inherit'; // not coloured links
targetElements = [...$body.find('.diff,.firstrevisionheader')];
} else if (IS_WG_EDIT_OR_SUBMIT_ACTION || IS_WG_HISTORY_ACTION || IS_TARGET_SPECIAL_PAGE) {
// in comments
targetElements = [...$body.find('.comment')];
} else {
// in code sections
targetElements = [...$body.find('source,.css,.source-css,.javascript,.source-javascript')];
}

return {
color,
targetElements,
};
};

export {type TargetElements, getTargetElements};

0 comments on commit 7efc969

Please sign in to comment.