Greasy Fork is available in English.

Github User Info

Show inline user information on avatar hover.

Mint 2015.04.14.. Lásd a legutóbbi verzió

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @id          Github_User_Info@https://github.com/jerone/UserScripts
// @name        Github User Info
// @namespace   https://github.com/jerone/UserScripts
// @description Show inline user information on avatar hover.
// @author      jerone
// @copyright   2015+, jerone (http://jeroenvanwarmerdam.nl)
// @license     GNU GPLv3
// @homepage    https://github.com/jerone/UserScripts/tree/master/Github_User_Info
// @homepageURL https://github.com/jerone/UserScripts/tree/master/Github_User_Info
// @supportURL  https://github.com/jerone/UserScripts/issues
// @contributionURL https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=VCYMHWQ7ZMBKW
// @version     0.3.0
// @grant       GM_xmlhttpRequest
// @grant       GM_setValue
// @grant       GM_getValue
// @grant       unsafeWindow
// @run-at      document-end
// @include     https://github.com/*
// @include     https://gist.github.com/*
// ==/UserScript==

(function() {

	function proxy(fn) {
		return function proxyScope() {
			var that = this;
			return function proxyEvent(e) {
				var args = that.slice(0); // clone;
				args.unshift(e); // prepend event;
				fn.apply(this, args);
			};
		}.call([].slice.call(arguments, 1));
	}

	var _timer;

	var userMenu = document.createElement('div');
	userMenu.style =
		'display: none;' +
		'background-color: #F5F5F5;' +
		'border-radius: 3px;' +
		'border: 1px solid #DDDDDD;' +
		'box-shadow: 0 0 10px rgba(0, 0, 1, 0.1);' +
		'font-size: 11px;' +
		'padding: 10px;' +
		'position: absolute;' +
		'width: 335px;' +
		'z-index: 99;';
	userMenu.classList.add('GithubUserInfo');
	userMenu.addEventListener('mouseleave', function mouseleave() {
		console.log('GithubUserInfo:userMenu', 'mouseleave');
		window.clearTimeout(_timer);
		userMenu.style.display = 'none';
	});
	document.body.appendChild(userMenu);


	var userAvatar = document.createElement('a');
	userAvatar.style =
		'width: 100px;' +
		'height: 100px;' +
		'float: left;' +
		'margin-bottom: 10px;';
	userMenu.appendChild(userAvatar);
	var userAvatarImg = document.createElement('img');
	userAvatarImg.style =
		'border-radius: 3px;' +
		'transition-property: height, width;' +
		'transition-duration: 0.5s;';
	userAvatar.appendChild(userAvatarImg);


	var userInfo = document.createElement('div');
	userInfo.style =
		'width: 100%;' +
		'padding-left: 102px;';
	userMenu.appendChild(userInfo);

	var userName = document.createElement('strong');
	userName.style =
		'padding-left: 24px;' +
		'white-space: nowrap;' +
		'overflow: hidden;' +
		'text-overflow: ellipsis;';
	userInfo.appendChild(userName);

	var userCompany = document.createElement('div');
	userCompany.style =
		'display: none;' +
		'white-space: nowrap;' +
		'overflow: hidden;' +
		'text-overflow: ellipsis;';
	userInfo.appendChild(userCompany);
	var userCompanyIcon = document.createElement('span');
	userCompanyIcon.classList.add('octicon', 'octicon-organization');
	userCompanyIcon.style =
		'width: 24px;' +
		'text-align: center;' +
		'color: #CCC;';
	userCompany.appendChild(userCompanyIcon);
	var userCompanyText = document.createElement('span');
	userCompany.appendChild(userCompanyText);
	var userCompanyAdmin = document.createElement('span');
	userCompanyAdmin.style =
		'display: none;' +
		'margin-left: 5px;' +
		'position: relative;' +
		'top: -1px;' +
		'padding: 2px 5px;' +
		'font-size: 10px;' +
		'font-weight: bold;' +
		'color: #FFF;' +
		'text-transform: uppercase;' +
		'background-color: #4183C4;' +
		'border-radius: 3px;';
	userCompanyAdmin.appendChild(document.createTextNode('Staff'));
	userCompany.appendChild(userCompanyAdmin);

	var userLocation = document.createElement('div');
	userLocation.style =
		'display: none;' +
		'white-space: nowrap;' +
		'overflow: hidden;' +
		'text-overflow: ellipsis;';
	userInfo.appendChild(userLocation);
	var userLocationIcon = document.createElement('span');
	userLocationIcon.classList.add('octicon', 'octicon-location');
	userLocationIcon.style =
		'width: 24px;' +
		'text-align: center;' +
		'color: #CCC;';
	userLocation.appendChild(userLocationIcon);
	var userLocationText = document.createElement('a');
	userLocationText.setAttribute('target', '_blank');
	userLocation.appendChild(userLocationText);

	var userMail = document.createElement('div');
	userMail.style =
		'display: none;' +
		'white-space: nowrap;' +
		'overflow: hidden;' +
		'text-overflow: ellipsis;';
	userInfo.appendChild(userMail);
	var userMailIcon = document.createElement('span');
	userMailIcon.classList.add('octicon', 'octicon-mail');
	userMailIcon.style =
		'width: 24px;' +
		'text-align: center;' +
		'color: #CCC;';
	userMail.appendChild(userMailIcon);
	var userMailText = document.createElement('a');
	userMail.appendChild(userMailText);

	var userLink = document.createElement('div');
	userLink.style =
		'display: none;' +
		'white-space: nowrap;' +
		'overflow: hidden;' +
		'text-overflow: ellipsis;';
	userInfo.appendChild(userLink);
	var userLinkIcon = document.createElement('span');
	userLinkIcon.classList.add('octicon', 'octicon-link');
	userLinkIcon.style =
		'width: 24px;' +
		'text-align: center;' +
		'color: #CCC;';
	userLink.appendChild(userLinkIcon);
	var userLinkText = document.createElement('a');
	userLinkText.setAttribute('target', '_blank');
	userLink.appendChild(userLinkText);

	var userJoined = document.createElement('div');
	userJoined.style =
		'display: none;' +
		'white-space: nowrap;' +
		'overflow: hidden;' +
		'text-overflow: ellipsis;';
	userInfo.appendChild(userJoined);
	var userJoinedIcon = document.createElement('span');
	userJoinedIcon.classList.add('octicon', 'octicon-clock');
	userJoinedIcon.style =
		'width: 24px;' +
		'text-align: center;' +
		'color: #CCC;';
	userJoined.appendChild(userJoinedIcon);
	userJoined.appendChild(document.createTextNode('Joined on '));
	var userJoinedText = unsafeWindow.document.createElement('time', 'local-time'); // https://github.com/github/time-elements
	userJoinedText.setAttribute('day', 'numeric');
	userJoinedText.setAttribute('month', 'short');
	userJoinedText.setAttribute('year', 'numeric');
	userJoined.appendChild(userJoinedText);


	var userCounts = document.createElement('div');
	userCounts.style =
		'text-align: center;' +
		'border-top: 1px solid #EEE;' +
		'padding-top: 5px;' +
		'margin-top: 10px;' +
		'clear: left;';
	userMenu.appendChild(userCounts);

	var userFollowers = document.createElement('a');
	userFollowers.style =
		'display: none;' +
		'float: left;' +
		'width: 20%;' +
		'text-decoration: none;';
	userFollowers.classList.add('vcard-stat');
	userFollowers.setAttribute('target', '_blank');
	userFollowers.setAttribute('title', 'Followers');
	userCounts.appendChild(userFollowers);
	var userFollowersCount = document.createElement('strong');
	userFollowersCount.style =
		'display: block;' +
		'font-size: 28px;';
	userFollowers.appendChild(userFollowersCount);
	var userFollowersText = document.createElement('span');
	userFollowersText.appendChild(document.createTextNode('Followers'));
	userFollowersText.classList.add('text-muted');
	userFollowers.appendChild(userFollowersText);

	var userFollowing = document.createElement('a');
	userFollowing.style =
		'display: none;' +
		'float: left;' +
		'width: 20%;' +
		'text-decoration: none;';
	userFollowing.classList.add('vcard-stat');
	userFollowing.setAttribute('target', '_blank');
	userFollowing.setAttribute('title', 'Following');
	userCounts.appendChild(userFollowing);
	var userFollowingCount = document.createElement('strong');
	userFollowingCount.style =
		'display: block;' +
		'font-size: 28px;';
	userFollowing.appendChild(userFollowingCount);
	var userFollowingText = document.createElement('span');
	userFollowingText.appendChild(document.createTextNode('Following'));
	userFollowingText.classList.add('text-muted');
	userFollowing.appendChild(userFollowingText);

	var userRepos = document.createElement('a');
	userRepos.style =
		'display: none;' +
		'float: left;' +
		'width: 20%;' +
		'text-decoration: none;';
	userRepos.classList.add('vcard-stat');
	userRepos.setAttribute('target', '_blank');
	userRepos.setAttribute('title', 'Public repositories');
	userCounts.appendChild(userRepos);
	var userReposCount = document.createElement('strong');
	userReposCount.style =
		'display: block;' +
		'font-size: 28px;';
	userRepos.appendChild(userReposCount);
	var userReposText = document.createElement('span');
	userReposText.appendChild(document.createTextNode('Repos'));
	userReposText.classList.add('text-muted');
	userRepos.appendChild(userReposText);

	var userOrgs = document.createElement('a');
	userOrgs.style =
		'display: none;' +
		'float: left;' +
		'width: 20%;' +
		'text-decoration: none;';
	userOrgs.classList.add('vcard-stat');
	userOrgs.setAttribute('target', '_blank');
	userOrgs.setAttribute('title', 'Public organisations');
	userCounts.appendChild(userOrgs);
	var userOrgsCount = document.createElement('strong');
	userOrgsCount.style =
		'display: block;' +
		'font-size: 28px;';
	userOrgs.appendChild(userOrgsCount);
	var userOrgsText = document.createElement('span');
	userOrgsText.appendChild(document.createTextNode('Orgs'));
	userOrgsText.classList.add('text-muted');
	userOrgs.appendChild(userOrgsText);

	var userMembers = document.createElement('a');
	userMembers.style =
		'display: none;' +
		'float: left;' +
		'width: 20%;' +
		'text-decoration: none;';
	userMembers.classList.add('vcard-stat');
	userMembers.setAttribute('target', '_blank');
	userMembers.setAttribute('title', 'Public members');
	userCounts.appendChild(userMembers);
	var userMembersCount = document.createElement('strong');
	userMembersCount.style =
		'display: block;' +
		'font-size: 28px;';
	userMembers.appendChild(userMembersCount);
	var userMembersText = document.createElement('span');
	userMembersText.appendChild(document.createTextNode('Members'));
	userMembersText.classList.add('text-muted');
	userMembers.appendChild(userMembersText);

	var userGists = document.createElement('a');
	userGists.style =
		'display: none;' +
		'float: left;' +
		'width: 20%;' +
		'text-decoration: none;';
	userGists.classList.add('vcard-stat');
	userGists.setAttribute('target', '_blank');
	userGists.setAttribute('title', 'Public gists');
	userCounts.appendChild(userGists);
	var userGistsCount = document.createElement('strong');
	userGistsCount.style =
		'display: block;' +
		'font-size: 28px;';
	userGists.appendChild(userGistsCount);
	var userGistsText = document.createElement('span');
	userGistsText.appendChild(document.createTextNode('Gists'));
	userGistsText.classList.add('text-muted');
	userGists.appendChild(userGistsText);


	var UPDATE_INTERVAL_DAYS = 7;

	function getData(elm) {
		var username;
		if (elm.getAttribute('alt')) {
			username = elm.getAttribute('alt').replace('@', '');
		} else if (elm.parentNode.parentNode.querySelector('.author')) {
			username = elm.parentNode.parentNode.querySelector('.author').textContent.trim();
		} else {
			return;
		}

		var rect = elm.getBoundingClientRect();
		var position = {
			top: rect.top + window.scrollY,
			left: rect.left + window.scrollX
		};
		var avatarSize = {
			height: elm.height,
			width: elm.width
		};

		var usersString = GM_getValue('users', '{}');
		var users = JSON.parse(usersString);
		if (users[username]) {
			var date = new Date(users[username].checked_at),
				now = new Date();
			if (date > now.setDate(now.getDate() - UPDATE_INTERVAL_DAYS)) {
				var data = users[username].data;
				console.log('GithubUserInfo:getData', 'CACHED', data);
				fillData(defaultData(data), position, avatarSize);
			} else {
				console.log('GithubUserInfo:getData', 'AJAX - OUTDATED', username);
				fetchData(username, position, avatarSize);
			}
		} else {
			console.log('GithubUserInfo:getData', 'AJAX - NON-EXISTING', username);
			fetchData(username, position, avatarSize);
		}
	}

	function fetchData(username, position, avatarSize) {
		console.log('GithubUserInfo:fetchData', username);
		GM_xmlhttpRequest({
			method: 'GET',
			url: 'https://api.github.com/users/' + username,
			onload: proxy(parseUserData, position, avatarSize)
		});
	}

	function parseUserData(response, position, avatarSize) {
		var dataParsed = parseRawData(response.responseText);
		if (!dataParsed) {
			return;
		}
		var data = defaultData(normalizeData(dataParsed));
		console.log('GithubUserInfo:parseUserData', data.username);

		GM_xmlhttpRequest({
			method: 'GET',
			url: 'https://api.github.com/users/' + data.username + '/orgs',
			onload: proxy(parseOrgsData, position, avatarSize, data)
		});
	}

	function parseOrgsData(response, position, avatarSize, data) {
		var dataParsed = parseRawData(response.responseText);
		if (!dataParsed) {
			return;
		}
		data.orgs = dataParsed.length;
		console.log('GithubUserInfo:parseOrgsData', data.username, data.orgs);

		switch (data.type) {
			case 'Organization':
				{
					GM_xmlhttpRequest({
						method: 'GET',
						url: 'https://api.github.com/orgs/' + data.username + '/members',
						onload: proxy(parseMembersData, position, avatarSize, data)
					});
					break;
				}
			default:
				{
					fillData(data, position, avatarSize);
					setData(data, data.username);
					break;
				}
		}
	}

	function parseMembersData(response, position, avatarSize, data) {
		var dataParsed = parseRawData(response.responseText);
		if (!dataParsed) {
			return;
		}
		data.members = dataParsed.length;
		console.log('GithubUserInfo:parseMembersData', data.username, data.members);

		fillData(data, position, avatarSize);
		setData(data, data.username);
	}

	function parseRawData(data) {
		data = JSON.parse(data);
		if (data.message && data.message.startsWith('API rate limit exceeded')) {
			console.log('GithubUserInfo:parseRawData', 'API RATE LIMIT EXCEEDED');
			return;
		}
		return data;
	}

	function normalizeData(data) {
		return {
			'username': data.login,
			'avatar': data.avatar_url,
			'type': data.type,
			'name': data.name,
			'company': data.company,
			'blog': data.blog,
			'location': data.location,
			'mail': data.email,
			'repos': data.public_repos,
			'gists': data.public_gists,
			'followers': data.followers,
			'following': data.following,
			'created_at': data.created_at,
			'admin': !!data.site_admin
		};
	}

	function defaultData(data) {
		return {
			'username': data.username,
			'avatar': data.avatar,
			'type': data.type,
			'name': data.name || data.username,
			'company': data.admin ? 'GitHub' : data.company || '',
			'blog': data.blog || '',
			'location': data.location || '',
			'mail': data.mail || '',
			'repos': data.repos || 0,
			'gists': data.gists || 0,
			'followers': data.followers || 0,
			'following': data.following || 0,
			'created_at': data.created_at,
			'admin': data.admin || false,
			'orgs': data.orgs || 0,
			'members': data.members || 0
		};
	}

	function setData(data, username) {
		console.log('GithubUserInfo:setData', username, data);
		var usersString = GM_getValue('users', '{}');
		var users = JSON.parse(usersString);
			users[username] = {
				checked_at: (new Date()).toJSON(),
				data: data
			};
		GM_setValue('users', JSON.stringify(users));
	}

	function fillData(data, position, avatarSize) {
		console.log('GithubUserInfo:fillData', data, position, avatarSize);
		userMenu.style.top = Math.max(position.top - 10 - 1, 2) + 'px';
		userMenu.style.left = Math.max(position.left - 10 - 1, 2) + 'px';
		userMenu.style.display = 'block';

		userAvatar.setAttribute('href', 'https://github.com/' + data.username);
		userAvatarImg.style.height = avatarSize.height + 'px';
		userAvatarImg.style.width = avatarSize.width + 'px';
		window.setTimeout(function avatarAnimationTimeout() {
			userAvatarImg.style.height = '100px';
			userAvatarImg.style.width = '100px';
		}, 50);
		userAvatarImg.setAttribute('src', '');
		userAvatarImg.setAttribute('src', data.avatar);

		userName.setAttribute('title', data.username);
		userName.textContent = data.name;

		if (hasValue(data.company, userCompany)) {
			userCompanyText.textContent = data.company;
			userCompanyAdmin.style.display = data.admin ? 'inline' : 'none';
		}
		if (hasValue(data.location, userLocation)) {
			userLocationText.setAttribute('href', 'https://maps.google.com/maps?q=' + encodeURIComponent(data.location));
			userLocationText.textContent = data.location;
		}
		if (hasValue(data.mail, userMail)) {
			userMailText.setAttribute('href', 'mailto:' + data.mail);
			userMailText.textContent = data.mail;
		}
		if (hasValue(data.blog, userLink)) {
			userLinkText.setAttribute('href', data.blog);
			userLinkText.textContent = data.blog;
		}
		if (hasValue(data.created_at, userJoined)) {
			userJoinedText.setAttribute('datetime', data.created_at);
		}

		var userCountsHasValue = false;
		if (hasValue(data.followers, userFollowers)) {
			userCountsHasValue = true;
			userFollowers.setAttribute('href', 'https://github.com/' + data.username + '/followers');
			userFollowersCount.textContent = data.followers;
		}
		if (hasValue(data.following, userFollowing)) {
			userCountsHasValue = true;
			userFollowing.setAttribute('href', 'https://github.com/' + data.username + '/following');
			userFollowingCount.textContent = data.following;
		}
		if (hasValue(true, userRepos)) { // Always show repos count, as long another count is shown too;
			userCountsHasValue = userCountsHasValue ? true : !!data.repos;
			userRepos.setAttribute('href', 'https://github.com/' + data.username + '?tab=repositories');
			userReposCount.textContent = data.repos;
		}
		if (hasValue(data.orgs, userOrgs)) {
			userCountsHasValue = true;
			userOrgs.setAttribute('href', 'https://github.com/' + data.username);
			userOrgsCount.textContent = data.orgs;
		}
		if (hasValue(data.members, userMembers)) {
			userCountsHasValue = true;
			userMembers.setAttribute('href', 'https://github.com/orgs/' + data.username + '/people');
			userMembersCount.textContent = data.members;
		}
		if (hasValue(data.gists, userGists)) {
			userCountsHasValue = true;
			userGists.setAttribute('href', 'https://gist.github.com/' + data.username);
			userGistsCount.textContent = data.gists;
		}
		userCounts.style.display = userCountsHasValue ? 'block' : 'none';

		//if (data.type === 'Organization' || data.type === 'User') {}
	}

	function hasValue(property, elm) {
		elm.style.display = property ? 'block' : 'none';
		return !!property;
	}


	function init() {
		var avatars = document.querySelectorAll([
			'.avatar[alt^="@"]', // Logged-in user & commits author & issuse participant & users organization & organization member;
			'.timeline-comment-avatar[alt^="@"]', // GitHub comments author;
			'.gist-author img', // Gist author;
			'.gist .js-discussion .timeline-comment-avatar' // Gist comments author;
		].join(','));
		Array.prototype.forEach.call(avatars, function avatarsForEach(avatar) {
			avatar.addEventListener('mouseenter', function mouseenter() {
				console.log('GithubUserInfo:avatar', 'mouseenter');
				_timer = window.setTimeout(function mouseenterTimer() {
					console.log('GithubUserInfo:avatar', 'timeout');
					getData(this);
				}.bind(this), 500);
			});
			avatar.addEventListener('mouseleave', function mouseleave() {
				console.log('GithubUserInfo:avatar', 'mouseleave');
				window.clearTimeout(_timer);
			});
		});
	}

	// Page load;
	console.log('GithubUserInfo', 'page load');
	init();

	// On pjax;
	unsafeWindow.$(document).on("pjax:end", exportFunction(function pjaxEnd() {
		console.log('GithubUserInfo', 'pjax');
		init();
	}, unsafeWindow));

})();