Skip to content

Instantly share code, notes, and snippets.

@fuunnx
Last active February 21, 2025 13:34
Show Gist options
  • Save fuunnx/de64bce091596db4ddba73c6d805d2a7 to your computer and use it in GitHub Desktop.
Save fuunnx/de64bce091596db4ddba73c6d805d2a7 to your computer and use it in GitHub Desktop.
Test lolilol
// ==UserScript==
// @name nxtension-bundle
// @namespace https://nartex.fr
// @version 1.3.3
// @description copy-title-to-clipboard | issue-transfer | iteration-transfer | us_amendments
// @author Géraud Henrion, Joël Bohrer
// @match https://*.projects.nartex.fr/*/-/*
// @match https://*.support.nartex.fr/*/-/*
// @icon https://www.nartex.fr/wp-nartex/themes/nartex/favicon.png
// @grant none
// @updateURL https://gist.github.com/fuunnx/7a76571b259f9c7e05ce8ba09fe07cc7/raw/copy-title-to-clipboard.user.js
// @downloadURL https://gist.github.com/fuunnx/7a76571b259f9c7e05ce8ba09fe07cc7/raw/copy-title-to-clipboard.user.js
// ==/UserScript==
(function () {
'use strict';
// ==UserScript==
// @name nxtension-copy-title-to-clipboard
// @namespace https://projects.nartex.fr/nartex/web/nx-calendar
// @version 0.13
// @description Add a "copy title and link" button to Gitlab issues and MR
// @author Géraud Henrion
// @match https://*.projects.nartex.fr/*/-/*
// @match https://*.support.nartex.fr/*/-/*
// @icon https://www.nartex.fr/wp-nartex/themes/nartex/favicon.png
// @grant none
// @updateURL https://gist.github.com/fuunnx/7a76571b259f9c7e05ce8ba09fe07cc7/raw/copy-title-to-clipboard.user.js
// @downloadURL https://gist.github.com/fuunnx/7a76571b259f9c7e05ce8ba09fe07cc7/raw/copy-title-to-clipboard.user.js
// ==/UserScript==
(async function () {
const titleElement = document.querySelector('h1');
if (!titleElement) return
const title = titleElement.innerText.trim();
const url = window.location.href;
const buttonTitle = 'Copier le titre et le lien du ticket';
const container = document.createElement('div');
container.innerHTML = `
<button
class="gl-button btn btn-icon btn-sm btn-default btn-default-tertiary gl-display-none! gl-md-display-inline-block!"
title=${JSON.stringify(buttonTitle)}
aria-label=${JSON.stringify(buttonTitle)}
aria-live="polite"
data-toggle="tooltip"
data-placement="bottom"
data-container="body"
data-clipboard-text=${JSON.stringify(
`[${title.replaceAll('"', '&quot;')}](${url})`,
)}
type="button"
>
<svg class="s16 gl-icon gl-button-icon " data-testid="copy-to-clipboard-icon"><use href="/assets/icons-8791a66659d025e0a4c801978c79a1fbd82db1d27d85f044a35728ea7cf0ae80.svg#copy-to-clipboard"></use></svg>
</button>
`;
const button = container.children[0];
if (!button) return
titleElement?.appendChild(button);
})();
// ==UserScript==
// @name nxtension-issue-transfer
// @namespace https://projects.nartex.fr/nartex/web/nx-calendar
// @version 0.11
// @description Add a transfer button to Gitlab Support
// @author Géraud Henrion
// @match https://*.support.nartex.fr/*/-/issues/*
// @icon https://www.nartex.fr/wp-nartex/themes/nartex/favicon.png
// @grant none
// @updateURL https://gist.github.com/fuunnx/7a76571b259f9c7e05ce8ba09fe07cc7/raw/issue-transfer.user.js
// @downloadURL https://gist.github.com/fuunnx/7a76571b259f9c7e05ce8ba09fe07cc7/raw/issue-transfer.user.js
// ==/UserScript==
(async function () {
addStyle(`
.detail-page-header-actions {
flex-wrap: wrap;
justify-content: flex-end;
max-width: 50%;
}
`);
const config = {
GITLAB_PROJECTS_URL: 'https://projects.nartex.fr/nartex',
API_URL: window.location.origin + '/api/v4',
APP_DESIGN_CONFIG_URL: 'https://tests.nartex.fr/nxtension',
};
const projectId = document.body.getAttribute('data-project-id');
const issueIid = location.pathname.split('/').slice(-1)[0];
const currentIssue = await fetch(
`${config.API_URL}/projects/${projectId}/issues?iids[]=${issueIid}`,
)
.then((x) => x.json())
.then((x) => x[0]);
const projectsMap = await fetch(
`${config.APP_DESIGN_CONFIG_URL}/list.json`,
).then((res) => res.json());
let targetProjectUrl = projectsMap[projectId];
Button({
label: targetProjectUrl ? 'Transférer' : 'Transférer*',
href: targetProjectUrl ? buildHref(targetProjectUrl, currentIssue) : '#',
async onClick(event) {
if (targetProjectUrl) return
else event.preventDefault();
targetProjectUrl = window.prompt(`Quelle est l'url du projet app-design ?
Exemple :
${config.GITLAB_PROJECTS_URL}/<client>/<projet>/app-design
ATTENTION À NE PAS FAIRE D'ERREUR À LA SAISIE :)
`);
if (!targetProjectUrl) return
// TODO check url validity
const opened = window.open(
buildHref(targetProjectUrl, currentIssue),
'_blank',
);
const body = new FormData();
body.append('support_id', projectId);
body.append('url', targetProjectUrl);
await fetch(`${config.APP_DESIGN_CONFIG_URL}/nxtension.php`, {
method: 'POST',
body,
});
if (opened) {
window.location.reload();
} else {
alert("Merci d'autoriser les popups");
}
},
});
function buildHref(destUrl, issue) {
const title = `[Support] #${issue.iid} : ${issue.title}`;
const description = `
${issue.web_url} :
${issue.description
.split('\n')
.map((line) => `> ${line}`)
.join('\n')}
/label ~"Support"
`;
return `${destUrl}/-/issues/new?issue[title]=${encodeURIComponent(
title,
)}&issue[description]=${encodeURIComponent(description)}`
}
function Button(props) {
const { href, label, onClick } = props;
const editButton = document.querySelector('[data-testid="edit-button"]');
const transferButton = document.createElement('a');
transferButton.className = editButton.className;
transferButton.style.setProperty('display', 'flex', 'important');
transferButton.innerHTML = `<span class="gl-button-text">${label}</span>`;
transferButton.title = label;
transferButton.setAttribute('aria-label', label);
transferButton.onclick = onClick;
transferButton.href = href;
transferButton.target = '_blank';
editButton.parentElement.insertBefore(transferButton, editButton);
}
function addStyle(styleSheetContent = '') {
const styleTag = document.createElement('style');
styleTag.innerText = styleSheetContent;
document.head.appendChild(styleTag);
}
})();
// ==UserScript==
// @name nxtension-iteration-transfer
// @namespace https://projects.nartex.fr/nartex/web/nx-calendar
// @version 0.16
// @description Add a "transfer to current iteration" button to Gitlab
// @author Géraud Henrion
// @match https://*.projects.nartex.fr/*/-/issues/*
// @icon https://www.nartex.fr/wp-nartex/themes/nartex/favicon.png
// @grant none
// @updateURL https://gist.github.com/fuunnx/7a76571b259f9c7e05ce8ba09fe07cc7/raw/iteration-transfer.user.js
// @downloadURL https://gist.github.com/fuunnx/7a76571b259f9c7e05ce8ba09fe07cc7/raw/iteration-transfer.user.js
// ==/UserScript==
(async function () {
addStyle(`
.detail-page-header-actions {
flex-wrap: wrap;
justify-content: flex-end;
max-width: 50%;
}
`);
const context = {
projectId: document.body.getAttribute('data-project-id'),
issueIid: location.pathname.split('/').slice(-1)[0],
projectRootUrl: window.location.pathname.split('/-/')[0],
};
const commands = Commands();
const api = Api(window.location.origin + '/api/v4', context);
run();
async function run() {
let currentIssue = await api.findIssueByIid(context.issueIid);
const iterations = await api.getOpenedIterations();
const nextIteration = iterations.find(
(iteration) => new Date(iteration.start_date).getTime() > Date.now(),
);
const currentIteration = iterations.find(
(iteration) => new Date(iteration.start_date).getTime() < Date.now(),
);
const isCurrentIteration =
currentIssue.iteration?.id === currentIteration?.id;
const isNextIteration = currentIssue.iteration?.id === nextIteration?.id;
if (!isCurrentIteration && currentIteration) {
TransferButton({
targetIteration: currentIteration,
label: '⬇️ Vers itér. courante',
});
}
if (!isNextIteration && nextIteration) {
TransferButton({
targetIteration: nextIteration,
label: '⤵️ Itér. suivante',
});
}
TransferButton({
targetIteration: undefined,
addLabels: ['En attente'],
label: '⏸️ Mettre en attente',
});
function TransferButton(props) {
const { targetIteration, label, addLabels } = props;
async function runTransfer() {
// Get up to date version
currentIssue = await api.findIssueByIid(context.issueIid);
const totalSpent = currentIssue.time_stats.total_time_spent;
const remaining = currentIssue.time_stats.time_estimate - totalSpent; // seconds
const shouldClone =
totalSpent && (currentIssue.iteration || !targetIteration);
if (!shouldClone) {
await api.applyCommands(currentIssue.id, [
targetIteration && commands.setIteration(targetIteration.id),
]);
return
}
await api.applyCommands(currentIssue.id, [commands.clone()]);
await Promise.all([
await api.getLatestClone(currentIssue).then((clonedIssue) => {
return api.applyCommands(clonedIssue.id, [
commands.setEstimate(totalSpent),
commands.addTimeSpent(totalSpent),
commands.close(),
])
}),
api.applyCommands(currentIssue.id, [
commands.removeTimeSpent(),
commands.setEstimate(remaining),
targetIteration && commands.setIteration(targetIteration.id),
]),
]);
}
const button = Button({
label,
async onClick(event) {
event.preventDefault();
button.setAttribute('disabled', true);
await runTransfer();
await api.applyCommands(currentIssue.id, [
commands.addLabels(addLabels),
]);
button.removeAttribute('disabled');
if (targetIteration) {
button.parentElement.removeChild(button);
}
},
});
}
}
function Button(props) {
const { label, onClick } = props;
const editButton = document.querySelector('[data-testid="edit-button"]');
const transferButton = document.createElement('button');
transferButton.className = editButton.className;
transferButton.innerText = label;
transferButton.title = label;
transferButton.setAttribute('aria-label', label);
transferButton.onclick = onClick;
transferButton.target = '_blank';
editButton.parentElement.insertBefore(transferButton, editButton);
return transferButton
}
function Api(apiUrl, params) {
const instance = {
async findIssueByIid(iid) {
return await fetch(
`${apiUrl}/projects/${params.projectId}/issues?iids[]=${iid}`,
)
.then((x) => x.json())
.then((x) => x[0])
},
async getOpenedIterations() {
return await fetch(
`${apiUrl}/projects/${params.projectId}/iterations?state=opened`,
).then((x) => x.json())
},
async getLatestClone(issue) {
const notes = await fetch(issue._links.notes).then((res) => res.json());
// ordered by "most recent notes first"
const clonedIssueNote = notes.find((note) =>
note.body.startsWith('cloned to #'),
);
const clonedIssueIid = clonedIssueNote.body.replace('cloned to #', '');
return await instance.findIssueByIid(clonedIssueIid)
},
async applyCommands(issueId, commandsToApply) {
commandsToApply = commandsToApply.filter(Boolean);
if (!commandsToApply.length) return
await fetch(
`${params.projectRootUrl}/notes?target_id=${issueId}&target_type=issue`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': getCsrf(),
},
body: JSON.stringify({
note: {
confidential: false,
note: commandsToApply.join('\n'),
noteable_id: issueId,
noteable_type: 'issue',
},
}),
},
);
function getCsrf() {
return document
.querySelector('meta[name=csrf-token]')
?.getAttribute('content')
}
},
};
return instance
}
function Commands() {
return {
setIteration(iterationId) {
if (!iterationId) return
return `/iteration *iteration:${iterationId}`
},
close() {
return '/close'
},
clone() {
return '/clone'
},
setEstimate(estimationInSeconds) {
if (estimationInSeconds > 0) {
return `/estimate ${estimationInSeconds / 60}m`
}
return '/remove_estimate'
},
addTimeSpent(estimationInSeconds) {
if (!estimationInSeconds) return
return `/spend ${estimationInSeconds / 60}m`
},
removeTimeSpent() {
return '/remove_time_spent'
},
addLabels(labels = []) {
if (!labels?.length) return
return `/label ${labels.map((l) => `~${JSON.stringify(l)}`)}`
},
}
}
function addStyle(styleSheetContent = '') {
const styleTag = document.createElement('style');
styleTag.innerText = styleSheetContent;
document.head.appendChild(styleTag);
}
})();
// ==UserScript==
// @name New Userscript
// @namespace http://tampermonkey.net/
// @version 2024-10-09
// @description try to take over the world!
// @author You
// @match https://projects.nartex.fr/nartex/*/*/app-design/-/wikis/us/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=freecodecamp.org
// @grant none
// ==/UserScript==
async function check() {
const links = document.querySelectorAll('a.gfm-issue');
links.forEach(async (l) => {
const project_id = parseInt(l.getAttribute('data-project'), 10);
const response = await fetch(
`https://projects.nartex.fr/api/v4/projects/${project_id}/issues/${l.getAttribute(
'data-iid',
)}/links`,
);
const linked_issues = await response.json();
const amendments = linked_issues
.filter((i) => i.project_id === project_id)
.map((i) => {
return { iid: i.iid, title: i.title, url: i.web_url }
});
if (amendments.length) {
const p = document.createElement('p');
p.innerHTML = `<b>ATTENTION : les tickets suivants peuvent modifier ces spécifications :</b><ol>${amendments
.map(
(t) => `<li><a href="${t.url}" target="_blank">${t.title}</a></li>`,
)
.join('\n')}</ol>`;
l.parentElement.after(p);
}
});
}
(function () {
setTimeout(check, 3000);
})();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment