Skip to content
Merged
1 change: 1 addition & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ Thanks for contributing! 🦋🙌
- [](# "hide-zero-packages") [Hides the `Packages` tab in repositories if it’s empty.](https://user-images.githubusercontent.com/35382021/62426530-688ef780-b6d5-11e9-93f2-515110aed1eb.jpg)
- [](# "forked-to") [Adds a shortcut to your forks next to the `Fork` button on the current repo.](https://user-images.githubusercontent.com/55841/64077281-17bbf000-cccf-11e9-9123-092063f65357.png)
- [](# "repo-age") [Adds the age of the repository to the statistics bar.](https://user-images.githubusercontent.com/3848317/69494069-7d2b1180-0eb7-11ea-9aa1-d4194e566340.png)
- [](# "show-open-prs-of-forks") [In your forked repos, shows number of your open PRs to the original repo.](https://user-images.githubusercontent.com/1922624/76398271-e0648500-637c-11ea-8210-53dda1be9d51.png)

<!-- Refer to style guide above. Keep this message between sections. -->

Expand Down
1 change: 1 addition & 0 deletions source/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ import './features/repo-wide-file-finder';
import './features/preserve-file-finder-term';
import './features/file-finder-buffer';
import './features/pr-commit-lines-changed';
import './features/show-open-prs-of-forks';

// Add global for easier debugging
(window as any).select = select;
17 changes: 4 additions & 13 deletions source/features/forked-to.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import elementReady from 'element-ready';
import linkExternalIcon from 'octicon/link-external.svg';
import features from '../libs/features';
import fetchDom from '../libs/fetch-dom';
import {getRepoURL, getUsername} from '../libs/utils';
import {isForkedRepo} from '../libs/page-detect';
import {getRepoURL, getUsername, getForkedRepo} from '../libs/utils';

const getForkSourceRepo = (): string => findForkedRepo() ?? getRepoURL();
const getForkSourceRepo = (): string => getForkedRepo() ?? getRepoURL();
const getCacheKey = (): string => `forked-to:${getUsername()}@${getForkSourceRepo()}`;

const updateCache = cache.function(async (): Promise<string[] | undefined> => {
Expand All @@ -26,15 +27,6 @@ const updateCache = cache.function(async (): Promise<string[] | undefined> => {
staleWhileRevalidate: 5
});

function findForkedRepo(): string | undefined {
const forkSourceElement = select<HTMLAnchorElement>('.fork-flag a');
if (forkSourceElement) {
return forkSourceElement.pathname.slice(1);
}

return undefined;
}

async function updateUI(forks: string[]): Promise<void> {
// Don't add button if you're visiting the only fork available
if (forks.length === 1 && forks[0] === getRepoURL()) {
Expand Down Expand Up @@ -92,11 +84,10 @@ async function init(): Promise<void | false> {

// This feature only applies to users that have multiple organizations, because that makes a fork picker modal appear when clicking on "Fork"
const hasOrganizations = await elementReady('details-dialog[src*="/fork"] include-fragment');
const isForkedRepo = findForkedRepo();

// Only fetch/update forks when we see a fork (on the current page or in the cache).
// This avoids having to `updateCache` for every single repo you visit.
if (forks || (hasOrganizations && isForkedRepo)) {
if (forks || (hasOrganizations && isForkedRepo())) {
await updateCache();
} else {
return false;
Expand Down
108 changes: 108 additions & 0 deletions source/features/show-open-prs-of-forks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import React from 'dom-chef';
import cache from 'webext-storage-cache';
import select from 'select-dom';
import elementReady from 'element-ready';
import * as api from '../libs/api';
import features from '../libs/features';
import {isForkedRepo, isRepoWithAccess} from '../libs/page-detect';
import {getForkedRepo, getUsername, pluralize} from '../libs/utils';

function getLinkCopy(count: number): string {
return pluralize(count, 'one open pull request', '$$ open pull requests');
}

const countPRs = cache.function(async (forkedRepo: string): Promise<[number, number?]> => {
// Grab the PR count and the first PR's URL
// This allows to link to the PR directly if only one is found
const {search} = await api.v4(`
search(
first: 1,
type: ISSUE,
query: "repo:${forkedRepo} is:pr is:open author:${getUsername()}"
) {
issueCount
nodes {
... on PullRequest {
number
}
}
}
`);

if (search.issueCount === 1) {
return [1, search.nodes[0].number];
}

return [search.issueCount];
}, {
maxAge: 1 / 2, // Stale after 12 hours
staleWhileRevalidate: 2,
cacheKey: ([forkedRepo]): string => 'prs-on-forked-repo:' + forkedRepo
});

async function getPRs(): Promise<[number, string] | []> {
await elementReady('.repohead + *'); // Wait for the tab bar to be loaded
if (!isRepoWithAccess()) {
return [];
}

const forkedRepo = getForkedRepo()!;
const [count, firstPr] = await countPRs(forkedRepo);
if (count === 1) {
return [count, `/${forkedRepo}/pull/${firstPr!}`];
}

return [count, `/${forkedRepo}/pulls?q=is%3Apr+is%3Aopen+sort%3Aupdated-desc+author%3A${getUsername()}`];
}

async function initHeadHint(): Promise<void | false> {
const [count, url] = await getPRs();
if (!count) {
return false;
}

select('.fork-flag .text')!.append(
<> with <a href={url}>{getLinkCopy(count)}</a></>
);
}

async function initDeleteHint(): Promise<void | false> {
const [count, url] = await getPRs();
if (!count) {
return false;
}

select('details-dialog[aria-label*="Delete"] .Box-body p:first-child')!.after(
<p className="flash flash-warn">
It will also abandon <a href={url}>your {getLinkCopy(count)}</a> in <strong>{getForkedRepo()!}</strong> and you’ll no longer be able to edit {count === 1 ? 'it' : 'them'}.
</p>
);
}

features.add({
id: __featureName__,
description: 'In your forked repos, shows number of your open PRs to the original repo.',
screenshot: 'https://user-images.githubusercontent.com/1922624/76398271-e0648500-637c-11ea-8210-53dda1be9d51.png',
include: [
features.isRepo
],
exclude: [
() => !isForkedRepo()
],
load: features.nowAndOnAjaxedPages,
init: initHeadHint
});

features.add({
id: __featureName__,
description: '',
screenshot: '',
include: [
features.isRepoSettings
],
exclude: [
() => !isForkedRepo()
],
load: features.nowAndOnAjaxedPages,
init: initDeleteHint
});
3 changes: 3 additions & 0 deletions source/libs/page-detect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,9 @@ export const _isFileFinder = [
'https://github.com/sindresorhus/refined-github/find/master'
];

export const isForkedRepo = (): boolean => select.exists('meta[name="octolytics-dimension-repository_is_fork"][content="true"]');
export const _isForkedRepo = domBased;

export const isSingleGist = (): boolean => isGist() && /^\/(gist\/)?[^/]+\/[\da-f]{32}$/.test(location.pathname);
export const _isSingleGist = [
'https://gist.github.com/sindresorhus/0ea3c2845718a0a0f0beb579ff14f064'
Expand Down
4 changes: 4 additions & 0 deletions source/libs/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ export const getOwnerAndRepo = (): {
return {ownerName, repoName};
};

export function getForkedRepo(): string | undefined {
Copy link
Member

@fregante fregante Mar 25, 2020

Choose a reason for hiding this comment

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

This is ambiguous. Is it the repo that was forked or the fork itself?

The correct term would be "source repo". Perhaps getForkSourceRepo would be the correct term.

Can you change this name everywhere? Including const forkedRepo = ... (but not isForkedRepo, that's more clear, I think)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I thought so as well, but I trusted the existing naming (the function existed before, I just moved it to utils).

Yes, I'll take care of it when I'm going to handle #2934.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hm, there's already a function with that name in the forked-to feature.

const getForkSourceRepo = (): string => getForkedRepo() ?? getRepoURL();

Copy link
Member

Choose a reason for hiding this comment

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

How about

  • getParentRepo: only works if there’s a parent)
  • getSourceRepo: always gets the source repo, whether it’s the parent or the current

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sound's good to me. I'll do some refactoring of these, but I think I'll open a separate PR for that.

return select<HTMLAnchorElement>('.fork-flag a')?.pathname.slice(1);
Copy link
Member

Choose a reason for hiding this comment

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

Also you could use the same meta tags I used in the isForkedRepo since they're available earlier on the page:

<meta name="octolytics-dimension-repository_parent_nwo" content="sindresorhus/refined-github" />
Suggested change
return select<HTMLAnchorElement>('.fork-flag a')?.pathname.slice(1);
return select<HTMLMetaElement>('[name="octolytics-dimension-repository_parent_nwo"]')?.content;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure. 👍

}

export const getReference = (): string | undefined => {
const pathnameParts = location.pathname.split('/');
if (['commits', 'blob', 'tree', 'blame'].includes(pathnameParts[3])) {
Expand Down