Skip to content
Merged
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
3 changes: 2 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ GitHub Enterprise is also supported. More info in the options.
- [Use the pull request title as the commit title when merging with `Squash and merge`](https://github.com/sindresorhus/refined-github/issues/276).
- [View linked gists inline in comments.](https://user-images.githubusercontent.com/6978877/33911900-c62ee968-df8b-11e7-8685-506ffafc60b4.PNG)
- [Avoid opening duplicate issues thanks to the list of possibly-related issues.](https://user-images.githubusercontent.com/29176678/37566899-85953e6e-2abf-11e8-9f0e-52d18c87bbe3.gif)
- [Use the pull request description as the commit message when merging with `Squash and merge`](https://github.com/sindresorhus/refined-github/issues/1322).
- [Use the pull request description as the commit message when merging with `Squash and merge`.](https://github.com/sindresorhus/refined-github/issues/1322).
- [Access related pages on 404 pages.](https://user-images.githubusercontent.com/1402241/46402857-7bdada80-c733-11e8-91a1-856573078ff5.png)

### More actions

Expand Down
7 changes: 6 additions & 1 deletion source/content.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ import hideCommentsFaster from './features/hide-comments-faster';
import linkifyCommitSha from './features/linkify-commit-sha';
import hideIssueListAutocomplete from './features/hide-issue-list-autocomplete';
import userProfileFollowerBadge from './features/user-profile-follower-badge';
import usefulNotFoundPage from './features/useful-not-found-page';
import setDefaultRepositoriesTypeToSources from './features/set-default-repositories-type-to-sources';
import markPrivateOrgs from './features/mark-private-orgs';
import navigatePagesWithArrowKeys from './features/navigate-pages-with-arrow-keys';
Expand All @@ -95,10 +96,14 @@ window.select = select;
async function init() {
await safeElementReady('body');

if (pageDetect.is404() || pageDetect.is500()) {
if (pageDetect.is500()) {
return;
}

if (pageDetect.is404()) {
enableFeature(usefulNotFoundPage);
return;
}
if (document.body.classList.contains('logged-out')) {
console.warn('%cRefined GitHub%c only works when you’re logged in to GitHub.', 'font-weight: bold', '');
return;
Expand Down
49 changes: 8 additions & 41 deletions source/features/add-branch-buttons.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,10 @@ import {h} from 'dom-chef';
import select from 'select-dom';
import compareVersions from 'tiny-version-compare';
import * as icons from '../libs/icons';
import * as cache from '../libs/cache';
import {appendBefore} from '../libs/utils';
import {groupSiblings} from '../libs/group-buttons';
import {getRepoURL, isRepoRoot, getOwnerAndRepo} from '../libs/page-detect';

// This regex should match all of these combinations:
// "This branch is even with master."
// "This branch is 1 commit behind master."
// "This branch is 1 commit ahead of master."
// "This branch is 1 commit ahead, 27 commits behind master."
const branchInfoRegex = /([^ ]+)\.$/;
import getDefaultBranch from '../libs/get-default-branch';
import {getRepoURL, isRepoRoot} from '../libs/page-detect';

function getTagLink() {
const tags = select
Expand Down Expand Up @@ -45,46 +38,20 @@ function getTagLink() {
return link;
}

async function getDefaultBranchNameIfDifferent() {
const {ownerName, repoName} = getOwnerAndRepo();
const cacheKey = `default-branch:${ownerName}/${repoName}`;

// Return the cached name if it differs from the current one
const cachedName = await cache.get(cacheKey);
if (cachedName) {
const currentBranch = select('[data-hotkey="w"] span').textContent;
return cachedName === currentBranch ? false : cachedName;
}

// We can find the name in the infobar, available in folder views
const branchInfo = select('.branch-infobar');
if (!branchInfo) {
return;
}

// Parse the infobar
const [, branchName] = branchInfo.textContent.trim().match(branchInfoRegex) || [];
if (branchName) {
cache.set(cacheKey, branchName, 1);
return branchName;
}
}

async function getDefaultBranchLink() {
if (select.exists('.repohead h1 .octicon-repo-forked')) {
return; // It's a fork, no "default branch" info available #1132
}
const defaultBranch = await getDefaultBranch();
const currentBranch = select('[data-hotkey="w"] span').textContent;

const branchName = await getDefaultBranchNameIfDifferent();
if (!branchName) {
// Don't show the button if we’re already on the default branch
if (defaultBranch === currentBranch) {
return;
}

let url;
if (isRepoRoot()) {
url = `/${getRepoURL()}`;
} else {
const branchLink = select(`.select-menu-item[data-name='${branchName}']`);
const branchLink = select(`.select-menu-item[data-name='${defaultBranch}']`);
if (!branchLink) {
return;
}
Expand All @@ -98,7 +65,7 @@ async function getDefaultBranchLink() {
aria-label="Visit the default branch">
{icons.branch()}
{' '}
{branchName}
{defaultBranch}
</a>
);
}
Expand Down
104 changes: 104 additions & 0 deletions source/features/useful-not-found-page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
This feature adds more useful 404 (not found) page.
- Display the full URL clickable piece by piece
- Strikethrough all anchor that return a 404 status code
*/

import {h} from 'dom-chef';
import select from 'select-dom';
import {getCleanPathname} from '../libs/page-detect';
import getDefaultBranch from '../libs/get-default-branch';

async function is404(url) {
const {status} = await fetch(url, {method: 'head'});
return status === 404;
}

function getStrikeThrough(text) {
return <del style={{color: '#6a737d'}}>{text}</del>;
}

async function checkAnchor(anchor) {
if (await is404(anchor.href)) {
anchor.replaceWith(getStrikeThrough(anchor.textContent));
}
}

function parseCurrentURL() {
const parts = getCleanPathname().split('/');
if (parts[2] === 'blob') { // Blob URLs are never useful
parts[2] = 'tree';
}
return parts;
}

// If the resource was deleted, link to the commit history
async function addCommitHistoryLink(bar) {
const parts = parseCurrentURL();
if (parts[2] !== 'tree') {
return;
}
parts[2] = 'commits';
const url = '/' + parts.join('/');
if (await is404(url)) {
return;
}
bar.after(
<p class="container">
See also the file’s {<a href={url}>commit history</a>}
</p>
);
}

// If the resource exists in the default branch, link to it
async function addDefaultBranchLink(bar) {
const parts = getCleanPathname().split('/');
const branch = parts[3];
if (!branch) {
return;
}
const defaultBranch = await getDefaultBranch();
if (!defaultBranch || branch === defaultBranch) {
return;
}
parts[3] = defaultBranch; // Change branch
const url = '/' + parts.join('/');
if (await is404(url)) {
return;
}
bar.after(
<p class="container">
See also the file on the {<a href={url}>default branch</a>}
</p>
);
}

export default function () {
const parts = parseCurrentURL();
const bar = <h2 class="container"/>;

for (const [i, part] of parts.entries()) {
if (i === 2 && part === 'tree') {
// `/tree/` is not a real part of the URL
continue;
}
if (i === parts.length - 1) {
// The last part of the URL is a known 404
bar.append(' / ', getStrikeThrough(part));
Copy link
Member

Choose a reason for hiding this comment

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

Small nitpick if the user is not found then the slash is appended.
Is that intended?

image

} else {
const pathname = '/' + parts.slice(0, i + 1).join('/');
bar.append(i ? ' / ' : '', <a href={pathname}>{part}</a>);
}
}

// NOTE: We need to append it after the parallax_wrapper because other elements might not be available yet.
select('#parallax_wrapper').after(bar);

// Check parts from right to left; skip the last part
for (let i = bar.children.length - 2; i >= 0; i--) {
checkAnchor(bar.children[i]);
}

addCommitHistoryLink(bar);
addDefaultBranchLink(bar);
}
22 changes: 13 additions & 9 deletions source/libs/cache.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
export default async function getSet(key, getter, expiration) {
export async function getSet(key, getter, expiration) {
const cache = await get(key);
if (cache === undefined) {
const value = getter();
if (value !== undefined) {
await set(key, value, expiration);
return value;
}
if (cache !== undefined) {
return cache;
}
const value = await getter();
if (value !== undefined) {
await set(key, value, expiration);
return value;
}
}

Expand All @@ -16,7 +17,7 @@ export async function get(key) {
});

// If it's not in the cache, it's best to return "undefined"
if (value === null) {
if (value === null || value === undefined) {
return undefined;
}
return value;
Expand All @@ -41,14 +42,17 @@ if (!browser.runtime.getBackground) {
if (code === 'get-cache') {
const [cached] = document.cookie.split('; ')
.filter(item => item.startsWith(key + '='));

if (cached) {
const [, value] = cached.split('=');
sendResponse(JSON.parse(value));
console.log('CACHE: found', key, value);
} else {
sendResponse();
console.log('CACHE: not found', key);
}
} else if (code === 'set-cache') {
console.log('CACHE: setting', key, value);

// Store as JSON to preserve data type
// otherwise Booleans and Numbers become strings
document.cookie = `${key}=${JSON.stringify(value)}; max-age=${expiration ? expiration * 3600 * 24 : ''}`;
Expand Down
41 changes: 41 additions & 0 deletions source/libs/get-default-branch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import select from 'select-dom';
import * as cache from './cache';
import * as api from './api';
import {getOwnerAndRepo} from './page-detect';

// This regex should match all of these combinations:
// "This branch is even with master."
// "This branch is 1 commit behind master."
// "This branch is 1 commit ahead of master."
// "This branch is 1 commit ahead, 27 commits behind master."
const branchInfoRegex = /([^ ]+)\.$/;

function parseBranchFromDom() {
if (select.exists('.repohead h1 .octicon-repo-forked')) {
return; // It's a fork, no "default branch" info available #1132
}

// We can find the name in the infobar, available in folder views
const branchInfo = select('.branch-infobar');
if (!branchInfo) {
return;
}

// Parse the infobar
const [, branchName] = branchInfo.textContent.trim().match(branchInfoRegex) || [];
return branchName; // `string` or undefined
}

async function fetchFromApi(user, repo) {
const response = await api.v3(`repos/${user}/${repo}`);
if (response && response.default_branch) {
return response.default_branch;
}
}

export default function () {
const {ownerName, repoName} = getOwnerAndRepo();
return cache.getSet(`default-branch:${ownerName}/${repoName}`,
() => parseBranchFromDom() || fetchFromApi(ownerName, repoName)
);
}