From 63bcaf343ac0c7b13e6778e4884d85fb77b92ce3 Mon Sep 17 00:00:00 2001 From: Chlod Alejandro Date: Tue, 23 Apr 2024 01:36:09 +0800 Subject: [PATCH] util: add diff URL parser --- .../ante/ui/pages/CopiedTemplateRowPage.tsx | 34 +-- src/wiki/util/index.ts | 1 + src/wiki/util/parseDiffUrl.ts | 92 ++++++ tests/unit/browser/UtilityUnitTests.ts | 270 ++++++++++++++++++ 4 files changed, 370 insertions(+), 27 deletions(-) create mode 100644 src/wiki/util/parseDiffUrl.ts diff --git a/src/modules/ante/ui/pages/CopiedTemplateRowPage.tsx b/src/modules/ante/ui/pages/CopiedTemplateRowPage.tsx index dcd62d64f..20a8fc6a1 100644 --- a/src/modules/ante/ui/pages/CopiedTemplateRowPage.tsx +++ b/src/modules/ante/ui/pages/CopiedTemplateRowPage.tsx @@ -16,6 +16,7 @@ import RevisionDateGetButton from '../components/RevisionDateGetButton'; import SmartTitleInputWidget from '../components/SmartTitleInputWidget'; import PageLatestRevisionGetButton from '../components/PageLatestRevisionGetButton'; import dangerModeConfirm from '../../../../util/dangerModeConfirm'; +import parseDiffUrl from '../../../../wiki/util/parseDiffUrl'; export interface CopiedTemplateRowPageData { /** @@ -551,33 +552,12 @@ function initCopiedTemplateRowPage() { return; } } - // From the same wiki, accept deprecation - - // Attempt to get values from URL parameters (when using `/w/index.php?action=diff`) - let oldid = url.searchParams.get( 'oldid' ); - let diff = url.searchParams.get( 'diff' ); - const title = url.searchParams.get( 'title' ); - - // Attempt to get values from Special:Diff short-link - const diffSpecialPageCheck = - // eslint-disable-next-line security/detect-unsafe-regex - /\/wiki\/Special:Diff\/(prev|next|\d+)(?:\/(prev|next|\d+))?/.exec( url.pathname ); - if ( diffSpecialPageCheck != null ) { - if ( - diffSpecialPageCheck[ 1 ] != null && - diffSpecialPageCheck[ 2 ] == null - ) { - // Special:Diff/diff - diff = diffSpecialPageCheck[ 1 ]; - } else if ( - diffSpecialPageCheck[ 1 ] != null && - diffSpecialPageCheck[ 2 ] != null - ) { - // Special:Diff/oldid/diff - oldid = diffSpecialPageCheck[ 1 ]; - diff = diffSpecialPageCheck[ 2 ]; - } - } + // From the same wiki, accept deprecation immediately. + + // Parse out info from this diff URL + const parseInfo = parseDiffUrl( url ); + let { diff, oldid } = parseInfo; + const { title } = parseInfo; // If only an oldid was provided, and no diff if ( oldid && !diff ) { diff --git a/src/wiki/util/index.ts b/src/wiki/util/index.ts index fdb800f13..12c9d1ce5 100644 --- a/src/wiki/util/index.ts +++ b/src/wiki/util/index.ts @@ -43,6 +43,7 @@ export default { nsId: nsId, openWindow: openWindow, pagelinkToTitle: pagelinkToTitle, + parseDiffUrl: parseDiffUrl, performHacks: performHacks, purge: purge, renderWikitext: renderWikitext, diff --git a/src/wiki/util/parseDiffUrl.ts b/src/wiki/util/parseDiffUrl.ts new file mode 100644 index 000000000..f72e13f62 --- /dev/null +++ b/src/wiki/util/parseDiffUrl.ts @@ -0,0 +1,92 @@ +interface DiffInfo { + diff?: number | string; + oldid?: number | string; + title?: string; +} + +/** + * What it says on the tin. Attempt to parse out a `title`, `diff`, + * or `oldid` from a URL. This is useful for converting diff URLs into actual + * diff information, and especially useful for {{copied}} templates. + * + * @param url The URL to parse + * @return Parsed info: `diff` or `oldid` revision IDs, and/or the page title. + */ +export default function parseDiffUrl( url: URL | string ): DiffInfo { + if ( typeof url === 'string' ) { + url = new URL( url ); + } + + // Attempt to get values from URL parameters (when using `/w/index.php?action=diff`) + let oldid: string | number = url.searchParams.get( 'oldid' ); + let diff: string | number = url.searchParams.get( 'diff' ); + let title = url.searchParams.get( 'title' ); + + // Attempt to get information from this URL. + tryConvert: { + if ( title && oldid && diff ) { + // Skip if there's nothing else we need to get. + break tryConvert; + } + + // Attempt to get values from Special:Diff short-link + const diffSpecialPageCheck = + // eslint-disable-next-line security/detect-unsafe-regex + /\/wiki\/Special:Diff\/(prev|next|\d+)(?:\/(prev|next|\d+))?/i.exec( url.pathname ); + if ( diffSpecialPageCheck != null ) { + if ( + diffSpecialPageCheck[ 1 ] != null && + diffSpecialPageCheck[ 2 ] == null + ) { + // Special:Diff/diff + diff = diffSpecialPageCheck[ 1 ]; + } else if ( + diffSpecialPageCheck[ 1 ] != null && + diffSpecialPageCheck[ 2 ] != null + ) { + // Special:Diff/oldid/diff + oldid = diffSpecialPageCheck[ 1 ]; + diff = diffSpecialPageCheck[ 2 ]; + } + break tryConvert; + } + + // Attempt to get values from Special:PermanentLink short-link + const permanentLinkCheck = + /\/wiki\/Special:Perma(nent)?link\/(\d+)/i.exec( url.pathname ); + if ( permanentLinkCheck != null ) { + oldid = permanentLinkCheck[ 2 ]; + break tryConvert; + } + + // Attempt to get values from article path with ?oldid or ?diff + // eslint-disable-next-line security/detect-non-literal-regexp + const articlePathRegex = new RegExp( mw.util.getUrl( '(.*)' ) ) + .exec( url.pathname ); + if ( articlePathRegex != null ) { + title = articlePathRegex[ 1 ]; + break tryConvert; + } + } + + // Convert numbers to numbers + if ( oldid != null && !isNaN( +oldid ) ) { + oldid = +oldid; + } + if ( diff != null && !isNaN( +diff ) ) { + diff = +diff; + } + + // Try to convert a page title + try { + title = new mw.Title( title ).getPrefixedText(); + } catch ( e ) { + console.warn( 'Failed to normalize page title during diff URL conversion.' ); + } + + return { + diff: diff, + oldid: oldid, + title: title + }; +} diff --git a/tests/unit/browser/UtilityUnitTests.ts b/tests/unit/browser/UtilityUnitTests.ts index 67f42ffc0..ac4ebdd2c 100644 --- a/tests/unit/browser/UtilityUnitTests.ts +++ b/tests/unit/browser/UtilityUnitTests.ts @@ -84,4 +84,274 @@ describe( 'wikiUtility (on-browser) function tests', () => { ] ); } ); + test( 'parseDiffUrl', async () => { + await Promise.all( [ + // title only + expect( + page.evaluate( () => { + return { ...window.deputy.wikiUtil.parseDiffUrl( + 'https://en.wikipedia.org/w/index.php?title=Main_Page' + ) }; + } ) + ).resolves.toEqual( { + diff: null, + oldid: null, + title: 'Main Page' + } ), + // diff only + expect( + page.evaluate( () => { + return { ...window.deputy.wikiUtil.parseDiffUrl( + 'https://en.wikipedia.org/w/index.php?diff=123456' + ) }; + } ) + ).resolves.toEqual( { + diff: 123456, + oldid: null, + title: null + } ), + // oldid only + expect( + page.evaluate( () => { + return { ...window.deputy.wikiUtil.parseDiffUrl( + 'https://en.wikipedia.org/w/index.php?oldid=123456' + ) }; + } ) + ).resolves.toEqual( { + diff: null, + oldid: 123456, + title: null + } ), + // title and diff + expect( + page.evaluate( () => { + return { ...window.deputy.wikiUtil.parseDiffUrl( + 'https://en.wikipedia.org/w/index.php?title=Main_Page&diff=123456' + ) }; + } ) + ).resolves.toEqual( { + diff: 123456, + oldid: null, + title: 'Main Page' + } ), + // title and oldid + expect( + page.evaluate( () => { + return { ...window.deputy.wikiUtil.parseDiffUrl( + 'https://en.wikipedia.org/w/index.php?title=Main_Page&oldid=123456' + ) }; + } ) + ).resolves.toEqual( { + diff: null, + oldid: 123456, + title: 'Main Page' + } ), + // diff and oldid + expect( + page.evaluate( () => { + return { ...window.deputy.wikiUtil.parseDiffUrl( + 'https://en.wikipedia.org/w/index.php?diff=123456&oldid=123457' + ) }; + } ) + ).resolves.toEqual( { + diff: 123456, + oldid: 123457, + title: null + } ), + // title, diff, and oldid + expect( + page.evaluate( () => { + return { ...window.deputy.wikiUtil.parseDiffUrl( + // eslint-disable-next-line max-len + 'https://en.wikipedia.org/w/index.php?title=Main_Page&diff=123456&oldid=123457' + ) }; + } ) + ).resolves.toEqual( { + diff: 123456, + oldid: 123457, + title: 'Main Page' + } ), + // Special:Diff/diff + expect( + page.evaluate( () => { + return { ...window.deputy.wikiUtil.parseDiffUrl( + 'https://en.wikipedia.org/wiki/Special:Diff/123456' + ) }; + } ) + ).resolves.toEqual( { + diff: 123456, + oldid: null, + title: null + } ), + // Special:Diff/oldid/diff + expect( + page.evaluate( () => { + return { ...window.deputy.wikiUtil.parseDiffUrl( + 'https://en.wikipedia.org/wiki/Special:Diff/123456/123457' + ) }; + } ) + ).resolves.toEqual( { + diff: 123457, + oldid: 123456, + title: null + } ), + // Special:Diff/oldid/prev + expect( + page.evaluate( () => { + return { ...window.deputy.wikiUtil.parseDiffUrl( + 'https://en.wikipedia.org/wiki/Special:Diff/123456/prev' + ) }; + } ) + ).resolves.toEqual( { + diff: 'prev', + oldid: 123456, + title: null + } ), + // Special:Diff/oldid/next + expect( + page.evaluate( () => { + return { ...window.deputy.wikiUtil.parseDiffUrl( + 'https://en.wikipedia.org/wiki/Special:Diff/123456/next' + ) }; + } ) + ).resolves.toEqual( { + diff: 'next', + oldid: 123456, + title: null + } ), + // Special:Diff/prev/diff + expect( + page.evaluate( () => { + return { ...window.deputy.wikiUtil.parseDiffUrl( + 'https://en.wikipedia.org/wiki/Special:Diff/prev/123456' + ) }; + } ) + ).resolves.toEqual( { + diff: 123456, + oldid: 'prev', + title: null + } ), + // Special:Diff/next/diff + expect( + page.evaluate( () => { + return { ...window.deputy.wikiUtil.parseDiffUrl( + 'https://en.wikipedia.org/wiki/Special:Diff/next/123456' + ) }; + } ) + ).resolves.toEqual( { + diff: 123456, + oldid: 'next', + title: null + } ), + // (edge) Special:Diff/prev/prev + expect( + page.evaluate( () => { + return { ...window.deputy.wikiUtil.parseDiffUrl( + 'https://en.wikipedia.org/wiki/Special:Diff/prev/prev' + ) }; + } ) + ).resolves.toEqual( { + diff: 'prev', + oldid: 'prev', + title: null + } ), + // (edge) Special:Diff/prev/next + expect( + page.evaluate( () => { + return { ...window.deputy.wikiUtil.parseDiffUrl( + 'https://en.wikipedia.org/wiki/Special:Diff/prev/next' + ) }; + } ) + ).resolves.toEqual( { + diff: 'next', + oldid: 'prev', + title: null + } ), + // (edge) Special:Diff/next/next + expect( + page.evaluate( () => { + return { ...window.deputy.wikiUtil.parseDiffUrl( + 'https://en.wikipedia.org/wiki/Special:Diff/next/next' + ) }; + } ) + ).resolves.toEqual( { + diff: 'next', + oldid: 'next', + title: null + } ), + // (edge) Special:Diff/next/prev + expect( + page.evaluate( () => { + return { ...window.deputy.wikiUtil.parseDiffUrl( + 'https://en.wikipedia.org/wiki/Special:Diff/next/prev' + ) }; + } ) + ).resolves.toEqual( { + diff: 'prev', + oldid: 'next', + title: null + } ), + // Special:PermanentLink/oldid + expect( + page.evaluate( () => { + return { ...window.deputy.wikiUtil.parseDiffUrl( + 'https://en.wikipedia.org/wiki/Special:PermanentLink/123456' + ) }; + } ) + ).resolves.toEqual( { + diff: null, + oldid: 123456, + title: null + } ), + // /wiki/Title + expect( + page.evaluate( () => { + return { ...window.deputy.wikiUtil.parseDiffUrl( + 'https://en.wikipedia.org/wiki/Main_Page' + ) }; + } ) + ).resolves.toEqual( { + diff: null, + oldid: null, + title: 'Main Page' + } ), + // /wiki/Title?diff= + expect( + page.evaluate( () => { + return { ...window.deputy.wikiUtil.parseDiffUrl( + 'https://en.wikipedia.org/wiki/Main_Page?diff=123456' + ) }; + } ) + ).resolves.toEqual( { + diff: 123456, + oldid: null, + title: 'Main Page' + } ), + // /wiki/Title?oldid= + expect( + page.evaluate( () => { + return { ...window.deputy.wikiUtil.parseDiffUrl( + 'https://en.wikipedia.org/wiki/Main_Page?oldid=123456' + ) }; + } ) + ).resolves.toEqual( { + diff: null, + oldid: 123456, + title: 'Main Page' + } ), + // /wiki/Title?diff=&oldid= + expect( + page.evaluate( () => { + return { ...window.deputy.wikiUtil.parseDiffUrl( + 'https://en.wikipedia.org/wiki/Main_Page?diff=123456&oldid=123457' + ) }; + } ) + ).resolves.toEqual( { + diff: 123456, + oldid: 123457, + title: 'Main Page' + } ) + ] ); + } ); + } );