// ==UserScript==
// @name Github Open files in Editor
// @namespace http://tampermonkey.net/GithubOpenFilesInEditor
// @version 0.2
// @description Adds a button next to files on Github to quickly open it in your favorite IDE
// @author Alexandre Blanc
// @match https://github.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=undefined.localhost
// @grant none
// @license MIT
// ==/UserScript==
// For PhpStorm users, it requires plugin : IDE Remote Control
// For VSCode users on Mac OS, please add in your terminal:
// defaults write com.google.Chrome URLAllowlist -array "vscode://*"
(function() {
'use strict';
let userSettings = {projectList: {}, editor: ''};
let currentUrl = '';
let project = '';
const loadSettings = function() {
if (!window.localStorage.getItem('ghofieSettings')) {
return {projectList: {}, editor: 'phpstorm'};
}
userSettings = JSON.parse(window.localStorage.getItem('ghofieSettings'));
}
const saveSettings = function() {
window.localStorage.setItem('ghofieSettings', JSON.stringify(userSettings))
removeLinks();
setLinks();
}
const handleUrlChange = function() {
if (window.location.href === currentUrl) {
return;
}
window.setTimeout(initSettingsPanel, 500);
removeLinks();
setLinks();
currentUrl = window.location.href;
}
const initUrlChangeDetection = function() {
setInterval(function() {
handleUrlChange();
},1000);
}
const icon = function (color) {
return `<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" x="0px" y="0px" viewBox="0 0 1000 1000" xml:space="preserve">
<g>
<g transform="translate(0.000000,511.000000) scale(0.100000,-0.100000)" fill="red">
<path d="M267.3,4393.3c-47.8-23.9-100.4-76.5-124.3-124.3c-40.6-78.9-43-327.5-43-4159s2.4-4080.2,43-4159c23.9-47.8,76.5-100.4,124.3-124.3c78.9-40.6,353.8-43,4732.7-43c4378.9,0,4653.8,2.4,4732.7,43c47.8,23.9,100.4,76.5,124.3,124.3c40.6,78.9,43,327.5,43,4159s-2.4,4080.2-43,4159c-23.9,47.8-76.5,100.4-124.3,124.3c-78.9,40.6-353.8,43-4732.7,43C621.1,4436.3,346.2,4433.9,267.3,4393.3z M9314.4,2990.2v-860.5H5000H685.6l-7.2,836.6c-2.4,458.9,0,850.9,7.2,865.3c7.2,26.3,874.8,31.1,4319.2,26.3l4309.6-7.2V2990.2z M9321.6-1042.1l-7.2-2588.6H5000H685.6l-7.2,2588.6l-4.8,2586.2H5000h4326.3L9321.6-1042.1z"/>
<path d="M8284.2,3140.8l-45.4-55l55,45.4c28.7,26.3,52.6,50.2,52.6,55C8346.3,3205.4,8327.2,3191,8284.2,3140.8z"/>
<path d="M5425.5,927.5c-38.2-21.5-81.3-71.7-100.4-112.3c-16.7-38.2-294-855.7-611.9-1811.8c-621.5-1869.2-614.3-1845.3-499.6-1967.2c112.3-119.5,329.8-117.1,425.5,4.8c55,71.7,1221.4,3554.3,1221.4,3649.9C5860.5,905.9,5609.5,1044.6,5425.5,927.5z"/>
<path d="M2248.8-227c-894-600-944.1-635.8-975.2-729c-31.1-88.4-28.7-105.2,11.9-191.2c40.6-81.3,162.5-172.1,965.7-707.5c800.7-537.8,932.2-616.7,1011.1-616.7c124.3,0,217.5,52.6,265.3,153c47.8,100.4,47.8,136.2,4.8,241.4c-28.7,66.9-148.2,157.8-741,552.1c-389.6,260.5-707.5,478-707.5,485.2c0,7.2,315.5,224.7,700.3,480.4c595.2,399.2,705.1,480.4,743.4,556.9c38.2,78.9,40.6,107.6,16.7,181.7c-35.9,124.3-121.9,196-248.6,210.3C3193,401.6,3181,394.4,2248.8-227z"/>
<path d="M6601.5,365.8c-76.5-38.2-167.3-174.5-167.3-253.4c0-138.6,78.9-207.9,786.4-676.4c382.4-253.4,695.5-468.5,695.5-475.6c0-7.2-317.9-224.7-707.5-485.2c-592.8-394.4-712.3-485.2-741-552.1c-43-105.2-43-141,4.8-241.4c47.8-100.4,141-153,265.3-153c78.9,0,210.3,78.9,1011.1,616.7c1032.6,688.4,1039.8,695.6,975.2,896.3c-31.1,98-57.4,117.1-970.4,729C6766.4,427.9,6747.3,439.8,6601.5,365.8z"/>
</g>
</g>
</svg>`
};
document.phpStormLink = function (link) {
const hrefLink = `http://localhost:63342/api/file?file=${link}`;
const linkWindow = window.open(hrefLink,'autoOpenInEditor');
setTimeout(function() {
linkWindow.close();
},1000);
};
document.vsCodeLink = function (link) {
const hrefLink = `vscode://file/${link}`;
const linkWindow = window.open(hrefLink,'autoOpenInEditor');
setTimeout(function() {
linkWindow.close();
},1000);
};
const setLinks = function() {
const tabnavs = document.querySelectorAll('.tabnav [data-pjax="#repo-content-pjax-container"] .tabnav-tab');
if (tabnavs.length === 0) {
return;
}
project = document.querySelector('.AppHeader-context-full ul li:last-child a').href.split('/').pop();
switch (Array.from(document.querySelectorAll('.tabnav-tabs[aria-label="Pull request tabs"] .tabnav-tab')).findIndex(el => el.classList.contains("selected"))) {
case 0: console.log('setLinksInConversationTab'); setLinksInConversationTab(); break;
case 1: console.log('setLinksInCommitsTab'); setLinksInCommitsTab(); break;
case 3: console.log('setLinksInFilesChangedTab'); setLinksInFilesChangedTab(); break;
}
}
const handleClickOnOpenInEditorLink = function (link) {
const projectPath = userSettings.projectList[project] || '';
switch (userSettings.editor) {
case 'phpstorm': document.phpStormLink(projectPath + link); break;
case 'vscode': document.vsCodeLink(projectPath + link); break;
}
}
const setLinksInConversationTab = function() {
let linkCounter = 0;
document.querySelectorAll('turbo-frame[id^="review-thread-or-comment-id-"] summary a').forEach(function (a) {
linkCounter++;
const fileUrl = a.innerHTML;
const ghofieLinkId = `js-open-in-phpstorm-${linkCounter}`;
a.insertAdjacentHTML('afterend', `
<div
id="${ghofieLinkId}"
title="Open file in editor"
data-link="${fileUrl}"
class="ghofie-link"
style="margin: 2px 5px 0px 0px"
>
${icon()}
</div>
`);
document.getElementById(ghofieLinkId).addEventListener('click',function(e){
e.preventDefault();
handleClickOnOpenInEditorLink(e.currentTarget.dataset.link);
});
});
}
const setLinksInCommitsTab = function() {
let linkCounter = 0;
document.querySelectorAll(
'diff-layout #files div.js-file div.file-header span.Truncate clipboard-copy'
).forEach(function (e) {
linkCounter++;
const fileUrl = e.value;
const ghofieLinkId = `js-open-in-phpstorm-${linkCounter}`;
e.insertAdjacentHTML('afterend', `
<div
id="${ghofieLinkId}"
title="Open file in editor"
data-link="${fileUrl}"
class="ghofie-link"
style="margin: 2px 5px 0px 0px; cursor: pointer;"
>
${icon()}
</div>
`);
document.getElementById(ghofieLinkId).addEventListener('click',function(e){
e.preventDefault();
handleClickOnOpenInEditorLink(e.currentTarget.dataset.link);
});
});
}
const setLinksInFilesChangedTab = function() {
let linkCounter = 0;
document.querySelectorAll(
'div[data-target="diff-layout.mainContainer"] #files div.js-file div.file-header span.Truncate clipboard-copy'
).forEach(function (e) {
linkCounter++;
const fileUrl = e.value;
const ghofieLinkId = `js-open-in-phpstorm-${linkCounter}`;
e.insertAdjacentHTML('afterend', `
<div
id="${ghofieLinkId}"
title="Open file in editor"
data-link="${fileUrl}"
class="ghofie-link"
style="margin: 2px 5px 0px 0px; cursor: pointer;"
>
${icon()}
</div>
`);
document.getElementById(ghofieLinkId).addEventListener('click',function(e){
e.preventDefault();
handleClickOnOpenInEditorLink(e.currentTarget.dataset.link);
});
});
}
const removeLinks = function() {
document.querySelectorAll('.ghofie-link').forEach(function (v) {
v.parentElement.removeChild(v);
});
}
const isConfigurationValid = function() {
try {
JSON.parse(document.getElementById("ghofie-project-list").value);
return true;
} catch (e) {
return false;
}
};
const toggleSettingsDisplay = function() {
const element = document.getElementById('ghofie-settings');
element.style.display = element.style.display == 'none' ? 'inline-block' : 'none'
if (element.style.display == 'inline-block') {
document.getElementById("ghofie-project-list").value = JSON.stringify(userSettings.projectList);
document.getElementById('ghofie-editor-choice-' + userSettings.editor).checked = true;
}
};
const initSettingsPanel = function() {
if (document.querySelector('#ghofie-settings') !== null) {
document.querySelector('#ghofie-settings').remove();
document.querySelector('#js-btn-ghofie-settings').remove();
}
document.querySelector('.AppHeader-globalBar').insertAdjacentHTML('afterend', `
<div class="row px-3 px-md-4 px-lg-5" id="ghofie-settings" style="display:none;width:100%;">
<div class="form-group">
<h3>GitHub - Open file in editor</h3>
<label class="label-bold" for="ghofie-project-list">
Project association <i>{"${project}":"/home/src/localpath/"}</i>
</label>
<textarea class="form-control" rows="3" maxlength="650" id="ghofie-project-list" name="ghofie-settings"></textarea>
<label class="label-bold">Editor choice</label>
<div class="form-check">
<input type="radio" class="form-check-input" id="ghofie-editor-choice-phpstorm" name="ghofie-editor-choice" value="phpstorm">
<label for="ghofie-editor-choice-phpstorm" class="form-check-label">PhpStorm</label>
</div>
<div class="form-check">
<input type="radio" class="form-check-input" id="ghofie-editor-choice-vscode" name="ghofie-editor-choice" value="vscode">
<label for="ghofie-editor-choice-vscode" class="form-check-label">VS Code</label>
</div>
<button
id="ghofie-settings-save-button"
class="btn btn-secondary"
>
Save Settings
</button>
</div>
</div>
`);
let ghofieSettingsButton = document.createElement('a');
ghofieSettingsButton.addEventListener('click', function (e) {
e.preventDefault();
toggleSettingsDisplay();
return false;
});
ghofieSettingsButton.id = 'js-btn-ghofie-settings';
ghofieSettingsButton.href = '#';
ghofieSettingsButton.innerHTML = `${icon('#919191')}`;
ghofieSettingsButton.classList.add('AppHeader-button');
ghofieSettingsButton.classList.add('Button--secondary');
document.querySelector('.AppHeader-globalBar-end notification-indicator').insertAdjacentElement('afterend', ghofieSettingsButton);
document.getElementById('ghofie-settings-save-button').addEventListener(
'click',
function (e) {
e.preventDefault();
if (isConfigurationValid()) {
toggleSettingsDisplay();
userSettings.projectList = JSON.parse(document.getElementById("ghofie-project-list").value);
userSettings.editor = document.querySelector('input[name="ghofie-editor-choice"]:checked').value;
saveSettings();
}
return false;
}
);
};
loadSettings();
initUrlChangeDetection();
})();