github-url-detection 11.1.2 → 11.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,22 @@
1
1
  /* Examples added by add-examples-to-dts.ts */
2
+ /**
3
+ * Waits for a detection to return true by repeatedly checking it on each animation frame.
4
+ * Useful for DOM-based detections that need to wait for elements to appear.
5
+ * @param detection - A detection function to check repeatedly
6
+ * @returns A promise that resolves to the final result of the detection
7
+ * @example
8
+ * ```
9
+ * import {utils} from 'github-url-detection';
10
+ *
11
+ * async function init() {
12
+ * if (!await utils.waitFor(isOrganizationProfile)) {
13
+ * return;
14
+ * }
15
+ * // Do something when on organization profile
16
+ * }
17
+ * ```
18
+ */
19
+ declare function waitFor(detection: () => boolean): Promise<boolean>;
2
20
  export declare const is404: () => boolean;
3
21
  export declare const is500: () => boolean;
4
22
  export declare const isPasswordConfirmation: () => boolean;
@@ -42,15 +60,16 @@ export declare const isCompare: (url?: URL | HTMLAnchorElement | Location) => bo
42
60
  */
43
61
  export declare const isCompareWikiPage: (url?: URL | HTMLAnchorElement | Location) => boolean;
44
62
  /**
63
+ * @deprecated Use `isHome` and/or `isFeed` instead
64
+ *
45
65
  * @example https://github.com///
46
66
  * @example https://github.com//
47
67
  * @example https://github.com/
48
68
  * @example https://github.com
49
- * @example https://github.com/orgs/test/dashboard
69
+ * @example https://github.com/orgs/refined-github/dashboard
50
70
  * @example https://github.com/dashboard/index/2
51
71
  * @example https://github.com//dashboard
52
72
  * @example https://github.com/dashboard
53
- * @example https://github.com/orgs/edit/dashboard
54
73
  * @example https://github.big-corp.com/
55
74
  * @example https://not-github.com/
56
75
  * @example https://my-little-hub.com/
@@ -63,6 +82,29 @@ export declare const isCompareWikiPage: (url?: URL | HTMLAnchorElement | Locatio
63
82
  * @example https://github.com/dashboard-feed
64
83
  */
65
84
  export declare const isDashboard: (url?: URL | HTMLAnchorElement | Location) => boolean;
85
+ /**
86
+ * @example https://github.com
87
+ * @example https://github.com//dashboard
88
+ * @example https://github.com///
89
+ * @example https://github.com//
90
+ * @example https://github.com/
91
+ * @example https://github.com/dashboard
92
+ * @example https://github.big-corp.com/
93
+ * @example https://not-github.com/
94
+ * @example https://my-little-hub.com/
95
+ * @example https://github.com/?tab=repositories
96
+ * @example https://github.com/?tab=stars
97
+ * @example https://github.com/?tab=followers
98
+ * @example https://github.com/?tab=following
99
+ * @example https://github.com/?tab=overview
100
+ * @example https://github.com?search=1
101
+ */
102
+ export declare const isHome: (url?: URL | HTMLAnchorElement | Location) => boolean;
103
+ /**
104
+ * @example https://github.com/feed
105
+ * @example https://github.com/orgs/refined-github/dashboard
106
+ */
107
+ export declare const isFeed: (url?: URL | HTMLAnchorElement | Location) => boolean;
66
108
  /**
67
109
  * @example https://github.big-corp.com/
68
110
  * @example https://not-github.com/
@@ -274,6 +316,9 @@ export declare const hasWikiPageEditor: (url?: URL | HTMLAnchorElement | Locatio
274
316
  export declare const isRepo: (url?: URL | HTMLAnchorElement | Location) => boolean;
275
317
  export declare const hasRepoHeader: (url?: URL | HTMLAnchorElement | Location) => boolean;
276
318
  export declare const isEmptyRepoRoot: () => boolean;
319
+ /**
320
+ * @deprecated Doesn't work anymore. Use `isEmptyRepoRoot` or API instead.
321
+ */
277
322
  export declare const isEmptyRepo: () => boolean;
