Wanted to do this for a while. Did it today instead of sleeping.
Screenshot:
You can install it from here: https://greasyfork.org/en/scripts/468948-user-details-on-hover
Link to GitHub repo: https://github.com/lemmygod/lemmy-hovercards/tree/main
Or you can copy-paste the following code:
click here to view code.
// ==UserScript==
// @name User Details on Hover
// @namespace http://tampermonkey.net/
// @version 0.12
// @description Show user details on hover
// @author You
// @match *://*/*
// @grant none
// ==/UserScript==
(function () {
"use strict";
const isLemmy =
document.head.querySelector("[name~=Description][content]").content ===
"Lemmy";
if (!isLemmy) return;
// Inject styles for the user card
function main() {
const style = document.createElement("style");
style.innerHTML = `
.user-card {
position: absolute;
display: none;
width: 350px;
background-color: #242424;
color: white;
padding: 15px;
border-radius: 10px;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
z-index: 1000;
grid-gap: 10px;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.4;
}
.user-card .header {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.user-card img {
width: 80px;
height: 80px;
object-fit: cover;
border-radius: 50%;
margin-right: 15px;
}
.user-card .username {
font-size: 1.3em;
font-weight: bold;
}
.user-card .instance {
font-size: 0.8em;
color: #888;
}
.user-card .body {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 10px;
}
.user-card .key {
font-weight: bold;
}
.user-card .value {
color: #ddd;
margin-top: 10px;
}
.user-card .bio {
grid-column: 1 / -1;
font-style: italic;
}`;
document.head.appendChild(style);
// Create the user card
const userCard = document.createElement("div");
userCard.classList.add("user-card");
userCard.id = "user-card";
document.body.appendChild(userCard);
let timer;
// Find all user links
const userLinks = document.querySelectorAll('a.text-info[href*="/u/"]');
userLinks.forEach((userLink) => {
userLink.setAttribute("title", "");
// When mouse enters, show the user card
userLink.addEventListener("mouseenter", async (event) => {
const username = userLink.href.split("/u/")[1];
// Fetch user details
const userInfo = await getUserInfo(username);
// Format the date
const date = new Date(userInfo.creationDate);
const formattedDate = `${date.getFullYear()}/${String(
date.getMonth() + 1
).padStart(2, "0")}/${String(date.getDate()).padStart(2, "0")}`;
// Update the user card
userCard.innerHTML = `
<div class="header">
<img src="${
userInfo.profilePicture ||
`https://api.dicebear.com/6.x/identicon/svg?seed=${username}`
}" alt="User avatar">
<div>
<div class="username">${
userInfo.name || username.split("@")[0]
}</div>
<a href="https://${
userInfo.instance
}/u/${username}" class="instance">${username}${
username.indexOf("@") === -1 ? "@" + userInfo.instance : ""
}
</a>
</div>
</div>
<div class="body">
<div><span class="key">ID:</span> <span class="value">${
userInfo.id
}</span></div>
<div style="display:flex; flex-direction: column; gap: 3px"><span class="key">
<svg class="icon"><use xlink:href="/static/assets/symbols.svg#icon-cake"></use><div class="sr-only"><title>cake</title></div></svg>
Cake Day:</span> <span class="value">${formattedDate}</span></div>
<div><span class="key">Posts:</span> <span class="value">${
userInfo.post_count
}</span></div>
<div><span class="key">Comments:</span> <span class="value">${
userInfo.comment_count
}</span></div>
<div><span class="key">Post Score:</span> <span class="value">${
userInfo.post_score
}</span></div>
<div><span class="key">Comment Score:</span> <span class="value">${
userInfo.comment_score
}</span></div>
${
userInfo.bio ? `<div class="bio">${userInfo.bio}</div>` : ""
}
</div>`;
// Show the user card at the cursor
const rect = userLink.getBoundingClientRect();
userCard.style.left = `${window.pageXOffset + rect.left}px`;
userCard.style.top = `${window.pageYOffset + rect.bottom + 5}px`;
// setTimeout(() => {
if (userLink.querySelector(":hover")) {
userCard.style.display = "block";
}
// }, 250);
timer = setTimeout(() => {
// check if username is not being hovered anymore after 150ms, after which point we must change display to none
if (!userLink.querySelector(":hover")) {
userCard.style.display = "none";
}
}, 150);
});
// When mouse leaves, hide the user card after a slight delay
userLink.addEventListener("mouseleave", () => {
// after a slight delay, delete the node
timer = setTimeout(() => {
// delete the node
// userCard.parentElement.removeChild(userCard);
userCard.style.display = "none";
}, 250);
setTimeout(() => {
// check if both are unhovered after 260ms, and if that's the case, removeChild anyway
if (!userCard.parentElement) return;
if (!userCard.querySelector(":hover")) {
// userCard.parentElement.removeChild(userCard);
userCard.style.display = "none";
}
}, 250);
// timer = setTimeout(() => {
// userCard.style.display = "none";
// }, 250);
});
});
userCard.addEventListener("mouseenter", () => {
clearTimeout(timer);
});
userCard.addEventListener("mouseleave", () => {
userCard.style.display = "none";
// userCard.parentElement.removeChild(userCard);
});
// Fetch user info from the API
async function getUserInfo(userName) {
const instanceName = location.href.split("/")[2];
const response = await fetch(
`https://${instanceName}/api/v3/user?username=${userName}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
);
const user = await response.json();
const {
published: creationDate,
avatar: profilePicture,
bio,
display_name: name,
name: username,
id,
banner,
} = user.person_view.person;
const { comment_count, comment_score, post_count, post_score } =
user.person_view.counts;
return {
creationDate,
profilePicture,
bio,
name,
username,
id,
banner,
instance: instanceName,
comment_count,
comment_score,
post_count,
post_score,
};
}
}
// detect react changed url but didn't reload the page by checking for url change
var oldHref = document.location.href;
setInterval(function () {
if (document.location.href !== oldHref) {
oldHref = document.location.href;
// Wait for the page to load
setTimeout(main, 1000);
console.log("url changed!");
}
}, 500);
// run on page load
main();
})();
I see some issues, can I contribute? Pop this on a git and I’ll make a PR with what I see. Particularly, I see you prefetch the info for the userlist on page load, which is problematic. I think we can do better.
Thanks for building this! Love the concept. Really great idea that I was just thinking is sorely missing.
made a new github account today to post stuff like this
made the repo, here u go: https://github.com/lemmygod/lemmy-hovercards/tree/main
i am planning on improving a lot of things but i’m so busy with work. You’re welcome to make issues, PRs and stuff.
Awesome. I too am a bit busy, but I’ll take a look and start playing with it. We can def knock this outta the park, and then honestly we’re so close to that RES experience I’ve been missing lol
btw if you’re playing w it and have a few extra mins, im planning on adding buttons to send msg, block & perhaps even open in local instance.
something i just realized a couple of days ago is that lemmy ui uses bootstrap and i didn’t leverage this in the component i made, not that i know much bootstrap but some consistency and contrast would not suck. i’ll stop being so busy in a couple of weeks tho so meanwhile that’s that.
I found the card staying up too long and then staying up until clicked on new page.
Changing delay from 400 to 200 seems to make it better.
I made an update. It 100% fixes this, as far as I have tested!! It’s awesome. I was getting really annoyed and I’m now really happy :D 😀
This is great! Thanks! Could you make it work on self as well?
more updates happened, reinstall
it works on self, i don’t know why you think it doesn’t? but it didn’t handle url changes at all, that is fixed now.
got an extra
})();
at the very bottom. no biggielmao my bad :D ty for noticing, deleted that line
loving this script btw
<3
I always kept hovering usernames expecting a card to come out. After a week of doing that, I had to make it happen. It’s still missing a few things. I’m glad you like it. Hopefully it’ll improve over time.
Lmk if you find any actionable bugs. The only one I can think of rn is reading in regular intervals if the mouse is on a card or username and if not, closing it cuz it sometimes gets stuck open at a random spot of the screen for no reason.
possible improvements: add Send Message button, a Block button, a link to local instance and their own instance if different.
I think I fixed the getting stuck open problem (at least for me). I added this at the end of the mouseenter setup function around line 152.
if (!userCard.querySelector(":hover") && !userLink.querySelector(":hover")) { userCard.style.display = "none"; }
“If mouse is gone by the time we’re ready, then hide.”
I fixed this problem with an update. Seems to work perfectly now.