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
4 changes: 2 additions & 2 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
args: zip -qq -r release.zip dist

- name: Upload zip with production-ready files
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: production-files
path: ./release.zip
Expand All @@ -40,7 +40,7 @@ jobs:

steps:
- name: Download production-ready files
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: production-files

Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ The script available here swaps usernames with preferred names from profiles ex.
## Instalation

1. Install https://www.tampermonkey.net/ - It is a very popular browser extension that allows you to add custom scripts to selected domains.
- In our case, you will add a script to github.com.
- You can check the code if you are worried about security: it doesn't touch tokens at all.
- Manifest3 requires enabling development mode (as described on the page). Alternatively, you can use the Manifest2 version, which will work faster (M3 only trusts extensions with predefined scripts, but TM by design, allows any type of scripts to be added)

- In our case, you will add a script to github.com.
- You can check the code if you are worried about security: it doesn't touch tokens at all.
- Manifest3 requires enabling development mode (as described on the page). Alternatively, you can use the Manifest2 version, which will work faster (M3 only trusts extensions with predefined scripts, but TM by design, allows any type of scripts to be added)

2. Go to https://deykun.github.io/github-usernames/github-usernames.user.js

![Instalation demo](docs/demo-install.gif)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "github-usernames",
"private": true,
"version": "1.0.0",
"version": "1.1.0",
"type": "module",
"author": "Szymon Tondowski",
"license": "MIT",
Expand Down
2 changes: 2 additions & 0 deletions processes/dev-script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ console.log('');
console.log(chalk.green('UserScript endpoints are live!'));
console.log(' - http://localhost:1234/server.user-script.js');
console.log('');
console.log('(An updated version of the script will be downloaded when you check for updates in Tampermonkey)');
console.log('');

