Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,61 @@ test('getRepositoryInfo', () => {
],
}
`);
expect(getRepositoryInfoAdapter('https://github.com/refined-github/sandbox/blob/bracket-in-path/foo%2F%5Bbar%5D%2Fbaz')).toMatchInlineSnapshot(`
{
"name": "sandbox",
"nameWithOwner": "refined-github/sandbox",
"owner": "refined-github",
"path": "blob/bracket-in-path/foo/[bar]/baz",
"pathParts": [
"blob",
"bracket-in-path",
"foo",
"[bar]",
"baz",
],
}
`);
expect(getRepositoryInfoAdapter('https://github.com/refined-github/sandbox/blob/bracket-in-path/foo%2F%5Bbar%5D%2F%2Fbaz')).toMatchInlineSnapshot(`
{
"name": "sandbox",
"nameWithOwner": "refined-github/sandbox",
"owner": "refined-github",
"path": "blob/bracket-in-path/foo/[bar]/baz",
"pathParts": [
"blob",
"bracket-in-path",
"foo",
"[bar]",
"baz",
],
}
`);
expect(getRepositoryInfoAdapter('https://github.com/refined-github/sandbox/blob/bracket-in-path/foo%252F%5Bbar%5D%252Fbaz')).toMatchInlineSnapshot(`
{
"name": "sandbox",
"nameWithOwner": "refined-github/sandbox",
"owner": "refined-github",
"path": "blob/bracket-in-path/foo%2F[bar]%2Fbaz",
Copy link
Copy Markdown
Member Author

@SunsetTechuila SunsetTechuila Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even GitHub doesn't handle this correctly

Image

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should show my commit: refined-github/sandbox@17041ac

"pathParts": [
"blob",
"bracket-in-path",
"foo%2F[bar]%2Fbaz",
],
}
`);
expect(getRepositoryInfoAdapter('https://github.com/refined-github/sandbox/tree/%F0%9F%98%B1')).toMatchInlineSnapshot(`
{
"name": "sandbox",
"nameWithOwner": "refined-github/sandbox",
"owner": "refined-github",
"path": "tree/😱",
"pathParts": [
"tree",
"😱",
],
}
`);
}
});

Expand Down
45 changes: 26 additions & 19 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ TEST: addTests('isCompare', [
'isQuickPR',
]);

export const isCompareWikiPage = (url: URL | HTMLAnchorElement | Location = location): boolean => isRepoWiki(url) && getCleanPathname(url).split('/').slice(3, 5).includes('_compare');
export const isCompareWikiPage = (url: URL | HTMLAnchorElement | Location = location): boolean => isRepoWiki(url) && processPathname(url).split('/').slice(3, 5).includes('_compare');
TEST: addTests('isCompareWikiPage', [
'https://github.com/brookhong/Surfingkeys/wiki/_compare/8ebb46b1a12d16fc1af442b7df0ca13ca3bb34dc...80e51eeabe69b15a3f23880ecc36f800b71e6c6d',
'https://github.com/brookhong/Surfingkeys/wiki/Color-Themes/_compare/8ebb46b1a12d16fc1af442b7df0ca13ca3bb34dc...80e51eeabe69b15a3f23880ecc36f800b71e6c6d',
Expand All @@ -109,7 +109,7 @@ TEST: addTests('isCompareWikiPage', [
/**
* @deprecated Use `isHome` and/or `isFeed` instead
*/
export const isDashboard = (url: URL | HTMLAnchorElement | Location = location): boolean => !isGist(url) && /^$|^(orgs\/[^/]+\/)?dashboard(-feed)?(\/|$)/.test(getCleanPathname(url));
export const isDashboard = (url: URL | HTMLAnchorElement | Location = location): boolean => !isGist(url) && /^$|^(orgs\/[^/]+\/)?dashboard(-feed)?(\/|$)/.test(processPathname(url));
TEST: addTests('isDashboard', [
'https://github.com///',
'https://github.com//',
Expand All @@ -131,7 +131,7 @@ TEST: addTests('isDashboard', [
'https://github.com/dashboard-feed',
]);

export const isHome = (url: URL | HTMLAnchorElement | Location = location): boolean => !isGist(url) && /^$|^dashboard\/?$/.test(getCleanPathname(url));
export const isHome = (url: URL | HTMLAnchorElement | Location = location): boolean => !isGist(url) && /^$|^dashboard\/?$/.test(processPathname(url));
TEST: addTests('isHome', [
'https://github.com',
'https://github.com//dashboard',
Expand All @@ -150,7 +150,7 @@ TEST: addTests('isHome', [
'https://github.com?search=1', // Gotcha for `isRepoTree`
]);

export const isFeed = (url: URL | HTMLAnchorElement | Location = location): boolean => !isGist(url) && /^(feed|orgs\/[^/]+\/dashboard)\/?$/.test(getCleanPathname(url));
export const isFeed = (url: URL | HTMLAnchorElement | Location = location): boolean => !isGist(url) && /^(feed|orgs\/[^/]+\/dashboard)\/?$/.test(processPathname(url));
TEST: addTests('isFeed', [
'https://github.com/feed',
'https://github.com/orgs/refined-github/dashboard',
Expand Down Expand Up @@ -246,12 +246,12 @@ TEST: addTests('isNewRelease', [
'https://github.com/sindresorhus/refined-github/releases/new',
]);

export const isNewWikiPage = (url: URL | HTMLAnchorElement | Location = location): boolean => isRepoWiki(url) && getCleanPathname(url).endsWith('/_new');
export const isNewWikiPage = (url: URL | HTMLAnchorElement | Location = location): boolean => isRepoWiki(url) && processPathname(url).endsWith('/_new');
TEST: addTests('isNewWikiPage', [
'https://github.com/tooomm/wikitest/wiki/_new',
]);

export const isNotifications = (url: URL | HTMLAnchorElement | Location = location): boolean => getCleanPathname(url) === 'notifications';
export const isNotifications = (url: URL | HTMLAnchorElement | Location = location): boolean => processPathname(url) === 'notifications';
TEST: addTests('isNotifications', [
'https://github.com/notifications',
]);
Expand All @@ -271,7 +271,7 @@ TEST: addTests('isTeamDiscussion', [
'https://github.com/orgs/refined-github/teams/core-team',
]);

export const isOwnUserProfile = (): boolean => getCleanPathname() === getLoggedInUser();
export const isOwnUserProfile = (): boolean => processPathname() === getLoggedInUser();

// If there's a Report Abuse link, we're not part of the org
export const isOwnOrganizationProfile = (): boolean => isOrganizationProfile() && !exists('[href*="contact/report-abuse?report="]');
Expand Down Expand Up @@ -437,7 +437,7 @@ TEST: addTests('isEditingRelease', [
export const hasReleaseEditor = (url: URL | HTMLAnchorElement | Location = location): boolean => isEditingRelease(url) || isNewRelease(url);
TEST: addTests('hasReleaseEditor', combinedTestOnly);

export const isEditingWikiPage = (url: URL | HTMLAnchorElement | Location = location): boolean => isRepoWiki(url) && getCleanPathname(url).endsWith('/_edit');
export const isEditingWikiPage = (url: URL | HTMLAnchorElement | Location = location): boolean => isRepoWiki(url) && processPathname(url).endsWith('/_edit');
TEST: addTests('isEditingWikiPage', [
'https://github.com/tooomm/wikitest/wiki/Getting-Started/_edit',
]);
Expand All @@ -446,7 +446,7 @@ export const hasWikiPageEditor = (url: URL | HTMLAnchorElement | Location = loca
TEST: addTests('hasWikiPageEditor', combinedTestOnly);

export const isRepo = (url: URL | HTMLAnchorElement | Location = location): boolean => {
const [user, repo, extra] = getCleanPathname(url).split('/');
const [user, repo, extra] = processPathname(url).split('/');
return Boolean(user
&& repo
&& !reservedNames.includes(user)
Expand Down Expand Up @@ -776,7 +776,7 @@ const doesLookLikeAProfile = (string: string | undefined): boolean =>

export const isProfile = (url: URL | HTMLAnchorElement | Location = location): boolean =>
!isGist(url)
&& doesLookLikeAProfile(getCleanPathname(url));
&& doesLookLikeAProfile(processPathname(url));

TEST: addTests('isProfile', [
'https://github.com/fregante',
Expand Down Expand Up @@ -894,7 +894,7 @@ TEST: addTests('isRepoGitObject', [
/** Covers blob, trees and blame pages */
export const isRepoGitObject = (url: URL | HTMLAnchorElement | Location = location): boolean =>
isRepo(url)
&& [undefined, 'blob', 'tree', 'blame'].includes(getCleanPathname(url).split('/')[2]);
&& [undefined, 'blob', 'tree', 'blame'].includes(processPathname(url).split('/')[2]);

TEST: addTests('hasFiles', combinedTestOnly);
/** Has a list of files */
Expand Down Expand Up @@ -952,7 +952,7 @@ export const canUserAdminRepo = (): boolean => {
// eslint-disable-next-line @typescript-eslint/no-deprecated
export const canUserAccessRepoSettings = canUserAdminRepo;

export const isNewRepo = (url: URL | HTMLAnchorElement | Location = location): boolean => !isGist(url) && (url.pathname === '/new' || /^organizations\/[^/]+\/repositories\/new$/.test(getCleanPathname(url)));
export const isNewRepo = (url: URL | HTMLAnchorElement | Location = location): boolean => !isGist(url) && (url.pathname === '/new' || /^organizations\/[^/]+\/repositories\/new$/.test(processPathname(url)));
TEST: addTests('isNewRepo', [
'https://github.com/new',
'https://github.com/organizations/npmhub/repositories/new',
Expand All @@ -967,11 +967,18 @@ TEST: addTests('isNewRepoTemplate', [
/** Get the logged-in user’s username */
const getLoggedInUser = (): string | undefined => $('meta[name="user-login"]')?.getAttribute('content') ?? undefined;

/** Drop all redundant slashes */
const getCleanPathname = (url: URL | HTMLAnchorElement | Location = location): string => url.pathname.replaceAll(/\/\/+/g, '/').replace(/\/$/, '').slice(1);
/** Decode it and drop all redundant slashes */
const processPathname = (url: URL | HTMLAnchorElement | Location = location): string =>
url.pathname
.split('/')
.map(part => decodeURIComponent(part))
.join('/')
.replaceAll(/\/\/+/g, '/')
.replace(/\/$/, '')
.slice(1);

const getCleanGistPathname = (url: URL | HTMLAnchorElement | Location = location): string | undefined => {
const pathname = getCleanPathname(url);
const pathname = processPathname(url);
if (url.hostname.startsWith('gist.')) {
return pathname;
}
Expand All @@ -981,7 +988,7 @@ const getCleanGistPathname = (url: URL | HTMLAnchorElement | Location = location
};

const getOrg = (url: URL | HTMLAnchorElement | Location = location): {name: string; path: string} | undefined => {
const [orgs, name, ...path] = getCleanPathname(url).split('/');
const [orgs, name, ...path] = processPathname(url).split('/');
if (orgs === 'orgs' && name) {
return {name, path: path.join('/')};
}
Expand Down Expand Up @@ -1027,7 +1034,7 @@ const getRepo = (url?: URL | HTMLAnchorElement | Location | string): RepositoryI
if (canonical) {
const canonicalUrl = new URL(canonical.content, location.origin);
// Sometimes GitHub sets the canonical to an incomplete URL, so it can't be used
if (getCleanPathname(canonicalUrl).toLowerCase() === getCleanPathname(location).toLowerCase()) {
if (processPathname(canonicalUrl).toLowerCase() === processPathname(location).toLowerCase()) {
url = canonicalUrl;
}
}
Expand All @@ -1041,7 +1048,7 @@ const getRepo = (url?: URL | HTMLAnchorElement | Location | string): RepositoryI
return;
}

const [owner, name, ...pathParts] = getCleanPathname(url).split('/') as [string, string, string];
const [owner, name, ...pathParts] = processPathname(url).split('/') as [string, string, string];
return {
owner,
name,
Expand All @@ -1054,7 +1061,7 @@ const getRepo = (url?: URL | HTMLAnchorElement | Location | string): RepositoryI
export const utils = {
getOrg,
getLoggedInUser,
getCleanPathname,
processPathname,
getCleanGistPathname,
getRepositoryInfo: getRepo,
parseRepoExplorerTitle,
Expand Down
Loading