您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Show inline user information on avatar hover.
当前为
// ==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)); })();