userScriptApp.get('/server.user-script.js', (req, res) => {
const devScript = fs.readFileSync('src/user-script/dev.user-srcipt.js', 'utf-8');
Expand Down
143 changes: 133 additions & 10 deletions public/github-usernames.user.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// @description Replace ambiguous usernames with actual names from user profiles.
// @namespace deykun
// @author deykun
// @version 1.0.0
// @version 1.1.0
// @include https://github.com*
// @grant none
// @run-at document-start
Expand Down Expand Up @@ -31,7 +31,7 @@ const getUsersByUsernamesFromLS = () => getFromLocalStorage('u2n-users');
const getCustomNamesByUsernamesFromLS = () => getFromLocalStorage('u2n-users-names');

window.U2N = {
version: '1.0.0',
version: '1.1.0',
isDevMode: false,
cache: {
HTML: {},
Expand Down Expand Up @@ -224,6 +224,10 @@ const resetUsers = () => {
});
};

const getIsSavedUser = (username) => {
return Boolean(username && window.U2N.usersByUsernames?.[username]);
};

const appendCSS = (styles, { sourceName = '' } = {}) => {
const appendOnceSelector = sourceName ? `g-u2n-css-${sourceName}`.trim() : undefined;
if (appendOnceSelector) {
Expand Down Expand Up @@ -298,6 +302,22 @@ const nestedSelectors = (selectors, subcontents) => {

const upperCaseFirstLetter = (text) => (typeof text === 'string' ? text.charAt(0).toUpperCase() + text.slice(1) : '');

const joinWithAnd = (items) => {
if (items.length === 0) {
return '';
}
if (items.length === 1) {
return items[0];
}
if (items.length === 2) {
return `${items[0]} and ${items[1]}`;
}

const allButLast = items.slice(0, -1).join(', ');
const last = items[items.length - 1];
return `${allButLast} and ${last}`;
};

const getShouldUseUsernameAsDisplayname = (username) => {
const {
shouldFilterBySubstring,
Expand Down Expand Up @@ -1040,8 +1060,10 @@ const renderStatus = () => {
</span>`, 'u2n-status');
};

const getUserElements = () => {
const links = Array.from(document.querySelectorAll('[data-hovercard-url^="/users/"]')).map((el) => {
const dataU2NSource = 'data-u2n-source';

const getUserElements = () => {
const hovercardUrls = Array.from(document.querySelectorAll('[data-hovercard-url^="/users/"]')).map((el) => {
const username = el.getAttribute('data-hovercard-url').match(/users\/([A-Za-z0-9_-]+)\//)[1];

if (username && el.textContent.includes(username)) {
Expand All @@ -1054,7 +1076,64 @@ const renderStatus = () => {
return undefined;
}).filter(Boolean);

return links;
const kanbanListItems = Array.from(document.querySelectorAll('[class*="slicer-items-module__title"]')).map((el) => {
const username = el.getAttribute(dataU2NSource) || el.textContent.trim();

const isSavedUser = getIsSavedUser(username);
if (isSavedUser) {
return {
el,
username,
};
}

return undefined;
}).filter(Boolean);

const tooltipsItems = Array.from(document.querySelectorAll('[data-visible-text]')).map((el) => {
const username = el.getAttribute(dataU2NSource) || el.getAttribute('data-visible-text').trim();

const isSavedUser = getIsSavedUser(username);
if (isSavedUser) {
return {
el,
username,
updateAttributeInstead: 'data-visible-text',
};
}

return undefined;
}).filter(Boolean);

return [
...hovercardUrls,
...kanbanListItems,
...tooltipsItems,
];
};

const getGroupedUserElements = () => {
/* Example page https://github.com/orgs/input-output-hk/projects/102/ */
const projectsCellItems = Array.from(document.querySelectorAll('[role="gridcell"]:has([data-component="Avatar"] + span, [data-avatar-count] + span)')).map((el) => {
const source = el.getAttribute(dataU2NSource) || el.textContent.trim() || '';
const usernames = source.replace(' and ', ', ').split(', ').filter(Boolean);

const hasSavedUsername = (usernames?.length || 0) > 0 && usernames.some(getIsSavedUser);

if (hasSavedUsername) {
return {
el,
usernames,
source,
};
}

return undefined;
}).filter(Boolean);

return [
...projectsCellItems,
];
};

appendCSS(`
Expand Down Expand Up @@ -1087,10 +1166,16 @@ appendCSS(`
}

[data-u2n-cache-user] {
display: inline-block;
display: inline-flex;
justify-content: start;
vertical-align: middle;
font-size: 0;
text-overflow: unset !important;
}

[data-u2n-cache-user] [class*="ActionList-ActionListSubContent"] {
display: none;
}

.user-mention[data-u2n-cache-user] {
background-color: transparent !important;
Expand Down Expand Up @@ -1148,6 +1233,7 @@ appendCSS(`
${nestedSelectors([
'.gh-header', // pr header on pr site
'.u2n-nav-user-preview', // preview in user tab
'[data-testid="list-row-repo-name-and-number"]', // prs in repo
'[data-issue-and-pr-hovercards-enabled] [id*="issue_"]', // prs in repo
'[data-issue-and-pr-hovercards-enabled] [id*="check_"]', // actions in repo
'.timeline-comment-header', // comments headers
Expand All @@ -1159,7 +1245,6 @@ appendCSS(`
`, { sourceName: 'render-users' });

const renderUsers = () => {
const elements = getUserElements();
const {
color,
shouldShowAvatars,
Expand All @@ -1170,19 +1255,30 @@ const renderUsers = () => {
document.body.setAttribute('data-u2n-color', color);
}

elements.forEach(({ el, username }) => {
const userElements = getUserElements();

userElements.forEach(({ el, username: usernameFromElement, updateAttributeInstead }) => {
const username = usernameFromElement;
const user = window.U2N.usersByUsernames?.[username];
const displayName = getDisplayNameByUsername(username);
const previousCacheValue = el.getAttribute('data-u2n-cache-user') || '';

const cacheValue = `${displayName}${user ? '+u' : '-u'}${shouldShowAvatars ? '+a' : '-a'}`;
const cacheValue = `${username}|${displayName}${user ? '+u' : '-u'}${shouldShowAvatars ? '+a' : '-a'}`;

const isAlreadySet = el.getAttribute('data-u2n-cache-user') === cacheValue;
const isAlreadySet = previousCacheValue === cacheValue;
if (isAlreadySet) {
return;
}

el.setAttribute(dataU2NSource, username);
el.setAttribute('data-u2n-cache-user', cacheValue);

if (updateAttributeInstead) {
el.setAttribute(updateAttributeInstead, displayName);

return;
}

el.querySelector('.u2n-tags-holder')?.remove();

const tagsHolderEl = document.createElement('span');
Expand All @@ -1205,6 +1301,33 @@ const renderUsers = () => {

el.append(tagsHolderEl);
});

const groupedUsersElements = getGroupedUserElements();

groupedUsersElements.forEach(({ el, usernames: usernamesFromElement, source }) => {
const hasSavedUsername = usernamesFromElement.some(getIsSavedUser);

if (!hasSavedUsername) {
return;
}

const displayNames = usernamesFromElement.map((username) => getDisplayNameByUsername(username));
const displayNamesString = joinWithAnd(displayNames);

const previousCacheValue = el.getAttribute('data-u2n-cache-user') || '';

const cacheValue = `${source}|${displayNamesString}${shouldShowAvatars ? '+a' : '-a'}`;

const isAlreadySet = previousCacheValue === cacheValue;
if (isAlreadySet) {
return;
}

el.setAttribute(dataU2NSource, source);
el.setAttribute('data-u2n-cache-user', cacheValue);

Array.from(el.querySelectorAll('span')).at(-1).textContent = displayNamesString;
});
};

const getUserFromUserPageIfPossible = () => {
Expand Down
4 changes: 4 additions & 0 deletions src/user-script/parts/db.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,7 @@ const resetUsers = () => {
text: "The users' data were removed.",
});
};

const getIsSavedUser = (username) => {
return Boolean(username && window.U2N.usersByUsernames?.[username]);
};
16 changes: 16 additions & 0 deletions src/user-script/parts/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,22 @@ const debounce = (fn, time) => {

export const upperCaseFirstLetter = (text) => (typeof text === 'string' ? text.charAt(0).toUpperCase() + text.slice(1) : '');

export const joinWithAnd = (items) => {
if (items.length === 0) {
return '';
}
if (items.length === 1) {
return items[0];
}
if (items.length === 2) {
return `${items[0]} and ${items[1]}`;
}

const allButLast = items.slice(0, -1).join(', ');
const last = items[items.length - 1];
return `${allButLast} and ${last}`;
};

export const getShouldUseUsernameAsDisplayname = (username) => {
const {
shouldFilterBySubstring,
Expand Down
Loading