278
323
  export declare const isPublicRepo: () => boolean;
279
324
  export declare const isArchivedRepo: () => boolean;
@@ -599,7 +644,11 @@ export declare const isNewAction: (url?: URL | HTMLAnchorElement | Location) =>
599
644
  */
600
645
  export declare const isRepositoryActions: (url?: URL | HTMLAnchorElement | Location) => boolean;
601
646
  export declare const isUserTheOrganizationOwner: () => boolean;
647
+ /**
648
+ * @deprecated Use canUserAccessRepoSettings or API instead.
649
+ */
602
650
  export declare const canUserAdminRepo: () => boolean;
651
+ export declare const canUserAccessRepoSettings: () => boolean;
603
652
  /**
604
653
  * @example https://github.com/new
605
654
  * @example https://github.com/organizations/npmhub/repositories/new
@@ -636,4 +685,6 @@ export declare const utils: {
636
685
  getCleanGistPathname: (url?: URL | HTMLAnchorElement | Location) => string | undefined;
637
686
  getRepositoryInfo: (url?: URL | HTMLAnchorElement | Location | string) => RepositoryInfo | undefined;
638
687
  parseRepoExplorerTitle: (pathname: string, title: string) => RepoExplorerInfo | undefined;
688
+ waitFor: typeof waitFor;
639
689
  };
690
+ export {};
@@ -2,6 +2,14 @@
2
2
  import reservedNames from "github-reserved-names/reserved-names.json" with { type: "json" };
3
3
  var $ = (selector) => document.querySelector(selector);
4
4
  var exists = (selector) => Boolean($(selector));
5
+ async function waitFor(detection) {
6
+ while (!detection() && document.readyState !== "complete") {
7
+ await new Promise((resolve) => {
8
+ requestAnimationFrame(resolve);
9
+ });
10
+ }
11
+ return detection();
12
+ }
5
13
  var is404 = () => /^(Page|File) not found · GitHub/.test(document.title);
6
14
  var is500 = () => document.title === "Server Error \xB7 GitHub" || document.title === "Unicorn! \xB7 GitHub" || document.title === "504 Gateway Time-out";
7
15
  var isPasswordConfirmation = () => document.title === "Confirm password" || document.title === "Confirm access";
@@ -13,6 +21,8 @@ var isRepoCommitList = (url = location) => Boolean(getRepo(url)?.path.startsWith
13
21
  var isCompare = (url = location) => Boolean(getRepo(url)?.path.startsWith("compare"));
14
22
  var isCompareWikiPage = (url = location) => isRepoWiki(url) && getCleanPathname(url).split("/").slice(3, 5).includes("_compare");
15
23
  var isDashboard = (url = location) => !isGist(url) && /^$|^(orgs\/[^/]+\/)?dashboard(-feed)?(\/|$)/.test(getCleanPathname(url));
24
+ var isHome = (url = location) => !isGist(url) && /^$|^dashboard\/?$/.test(getCleanPathname(url));
25
+ var isFeed = (url = location) => !isGist(url) && /^(feed|orgs\/[^/]+\/dashboard)\/?$/.test(getCleanPathname(url));
16
26
  var isEnterprise = (url = location) => url.hostname !== "github.com" && url.hostname !== "gist.github.com";
17
27
  var isGist = (url = location) => typeof getCleanGistPathname(url) === "string";
18
28
  var isGlobalIssueOrPRList = (url = location) => ["issues", "pulls"].includes(url.pathname.split("/", 2)[1]);
@@ -29,7 +39,11 @@ var isNewRelease = (url = location) => getRepo(url)?.path === "releases/new";
29
39
  var isNewWikiPage = (url = location) => isRepoWiki(url) && getCleanPathname(url).endsWith("/_new");
30
40
  var isNotifications = (url = location) => getCleanPathname(url) === "notifications";
31
41
  var isOrganizationProfile = () => exists('meta[name="hovercard-subject-tag"][content^="organization"]');
32
- var isOrganizationRepo = () => exists('.AppHeader-context-full [data-hovercard-type="organization"]');
42
+ var isOrganizationRepo = () => exists([
43
+ 'qbsearch-input[data-current-repository][data-current-org]:not([data-current-repository=""], [data-current-org=""])',
44
+ // TODO: Remove after June 2026
45
+ '.AppHeader-context-full [data-hovercard-type="organization"]'
46
+ ].join(","));
33
47
  var isTeamDiscussion = (url = location) => Boolean(getOrg(url)?.path.startsWith("teams"));
34
48
  var isOwnUserProfile = () => getCleanPathname() === getLoggedInUser();
35
49
  var isOwnOrganizationProfile = () => isOrganizationProfile() && !exists('[href*="contact/report-abuse?report="]');
@@ -82,7 +96,12 @@ var isRepo = (url = location) => {
82
96
  return Boolean(user && repo && !reservedNames.includes(user) && !url.hostname.startsWith("gist.") && extra !== "generate");
83
97
  };
84
98
  var hasRepoHeader = (url = location) => isRepo(url) && !isRepoSearch(url);
85
- var isEmptyRepoRoot = () => isRepoHome() && !exists('link[rel="canonical"]');
99
+ var isEmptyRepoRoot = () => isRepoHome() && exists([
100
+ // If you don't have write access
101
+ ".blankslate-icon",
102
+ // If you have write access
103
+ "#empty-setup-clone-url"
104
+ ].join(","));
86
105
  var isEmptyRepo = () => exists('[aria-label="Cannot fork because repository is empty."]');
87
106
  var isPublicRepo = () => exists('meta[name="octolytics-dimension-repository_public"][content="true"]');
88
107
  var isArchivedRepo = () => Boolean(isRepo() && $("main > .flash-warn")?.textContent.includes("archived"));
@@ -163,7 +182,7 @@ var doesLookLikeAProfile = (string) => typeof string === "string" && string.leng
163
182
  var isProfile = (url = location) => !isGist(url) && doesLookLikeAProfile(getCleanPathname(url));
164
183
  var isGistProfile = (url = location) => doesLookLikeAProfile(getCleanGistPathname(url));
165
184
  var isUserProfile = () => isProfile() && !isOrganizationProfile();
166
- var isPrivateUserProfile = () => isUserProfile() && !exists('.UnderlineNav-item[href$="tab=stars"]');
185
+ var isPrivateUserProfile = () => isUserProfile() && exists("#user-private-profile-frame");
167
186
  var isUserProfileMainTab = () => isUserProfile() && !new URLSearchParams(location.search).has("tab");
168
187
  var isUserProfileRepoTab = (url = location) => isProfile(url) && new URLSearchParams(url.search).get("tab") === "repositories";
169
188
  var isUserProfileStarsTab = (url = location) => isProfile(url) && new URLSearchParams(url.search).get("tab") === "stars";
@@ -181,7 +200,15 @@ var isActionRun = (url = location) => /^(actions\/)?runs/.test(getRepo(url)?.pat
181
200
  var isNewAction = (url = location) => getRepo(url)?.path === "actions/new";
182
201
  var isRepositoryActions = (url = location) => /^actions(\/workflows\/.+\.ya?ml)?$/.test(getRepo(url)?.path);
183
202
  var isUserTheOrganizationOwner = () => isOrganizationProfile() && exists('[aria-label="Organization"] [data-tab-item="org-header-settings-tab"]');
184
- var canUserAdminRepo = () => isRepo() && exists('.reponav-item[href$="/settings"], [data-tab-item$="settings-tab"]');
203
+ var canUserAdminRepo = () => {
204
+ const repo = getRepo();
205
+ return Boolean(repo && exists(`:is(${[
206
+ ".GlobalNav",
207
+ // Remove after June 2026
208
+ ".js-repo-nav"
209
+ ].join(",")}) a[href="/${repo.nameWithOwner}/settings"]`));
210
+ };
211
+ var canUserAccessRepoSettings = canUserAdminRepo;
185
212
  var isNewRepo = (url = location) => !isGist(url) && (url.pathname === "/new" || /^organizations\/[^/]+\/repositories\/new$/.test(getCleanPathname(url)));
186
213
  var isNewRepoTemplate = (url = location) => Boolean(url.pathname.split("/")[3] === "generate");
187
214
  var getLoggedInUser = () => $('meta[name="user-login"]')?.getAttribute("content") ?? void 0;
@@ -232,9 +259,11 @@ var utils = {
232
259
  getCleanPathname,
233
260
  getCleanGistPathname,
234
261
  getRepositoryInfo: getRepo,
235
- parseRepoExplorerTitle
262
+ parseRepoExplorerTitle,
263
+ waitFor
236
264
  };
237
265
  export {
266
+ canUserAccessRepoSettings,
238
267
  canUserAdminRepo,
239
268
  hasCode,
240
269
  hasComments,
@@ -270,6 +299,7 @@ export {
270
299
  isEmptyRepo,
271
300
  isEmptyRepoRoot,
272
301
  isEnterprise,
302
+ isFeed,
273
303
  isFileFinder,
274
304
  isForkedRepo,
275
305
  isForkingRepo,
@@ -279,6 +309,7 @@ export {
279
309
  isGistRevision,
280
310
  isGlobalIssueOrPRList,
281
311
  isGlobalSearchResults,
312
+ isHome,
282
313
  isIssue,
283
314
  isIssueOrPRList,
284
315
  isLabelList,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "github-url-detection",
3
- "version": "11.1.2",
3
+ "version": "11.2.0",
4
4
  "description": "Which GitHub page are you on? Is it an issue? Is it a list? Perfect for your WebExtension or userscript.",
5
5
  "keywords": [
6
6
  "github",
@@ -42,7 +42,7 @@
42
42
  "xo": "xo"
43
43
  },
44
44
  "dependencies": {
45
- "github-reserved-names": "^2.1.1"
45
+ "github-reserved-names": "^2.1.3"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@sindresorhus/tsconfig": "^8.1.0",
package/readme.md CHANGED
@@ -67,6 +67,30 @@ if (pageDetect.isOrganizationProfile()) {
67
67
  }
68
68
  ```
69
69
 
70
+ ### Async detections with `waitFor`
71
+
72
+ The `waitFor` helper function allows you to wait for a detection to become true by repeatedly checking it on each animation frame. This is useful for DOM-based detections that need to wait for elements to appear before the document is fully loaded.
73
+
74
+ ```js
75
+ import {utils, isOrganizationProfile} from 'github-url-detection';
76
+
77
+ async function init() {
78
+ // Wait for the detection to return true or for the document to be complete
79
+ if (!await utils.waitFor(isOrganizationProfile)) {
80
+ return; // Not an organization profile
81
+ }
82
+
83
+ // The page is now confirmed to be an organization profile
84
+ console.log('On organization profile!');
85
+ }
86
+ ```
87
+
88
+ The `waitFor` function:
89
+ - Repeatedly calls the detection function on each animation frame
90
+ - Stops when the detection returns `true` or when `document.readyState` is `'complete'`
91
+ - Returns the final result of the detection
92
+ - Works with any detection function that returns a boolean
93
+
70
94
  ## Related
71
95
 
72
96
  - [github-reserved-names](https://github.com/Mottie/github-reserved-names) - Get a list, or check if a user or organization name is reserved by GitHub.