From df67b87e80a6703975354ea2081e3a018b62c688 Mon Sep 17 00:00:00 2001 From: Szymon Tondowski Date: Wed, 16 Apr 2025 22:33:15 +0200 Subject: [PATCH 1/7] Kanban names update --- README.md | 8 +++-- public/github-usernames.user.js | 44 ++++++++++++++++++++++----- src/user-script/parts/render-users.js | 44 ++++++++++++++++++++++----- 3 files changed, 79 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index c6d9743..47942c7 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/public/github-usernames.user.js b/public/github-usernames.user.js index 6cc8e66..fd7c799 100644 --- a/public/github-usernames.user.js +++ b/public/github-usernames.user.js @@ -1041,7 +1041,7 @@ const renderStatus = () => { }; const getUserElements = () => { - const links = Array.from(document.querySelectorAll('[data-hovercard-url^="/users/"]')).map((el) => { + 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)) { @@ -1054,7 +1054,24 @@ const renderStatus = () => { return undefined; }).filter(Boolean); - return links; + const kanbanListItems = Array.from(document.querySelectorAll('[class*="slicer-items-module__title"]')).map((el) => { + const username = el.textContent.trim(); + + if (username) { + return { + el, + username, + shouldSkipIfNotCached: true, + }; + } + + return undefined; + }).filter(Boolean); + + return [ + ...hovercardUrls, + ...kanbanListItems, + ]; }; appendCSS(` @@ -1170,13 +1187,26 @@ const renderUsers = () => { document.body.setAttribute('data-u2n-color', color); } - elements.forEach(({ el, username }) => { - const user = window.U2N.usersByUsernames?.[username]; - const displayName = getDisplayNameByUsername(username); + elements.forEach(({ el, username: usernameFromElement, shouldSkipIfNotCached }) => { + let username = usernameFromElement; + let user = window.U2N.usersByUsernames?.[username]; + let displayName = getDisplayNameByUsername(username); + const previousCacheValue = el.getAttribute('data-u2n-cache-user') || ''; + + if (!user && shouldSkipIfNotCached) { + const previousUsername = previousCacheValue.split('|')[0]; + username = previousUsername; + user = window.U2N.usersByUsernames?.[username]; + displayName = getDisplayNameByUsername(username); + + if (!user) { + return; + } + } - 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; } diff --git a/src/user-script/parts/render-users.js b/src/user-script/parts/render-users.js index 809cecc..f171edc 100644 --- a/src/user-script/parts/render-users.js +++ b/src/user-script/parts/render-users.js @@ -1,5 +1,5 @@ const getUserElements = () => { - const links = Array.from(document.querySelectorAll('[data-hovercard-url^="/users/"]')).map((el) => { + 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)) { @@ -12,7 +12,24 @@ const getUserElements = () => { return undefined; }).filter(Boolean); - return links; + const kanbanListItems = Array.from(document.querySelectorAll('[class*="slicer-items-module__title"]')).map((el) => { + const username = el.textContent.trim(); + + if (username) { + return { + el, + username, + shouldSkipIfNotCached: true, + }; + } + + return undefined; + }).filter(Boolean); + + return [ + ...hovercardUrls, + ...kanbanListItems, + ]; }; appendCSS(` @@ -128,13 +145,26 @@ export const renderUsers = () => { document.body.setAttribute('data-u2n-color', color); } - elements.forEach(({ el, username }) => { - const user = window.U2N.usersByUsernames?.[username]; - const displayName = getDisplayNameByUsername(username); + elements.forEach(({ el, username: usernameFromElement, shouldSkipIfNotCached }) => { + let username = usernameFromElement; + let user = window.U2N.usersByUsernames?.[username]; + let displayName = getDisplayNameByUsername(username); + const previousCacheValue = el.getAttribute('data-u2n-cache-user') || ''; + + if (!user && shouldSkipIfNotCached) { + const previousUsername = previousCacheValue.split('|')[0]; + username = previousUsername; + user = window.U2N.usersByUsernames?.[username]; + displayName = getDisplayNameByUsername(username); + + if (!user) { + return; + } + } - 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; } From f3ecb57068ef49771b6c683d72f819fdb899c1e7 Mon Sep 17 00:00:00 2001 From: Szymon Tondowski Date: Sat, 19 Apr 2025 10:10:08 +0200 Subject: [PATCH 2/7] Better logic for projects & tooltips with usernames shows correct displayNames --- public/github-usernames.user.js | 50 +++++++++++++++++---------- src/user-script/parts/render-users.js | 50 +++++++++++++++++---------- 2 files changed, 64 insertions(+), 36 deletions(-) diff --git a/public/github-usernames.user.js b/public/github-usernames.user.js index fd7c799..22f4c49 100644 --- a/public/github-usernames.user.js +++ b/public/github-usernames.user.js @@ -1055,13 +1055,30 @@ const renderStatus = () => { }).filter(Boolean); const kanbanListItems = Array.from(document.querySelectorAll('[class*="slicer-items-module__title"]')).map((el) => { - const username = el.textContent.trim(); + const username = el.getAttribute('data-u2n-username') || el.textContent.trim(); - if (username) { + const isSavedUsername = Boolean(username && window.U2N.usersByUsernames?.[username]); + + if (isSavedUsername) { + return { + el, + username, + }; + } + + return undefined; + }).filter(Boolean); + + const tooltipsItems = Array.from(document.querySelectorAll('[data-visible-text]')).map((el) => { + const username = el.getAttribute('data-u2n-username') || el.getAttribute('data-visible-text').trim(); + + const isSavedUsername = Boolean(username && window.U2N.usersByUsernames?.[username]); + + if (isSavedUsername) { return { el, username, - shouldSkipIfNotCached: true, + updateAttributeInstead: 'data-visible-text', }; } @@ -1071,6 +1088,7 @@ const renderStatus = () => { return [ ...hovercardUrls, ...kanbanListItems, + ...tooltipsItems, ]; }; @@ -1187,23 +1205,12 @@ const renderUsers = () => { document.body.setAttribute('data-u2n-color', color); } - elements.forEach(({ el, username: usernameFromElement, shouldSkipIfNotCached }) => { - let username = usernameFromElement; - let user = window.U2N.usersByUsernames?.[username]; - let displayName = getDisplayNameByUsername(username); + elements.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') || ''; - if (!user && shouldSkipIfNotCached) { - const previousUsername = previousCacheValue.split('|')[0]; - username = previousUsername; - user = window.U2N.usersByUsernames?.[username]; - displayName = getDisplayNameByUsername(username); - - if (!user) { - return; - } - } - const cacheValue = `${username}|${displayName}${user ? '+u' : '-u'}${shouldShowAvatars ? '+a' : '-a'}`; const isAlreadySet = previousCacheValue === cacheValue; @@ -1211,8 +1218,15 @@ const renderUsers = () => { return; } + el.setAttribute('data-u2n-username', 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'); diff --git a/src/user-script/parts/render-users.js b/src/user-script/parts/render-users.js index f171edc..f2310de 100644 --- a/src/user-script/parts/render-users.js +++ b/src/user-script/parts/render-users.js @@ -13,13 +13,30 @@ const getUserElements = () => { }).filter(Boolean); const kanbanListItems = Array.from(document.querySelectorAll('[class*="slicer-items-module__title"]')).map((el) => { - const username = el.textContent.trim(); + const username = el.getAttribute('data-u2n-username') || el.textContent.trim(); - if (username) { + const isSavedUsername = Boolean(username && window.U2N.usersByUsernames?.[username]); + + if (isSavedUsername) { return { el, username, - shouldSkipIfNotCached: true, + }; + } + + return undefined; + }).filter(Boolean); + + const tooltipsItems = Array.from(document.querySelectorAll('[data-visible-text]')).map((el) => { + const username = el.getAttribute('data-u2n-username') || el.getAttribute('data-visible-text').trim(); + + const isSavedUsername = Boolean(username && window.U2N.usersByUsernames?.[username]); + + if (isSavedUsername) { + return { + el, + username, + updateAttributeInstead: 'data-visible-text', }; } @@ -29,6 +46,7 @@ const getUserElements = () => { return [ ...hovercardUrls, ...kanbanListItems, + ...tooltipsItems, ]; }; @@ -145,23 +163,12 @@ export const renderUsers = () => { document.body.setAttribute('data-u2n-color', color); } - elements.forEach(({ el, username: usernameFromElement, shouldSkipIfNotCached }) => { - let username = usernameFromElement; - let user = window.U2N.usersByUsernames?.[username]; - let displayName = getDisplayNameByUsername(username); + elements.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') || ''; - if (!user && shouldSkipIfNotCached) { - const previousUsername = previousCacheValue.split('|')[0]; - username = previousUsername; - user = window.U2N.usersByUsernames?.[username]; - displayName = getDisplayNameByUsername(username); - - if (!user) { - return; - } - } - const cacheValue = `${username}|${displayName}${user ? '+u' : '-u'}${shouldShowAvatars ? '+a' : '-a'}`; const isAlreadySet = previousCacheValue === cacheValue; @@ -169,8 +176,15 @@ export const renderUsers = () => { return; } + el.setAttribute('data-u2n-username', 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'); From 5c5173762cb7c36399ee81ae4e650b425807b5b5 Mon Sep 17 00:00:00 2001 From: Szymon Tondowski Date: Sat, 19 Apr 2025 11:37:08 +0200 Subject: [PATCH 3/7] Grabbing usernames from cells with more than one user --- processes/dev-script.ts | 2 ++ public/github-usernames.user.js | 42 +++++++++++++++++++++++---- src/user-script/parts/db.js | 4 +++ src/user-script/parts/render-users.js | 36 ++++++++++++++++++++--- 4 files changed, 75 insertions(+), 9 deletions(-) diff --git a/processes/dev-script.ts b/processes/dev-script.ts index e31c8c2..8ec0052 100644 --- a/processes/dev-script.ts +++ b/processes/dev-script.ts @@ -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'); diff --git a/public/github-usernames.user.js b/public/github-usernames.user.js index 22f4c49..ebaac7f 100644 --- a/public/github-usernames.user.js +++ b/public/github-usernames.user.js @@ -224,6 +224,10 @@ const resetUsers = () => { }); }; +const isSavedUser = (username) => { + return Boolean(username && window.U2N.usersByUsernames?.[username]); +}; + const appendCSS = (styles, { sourceName = '' } = {}) => { const appendOnceSelector = sourceName ? `g-u2n-css-${sourceName}`.trim() : undefined; if (appendOnceSelector) { @@ -1040,7 +1044,9 @@ const renderStatus = () => { `, 'u2n-status'); }; - const getUserElements = () => { + + +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]; @@ -1057,7 +1063,7 @@ const renderStatus = () => { const kanbanListItems = Array.from(document.querySelectorAll('[class*="slicer-items-module__title"]')).map((el) => { const username = el.getAttribute('data-u2n-username') || el.textContent.trim(); - const isSavedUsername = Boolean(username && window.U2N.usersByUsernames?.[username]); + const isSavedUsername = isSavedUser(username); if (isSavedUsername) { return { @@ -1072,7 +1078,7 @@ const renderStatus = () => { const tooltipsItems = Array.from(document.querySelectorAll('[data-visible-text]')).map((el) => { const username = el.getAttribute('data-u2n-username') || el.getAttribute('data-visible-text').trim(); - const isSavedUsername = Boolean(username && window.U2N.usersByUsernames?.[username]); + const isSavedUsername = isSavedUser(username); if (isSavedUsername) { return { @@ -1092,6 +1098,28 @@ const renderStatus = () => { ]; }; +const getGroupedUserElements = () => { + /* Example page https://github.com/orgs/input-output-hk/projects/102/views/1 */ + const projectsCellItems = Array.from(document.querySelectorAll('[role="gridcell"]:has([data-component="Avatar"] + span, [data-avatar-count] + span)')).map((el) => { + const usernames = el.textContent.trim().replace(' and ', ', ').split(', ').filter(Boolean); + + const hasSavedUsername = usernames.some(isSavedUser); + + if (hasSavedUsername) { + return { + el, + usernames, + }; + } + + return undefined; + }).filter(Boolean); + + return [ + ...projectsCellItems, + ]; +}; + appendCSS(` :root { --u2n-user-text: #00293e; @@ -1194,7 +1222,7 @@ appendCSS(` `, { sourceName: 'render-users' }); const renderUsers = () => { - const elements = getUserElements(); + const { color, shouldShowAvatars, @@ -1205,7 +1233,9 @@ const renderUsers = () => { document.body.setAttribute('data-u2n-color', color); } - elements.forEach(({ el, username: usernameFromElement, updateAttributeInstead }) => { + const userElements = getUserElements(); + + userElements.forEach(({ el, username: usernameFromElement, updateAttributeInstead }) => { const username = usernameFromElement; const user = window.U2N.usersByUsernames?.[username]; const displayName = getDisplayNameByUsername(username); @@ -1249,6 +1279,8 @@ const renderUsers = () => { el.append(tagsHolderEl); }); + + const groupedUsersElements = getGroupedUserElements(); }; const getUserFromUserPageIfPossible = () => { diff --git a/src/user-script/parts/db.js b/src/user-script/parts/db.js index 499047a..89b25ec 100644 --- a/src/user-script/parts/db.js +++ b/src/user-script/parts/db.js @@ -130,3 +130,7 @@ const resetUsers = () => { text: "The users' data were removed.", }); }; + +const isSavedUser = (username) => { + return Boolean(username && window.U2N.usersByUsernames?.[username]); +}; diff --git a/src/user-script/parts/render-users.js b/src/user-script/parts/render-users.js index f2310de..044c683 100644 --- a/src/user-script/parts/render-users.js +++ b/src/user-script/parts/render-users.js @@ -1,3 +1,5 @@ + + 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]; @@ -15,7 +17,7 @@ const getUserElements = () => { const kanbanListItems = Array.from(document.querySelectorAll('[class*="slicer-items-module__title"]')).map((el) => { const username = el.getAttribute('data-u2n-username') || el.textContent.trim(); - const isSavedUsername = Boolean(username && window.U2N.usersByUsernames?.[username]); + const isSavedUsername = isSavedUser(username); if (isSavedUsername) { return { @@ -30,7 +32,7 @@ const getUserElements = () => { const tooltipsItems = Array.from(document.querySelectorAll('[data-visible-text]')).map((el) => { const username = el.getAttribute('data-u2n-username') || el.getAttribute('data-visible-text').trim(); - const isSavedUsername = Boolean(username && window.U2N.usersByUsernames?.[username]); + const isSavedUsername = isSavedUser(username); if (isSavedUsername) { return { @@ -50,6 +52,28 @@ const getUserElements = () => { ]; }; +const getGroupedUserElements = () => { + /* Example page https://github.com/orgs/input-output-hk/projects/102/views/1 */ + const projectsCellItems = Array.from(document.querySelectorAll('[role="gridcell"]:has([data-component="Avatar"] + span, [data-avatar-count] + span)')).map((el) => { + const usernames = el.textContent.trim().replace(' and ', ', ').split(', ').filter(Boolean); + + const hasSavedUsername = usernames.some(isSavedUser); + + if (hasSavedUsername) { + return { + el, + usernames, + }; + } + + return undefined; + }).filter(Boolean); + + return [ + ...projectsCellItems, + ]; +}; + appendCSS(` :root { --u2n-user-text: #00293e; @@ -152,7 +176,7 @@ appendCSS(` `, { sourceName: 'render-users' }); export const renderUsers = () => { - const elements = getUserElements(); + const { color, shouldShowAvatars, @@ -163,7 +187,9 @@ export const renderUsers = () => { document.body.setAttribute('data-u2n-color', color); } - elements.forEach(({ el, username: usernameFromElement, updateAttributeInstead }) => { + const userElements = getUserElements(); + + userElements.forEach(({ el, username: usernameFromElement, updateAttributeInstead }) => { const username = usernameFromElement; const user = window.U2N.usersByUsernames?.[username]; const displayName = getDisplayNameByUsername(username); @@ -207,4 +233,6 @@ export const renderUsers = () => { el.append(tagsHolderEl); }); + + const groupedUsersElements = getGroupedUserElements(); }; From be8544e34b9e29904cf13cd8c34dd26fc60f7e22 Mon Sep 17 00:00:00 2001 From: Szymon Tondowski Date: Sat, 19 Apr 2025 18:58:35 +0200 Subject: [PATCH 4/7] PoC of swappingn names in table --- public/github-usernames.user.js | 44 ++++++++++++++++++++++++--- src/user-script/parts/helpers.js | 16 ++++++++++ src/user-script/parts/render-users.js | 28 ++++++++++++++--- 3 files changed, 78 insertions(+), 10 deletions(-) diff --git a/public/github-usernames.user.js b/public/github-usernames.user.js index ebaac7f..37947c6 100644 --- a/public/github-usernames.user.js +++ b/public/github-usernames.user.js @@ -302,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, @@ -1044,7 +1060,7 @@ const renderStatus = () => { `, 'u2n-status'); }; - + const dataU2NSource = 'data-u2n-source'; const getUserElements = () => { const hovercardUrls = Array.from(document.querySelectorAll('[data-hovercard-url^="/users/"]')).map((el) => { @@ -1061,7 +1077,7 @@ const getUserElements = () => { }).filter(Boolean); const kanbanListItems = Array.from(document.querySelectorAll('[class*="slicer-items-module__title"]')).map((el) => { - const username = el.getAttribute('data-u2n-username') || el.textContent.trim(); + const username = el.getAttribute(dataU2NSource) || el.textContent.trim(); const isSavedUsername = isSavedUser(username); @@ -1076,7 +1092,7 @@ const getUserElements = () => { }).filter(Boolean); const tooltipsItems = Array.from(document.querySelectorAll('[data-visible-text]')).map((el) => { - const username = el.getAttribute('data-u2n-username') || el.getAttribute('data-visible-text').trim(); + const username = el.getAttribute(dataU2NSource) || el.getAttribute('data-visible-text').trim(); const isSavedUsername = isSavedUser(username); @@ -1101,7 +1117,8 @@ const getUserElements = () => { const getGroupedUserElements = () => { /* Example page https://github.com/orgs/input-output-hk/projects/102/views/1 */ const projectsCellItems = Array.from(document.querySelectorAll('[role="gridcell"]:has([data-component="Avatar"] + span, [data-avatar-count] + span)')).map((el) => { - const usernames = el.textContent.trim().replace(' and ', ', ').split(', ').filter(Boolean); + const source = el.textContent.trim(); + const usernames = el.getAttribute(dataU2NSource) || source.replace(' and ', ', ').split(', ').filter(Boolean); const hasSavedUsername = usernames.some(isSavedUser); @@ -1109,6 +1126,7 @@ const getGroupedUserElements = () => { return { el, usernames, + source, }; } @@ -1248,7 +1266,7 @@ const renderUsers = () => { return; } - el.setAttribute('data-u2n-username', username); + el.setAttribute(dataU2NSource, username); el.setAttribute('data-u2n-cache-user', cacheValue); if (updateAttributeInstead) { @@ -1281,6 +1299,22 @@ const renderUsers = () => { }); const groupedUsersElements = getGroupedUserElements(); + + groupedUsersElements.forEach(({ el, usernames: usernamesFromElement, source }) => { + console.log('usernamesFromElement', usernamesFromElement); + const hasSavedUsername = usernamesFromElement.some(isSavedUser); + + if (!hasSavedUsername) { + return; + } + + const displayNames = usernamesFromElement.map((username) => getDisplayNameByUsername(username)); + const displayNamesString = joinWithAnd(displayNames); + + console.log(displayNamesString); + + Array.from(el.querySelectorAll('span')).at(-1).textContent = displayNamesString; + }); }; const getUserFromUserPageIfPossible = () => { diff --git a/src/user-script/parts/helpers.js b/src/user-script/parts/helpers.js index 36ad1d8..6335eb8 100644 --- a/src/user-script/parts/helpers.js +++ b/src/user-script/parts/helpers.js @@ -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, diff --git a/src/user-script/parts/render-users.js b/src/user-script/parts/render-users.js index 044c683..4f10a55 100644 --- a/src/user-script/parts/render-users.js +++ b/src/user-script/parts/render-users.js @@ -1,4 +1,4 @@ - +const dataU2NSource = 'data-u2n-source'; const getUserElements = () => { const hovercardUrls = Array.from(document.querySelectorAll('[data-hovercard-url^="/users/"]')).map((el) => { @@ -15,7 +15,7 @@ const getUserElements = () => { }).filter(Boolean); const kanbanListItems = Array.from(document.querySelectorAll('[class*="slicer-items-module__title"]')).map((el) => { - const username = el.getAttribute('data-u2n-username') || el.textContent.trim(); + const username = el.getAttribute(dataU2NSource) || el.textContent.trim(); const isSavedUsername = isSavedUser(username); @@ -30,7 +30,7 @@ const getUserElements = () => { }).filter(Boolean); const tooltipsItems = Array.from(document.querySelectorAll('[data-visible-text]')).map((el) => { - const username = el.getAttribute('data-u2n-username') || el.getAttribute('data-visible-text').trim(); + const username = el.getAttribute(dataU2NSource) || el.getAttribute('data-visible-text').trim(); const isSavedUsername = isSavedUser(username); @@ -55,7 +55,8 @@ const getUserElements = () => { const getGroupedUserElements = () => { /* Example page https://github.com/orgs/input-output-hk/projects/102/views/1 */ const projectsCellItems = Array.from(document.querySelectorAll('[role="gridcell"]:has([data-component="Avatar"] + span, [data-avatar-count] + span)')).map((el) => { - const usernames = el.textContent.trim().replace(' and ', ', ').split(', ').filter(Boolean); + const source = el.textContent.trim(); + const usernames = el.getAttribute(dataU2NSource) || source.replace(' and ', ', ').split(', ').filter(Boolean); const hasSavedUsername = usernames.some(isSavedUser); @@ -63,6 +64,7 @@ const getGroupedUserElements = () => { return { el, usernames, + source, }; } @@ -202,7 +204,7 @@ export const renderUsers = () => { return; } - el.setAttribute('data-u2n-username', username); + el.setAttribute(dataU2NSource, username); el.setAttribute('data-u2n-cache-user', cacheValue); if (updateAttributeInstead) { @@ -235,4 +237,20 @@ export const renderUsers = () => { }); const groupedUsersElements = getGroupedUserElements(); + + groupedUsersElements.forEach(({ el, usernames: usernamesFromElement, source }) => { + console.log('usernamesFromElement', usernamesFromElement); + const hasSavedUsername = usernamesFromElement.some(isSavedUser); + + if (!hasSavedUsername) { + return; + } + + const displayNames = usernamesFromElement.map((username) => getDisplayNameByUsername(username)); + const displayNamesString = joinWithAnd(displayNames); + + console.log(displayNamesString); + + Array.from(el.querySelectorAll('span')).at(-1).textContent = displayNamesString; + }); }; From 5f4c76a7a2e1b4654d8149cc71cf88befd9edf05 Mon Sep 17 00:00:00 2001 From: Szymon Tondowski Date: Sat, 19 Apr 2025 21:32:55 +0200 Subject: [PATCH 5/7] Working assigned person marker --- .github/workflows/deploy.yml | 4 ++-- public/github-usernames.user.js | 31 ++++++++++++++++++++------- src/user-script/parts/render-users.js | 31 ++++++++++++++++++++------- 3 files changed, 48 insertions(+), 18 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index f12dafb..8d830e6 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -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 @@ -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 diff --git a/public/github-usernames.user.js b/public/github-usernames.user.js index 37947c6..b7b52cc 100644 --- a/public/github-usernames.user.js +++ b/public/github-usernames.user.js @@ -1115,12 +1115,12 @@ const getUserElements = () => { }; const getGroupedUserElements = () => { - /* Example page https://github.com/orgs/input-output-hk/projects/102/views/1 */ + /* 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.textContent.trim(); - const usernames = el.getAttribute(dataU2NSource) || source.replace(' and ', ', ').split(', ').filter(Boolean); + const source = el.getAttribute(dataU2NSource) || el.textContent.trim() || ''; + const usernames = source.replace(' and ', ', ').split(', ').filter(Boolean); - const hasSavedUsername = usernames.some(isSavedUser); + const hasSavedUsername = (usernames?.length || 0) > 0 && usernames.some(isSavedUser); if (hasSavedUsername) { return { @@ -1168,10 +1168,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; @@ -1229,6 +1235,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 @@ -1240,7 +1247,6 @@ appendCSS(` `, { sourceName: 'render-users' }); const renderUsers = () => { - const { color, shouldShowAvatars, @@ -1301,7 +1307,6 @@ const renderUsers = () => { const groupedUsersElements = getGroupedUserElements(); groupedUsersElements.forEach(({ el, usernames: usernamesFromElement, source }) => { - console.log('usernamesFromElement', usernamesFromElement); const hasSavedUsername = usernamesFromElement.some(isSavedUser); if (!hasSavedUsername) { @@ -1311,7 +1316,17 @@ const renderUsers = () => { const displayNames = usernamesFromElement.map((username) => getDisplayNameByUsername(username)); const displayNamesString = joinWithAnd(displayNames); - console.log(displayNamesString); + 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; }); diff --git a/src/user-script/parts/render-users.js b/src/user-script/parts/render-users.js index 4f10a55..654d6c5 100644 --- a/src/user-script/parts/render-users.js +++ b/src/user-script/parts/render-users.js @@ -53,12 +53,12 @@ const getUserElements = () => { }; const getGroupedUserElements = () => { - /* Example page https://github.com/orgs/input-output-hk/projects/102/views/1 */ + /* 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.textContent.trim(); - const usernames = el.getAttribute(dataU2NSource) || source.replace(' and ', ', ').split(', ').filter(Boolean); + const source = el.getAttribute(dataU2NSource) || el.textContent.trim() || ''; + const usernames = source.replace(' and ', ', ').split(', ').filter(Boolean); - const hasSavedUsername = usernames.some(isSavedUser); + const hasSavedUsername = (usernames?.length || 0) > 0 && usernames.some(isSavedUser); if (hasSavedUsername) { return { @@ -106,10 +106,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; @@ -167,6 +173,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 @@ -178,7 +185,6 @@ appendCSS(` `, { sourceName: 'render-users' }); export const renderUsers = () => { - const { color, shouldShowAvatars, @@ -239,7 +245,6 @@ export const renderUsers = () => { const groupedUsersElements = getGroupedUserElements(); groupedUsersElements.forEach(({ el, usernames: usernamesFromElement, source }) => { - console.log('usernamesFromElement', usernamesFromElement); const hasSavedUsername = usernamesFromElement.some(isSavedUser); if (!hasSavedUsername) { @@ -249,7 +254,17 @@ export const renderUsers = () => { const displayNames = usernamesFromElement.map((username) => getDisplayNameByUsername(username)); const displayNamesString = joinWithAnd(displayNames); - console.log(displayNamesString); + 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; }); From cbed1ea3f755dd5ad5f0a6c15e3b8a78cbe7a8a3 Mon Sep 17 00:00:00 2001 From: Szymon Tondowski Date: Sat, 19 Apr 2025 21:40:14 +0200 Subject: [PATCH 6/7] Version number updated --- package.json | 2 +- public/github-usernames.user.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 364df5b..832725a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "github-usernames", "private": true, - "version": "1.0.0", + "version": "1.1.0", "type": "module", "author": "Szymon Tondowski", "license": "MIT", diff --git a/public/github-usernames.user.js b/public/github-usernames.user.js index b7b52cc..661e623 100644 --- a/public/github-usernames.user.js +++ b/public/github-usernames.user.js @@ -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 @@ -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: {}, From c74ee3043cdd51321f48146306c4ca3694d0e669 Mon Sep 17 00:00:00 2001 From: Szymon Tondowski Date: Sat, 19 Apr 2025 21:45:00 +0200 Subject: [PATCH 7/7] Better name for function --- public/github-usernames.user.js | 16 +++++++--------- src/user-script/parts/db.js | 2 +- src/user-script/parts/render-users.js | 14 ++++++-------- 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/public/github-usernames.user.js b/public/github-usernames.user.js index 661e623..78931d8 100644 --- a/public/github-usernames.user.js +++ b/public/github-usernames.user.js @@ -224,7 +224,7 @@ const resetUsers = () => { }); }; -const isSavedUser = (username) => { +const getIsSavedUser = (username) => { return Boolean(username && window.U2N.usersByUsernames?.[username]); }; @@ -1079,9 +1079,8 @@ const getUserElements = () => { const kanbanListItems = Array.from(document.querySelectorAll('[class*="slicer-items-module__title"]')).map((el) => { const username = el.getAttribute(dataU2NSource) || el.textContent.trim(); - const isSavedUsername = isSavedUser(username); - - if (isSavedUsername) { + const isSavedUser = getIsSavedUser(username); + if (isSavedUser) { return { el, username, @@ -1094,9 +1093,8 @@ const getUserElements = () => { const tooltipsItems = Array.from(document.querySelectorAll('[data-visible-text]')).map((el) => { const username = el.getAttribute(dataU2NSource) || el.getAttribute('data-visible-text').trim(); - const isSavedUsername = isSavedUser(username); - - if (isSavedUsername) { + const isSavedUser = getIsSavedUser(username); + if (isSavedUser) { return { el, username, @@ -1120,7 +1118,7 @@ const getGroupedUserElements = () => { const source = el.getAttribute(dataU2NSource) || el.textContent.trim() || ''; const usernames = source.replace(' and ', ', ').split(', ').filter(Boolean); - const hasSavedUsername = (usernames?.length || 0) > 0 && usernames.some(isSavedUser); + const hasSavedUsername = (usernames?.length || 0) > 0 && usernames.some(getIsSavedUser); if (hasSavedUsername) { return { @@ -1307,7 +1305,7 @@ const renderUsers = () => { const groupedUsersElements = getGroupedUserElements(); groupedUsersElements.forEach(({ el, usernames: usernamesFromElement, source }) => { - const hasSavedUsername = usernamesFromElement.some(isSavedUser); + const hasSavedUsername = usernamesFromElement.some(getIsSavedUser); if (!hasSavedUsername) { return; diff --git a/src/user-script/parts/db.js b/src/user-script/parts/db.js index 89b25ec..6b69915 100644 --- a/src/user-script/parts/db.js +++ b/src/user-script/parts/db.js @@ -131,6 +131,6 @@ const resetUsers = () => { }); }; -const isSavedUser = (username) => { +const getIsSavedUser = (username) => { return Boolean(username && window.U2N.usersByUsernames?.[username]); }; diff --git a/src/user-script/parts/render-users.js b/src/user-script/parts/render-users.js index 654d6c5..a2bf161 100644 --- a/src/user-script/parts/render-users.js +++ b/src/user-script/parts/render-users.js @@ -17,9 +17,8 @@ const getUserElements = () => { const kanbanListItems = Array.from(document.querySelectorAll('[class*="slicer-items-module__title"]')).map((el) => { const username = el.getAttribute(dataU2NSource) || el.textContent.trim(); - const isSavedUsername = isSavedUser(username); - - if (isSavedUsername) { + const isSavedUser = getIsSavedUser(username); + if (isSavedUser) { return { el, username, @@ -32,9 +31,8 @@ const getUserElements = () => { const tooltipsItems = Array.from(document.querySelectorAll('[data-visible-text]')).map((el) => { const username = el.getAttribute(dataU2NSource) || el.getAttribute('data-visible-text').trim(); - const isSavedUsername = isSavedUser(username); - - if (isSavedUsername) { + const isSavedUser = getIsSavedUser(username); + if (isSavedUser) { return { el, username, @@ -58,7 +56,7 @@ const getGroupedUserElements = () => { const source = el.getAttribute(dataU2NSource) || el.textContent.trim() || ''; const usernames = source.replace(' and ', ', ').split(', ').filter(Boolean); - const hasSavedUsername = (usernames?.length || 0) > 0 && usernames.some(isSavedUser); + const hasSavedUsername = (usernames?.length || 0) > 0 && usernames.some(getIsSavedUser); if (hasSavedUsername) { return { @@ -245,7 +243,7 @@ export const renderUsers = () => { const groupedUsersElements = getGroupedUserElements(); groupedUsersElements.forEach(({ el, usernames: usernamesFromElement, source }) => { - const hasSavedUsername = usernamesFromElement.some(isSavedUser); + const hasSavedUsername = usernamesFromElement.some(getIsSavedUser); if (!hasSavedUsername) { return;