function renderNotifications() { const unreadNotifications = storage.get() .filter(shouldNotificationAppearHere); if (unreadNotifications.length === 0) { return; } // Don’t simplify selector, it’s for cross-extension compatibility let pageList = select('#notification-center .notifications-list'); if (!pageList) { pageList = <div class="notifications-list"></div>; select('.blankslate').replaceWith(pageList); } unreadNotifications.forEach(notification => { const group = getNotificationGroup(notification); const item = getNotification(notification); pageList.prepend(group); group .querySelector('ul.notifications') .prepend(item); }); // Make sure that all the boxes with unread items are at the top // This is necessary in the "All notifications" view for (const repo of select.all('.boxed-group')) { if (select.exists('.unread', repo)) { pageList.prepend(repo); } } }
function create(html){ if (html.charAt(0) == '<') { return select(newElement(html)); } return select(document.createElement(html)); }
function addError (error) { var top = select('.top'); if (!top) { setup(); top = select('.top'); }; classes.add(select('.top'), 'failed'); error.stack = format(templates.stack, error.stack.replace(/\n\s+/g, templates['stack-line'])); error.code = format(templates.code, { 'first-line-num': error.source[0].line, 'first-line-source': escape(error.source[0].code), 'second-line-num': error.source[1].line, 'second-line-source': escape(error.source[1].code), 'third-line-num': error.source[2].line, 'third-line-source': escape(error.source[2].code) }); if (error.expected != undefined) { error.diff = format(templates.diff, JSON.stringify(error.expected, null, " "), JSON.stringify(error.actual, null, " ")); } if (addError.last != error.test) { error.title = '<h3>' + error.test +'</h3>'; addError.last = error.test; } else { error.title = ''; } dom.add(select('.results .errors'), templates.error, error); }
const maxPixelsAvailable = () => { // Unfortunately can't cache this value, as it'll change with the browsers zoom level const filenameLeftOffset = select('.diff-toolbar-filename').getBoundingClientRect().left; const diffStatLeftOffset = select('.diffbar > .diffstat').getBoundingClientRect().left; return diffStatLeftOffset - filenameLeftOffset; };
export default async () => { const count = await updateReleasesCount(); if (count === 0) { return; } const releasesTab = ( <a href={`/${repoUrl}/releases`} class="reponav-item" data-hotkey="g r"> {icons.tag()} <span> Releases </span> {count === undefined ? '' : <span class="Counter">{count}</span>} </a> ); select('.reponav-dropdown').before(releasesTab); registerShortcut('repos', 'g r', 'Go to Releases'); if (pageDetect.isReleasesOrTags()) { const selected = select('.reponav-item.selected'); if (selected) { selected.classList.remove('js-selected-navigation-item', 'selected'); } releasesTab.classList.add('js-selected-navigation-item', 'selected'); releasesTab.setAttribute('data-selected-links', 'repo_releases'); // Required for ajaxLoad } };
function toggleFrame () { var results = select('.results'); var frame = select('.frame'); classes.toggle(results, 'open'); classes.toggle(frame, 'open'); updateFramePosition(); }
function setupGrep () { var el = select('#grep'); on(select('.grep label'), 'click', function () { el.focus(); }); bindKey(el, 'enter', function () { grep(el.value); }); }
function status (msg) { var el = select('.status'); if (!el) { document.body.innerHTML = format(templates.waiting, { message: msg }); return; } el.innerHTML = msg; select('.waiting').className = 'waiting center ' + msg; }
return statusElements.some(li => { // 首先得是登录用户自己的消息 const authorUrl = select('.author', li).href if (authorUrl !== getLoggedInUserProfilePageUrl()) return false // 且消息的时间应该在发送之后,否则认为是之前发送过但是没有加载进 TL 的消息 const statusTime = ( Date.parse(select('.time', li).getAttribute('stime')) + timeDifference ) if (startTime > statusTime) return false return true })
// 把当前登录用户的信息保存到切换列表里,或者更新用户信息 async function addOrUpdateCurrentUser() { const loggedInUserId = getLoggedInUserId() const allCookies = Cookies.get() const userData = { userId: loggedInUserId, nickname: select('#user_top h3').textContent, avatarUrl: select('#user_top img').src, cookies: omitBy(allCookies, (_, key) => ( key.startsWith('_') || // 跳过键名以「_」开头的 cookie key === 'uuid' // 跳过键名为「uuid」的 cookie )), } await updateUserData(loggedInUserId, userData) }
function ifNecessary (child, parent) { if (Array.isArray(child)) { child = child[0]; } if ( typeof child != 'string') { return child; } if (typeof parent == 'string') { parent = select(parent, document); } return select(child, parent); }
function addReadmeButtons() { const readmeContainer = select('#readme.readme'); if (!readmeContainer) { return; } const buttons = <div id="refined-github-readme-buttons"></div>; /** * Generate Release button */ const tags = select.all('.branch-select-menu [data-tab-filter="tags"] .select-menu-item') .map(element => [ element.getAttribute('data-name'), element.getAttribute('href') ]); const releases = new Map(tags); const [latestRelease] = toSemver([...releases.keys()], {clean: false}); if (latestRelease) { buttons.appendChild( <a class="tooltipped tooltipped-nw" href={`${releases.get(latestRelease)}#readme`} aria-label={`View this file at the latest version (${latestRelease})`}> {icons.tag} </a> ); } /** * Generate Edit button */ if (select('.branch-select-menu i').textContent === 'Branch:') { const readmeName = select('#readme > h3').textContent.trim(); const path = select('.breadcrumb').textContent.trim().split('/').slice(1).join('/'); const currentBranch = select('.branch-select-menu .select-menu-item.selected').textContent.trim(); buttons.appendChild( <a href={`/${repoUrl}/edit/${currentBranch}/${path}${readmeName}`} class="tooltipped tooltipped-nw" aria-label="Edit this file"> {icons.edit} </a> ); } readmeContainer.appendChild(buttons); }
function addCustomAllReadBtn() { const hasMarkAllReadBtnExists = select.exists('#notification-center a[href="#mark_as_read_confirm_box"]'); if (hasMarkAllReadBtnExists || storage.get().length === 0) { return; } select('.tabnav .float-right').append( <a href="#mark_as_read_confirm_box" class="btn btn-sm" rel="facebox">Mark all as read</a> ); document.body.append( <div id="mark_as_read_confirm_box" style={{display: 'none'}}> <h2 class="facebox-header" data-facebox-id="facebox-header">Are you sure?</h2> <p data-facebox-id="facebox-description">Are you sure you want to mark all unread notifications as read?</p> <div class="full-button"> <button id="clear-local-notification" class="btn btn-block">Mark all notifications as read</button> </div> </div> ); delegate('#clear-local-notification', 'click', () => { storage.set([]); location.reload(); }); }
function appendReleasesCount(count) { if (!count) { return; } select('.reponav-releases').append(<span class="Counter">{count}</span>); }
function addProjectNewLink() { if (select.exists('#projects-feature:checked') && !select.exists('#refined-github-project-new-link')) { select(`#projects-feature ~ p.note`).insertAdjacentElement('afterEnd', <a href={`/${repoUrl}/projects/new`} class="btn btn-sm" id="refined-github-project-new-link">Add a project</a> ); } }
function markTest (test) { var overview = select('.overview'); if (overview) return; var list = select('.waiting .list'); if (!list) { dom.add(select('.waiting h1'), templates.overview); list = select('.waiting .list'); } dom.add(list, templates.test, { icon: '', name: test.name }); }
gitHubInjection(async () => { destroy(); if (pageDetect.isNotifications()) { renderNotifications(); addCustomAllReadBtn(); updateLocalNotificationsCount(); updateLocalParticipatingCount(); listeners.push( delegate('.btn-link.delete-note', 'click', markNotificationRead), delegate('.js-mark-all-read', 'click', markAllNotificationsRead), delegate('.js-delete-notification button', 'click', updateUnreadIndicator), delegate('form[action="/notifications/mark"] button', 'click', () => { storage.set([]); }) ); } else if (pageDetect.isPR() || pageDetect.isIssue()) { markRead(location.href); // The sidebar changes when new comments are added or the issue status changes observeEl('.discussion-sidebar', addMarkUnreadButton); } else if (pageDetect.isIssueList()) { await domLoaded; for (const discussion of storage.get()) { const url = new URL(discussion.url); const listItem = select(`.read [href='${url.pathname}']`); if (listItem) { listItem.closest('.read').classList.replace('read', 'unread'); } } } updateUnreadIndicator(); });
export default function () { registerShortcut('releases', 'c', 'Create a new release'); const createReleaseButton = select('a[href$="/releases/new"]:not([data-hotkey])'); if (createReleaseButton) { createReleaseButton.setAttribute('data-hotkey', 'c'); } }
function getNotification(notification) { const { participants, title, state, type, dateTitle, date, url } = notification; const existing = select(`a.js-notification-target[href^="${stripHash(url)}"]`); if (existing) { const item = existing.closest('.js-notification'); item.classList.replace('read', 'unread'); return item; } const usernames = participants .map(participant => participant.username) .join(' and ') .replace(/ and (.+) and/, ', $1, and'); // 3 people only: A, B, and C const avatars = participants.map(participant => <a href={`/${participant.username}`} class="avatar"> <img alt={`@${participant.username}`} height="20" src={participant.avatar} width="20"/> </a> ); return ( <li class={`list-group-item js-notification js-navigation-item unread ${type}-notification rgh-unread`}> <span class="list-group-item-name css-truncate"> <span class={`type-icon type-icon-state-${state}`}> {stateIcons[type][state]()} </span> <a class="css-truncate-target js-notification-target js-navigation-open list-group-item-link" href={url}> {title} </a> </span> <ul class="notification-actions"> <li class="delete"> <button class="btn-link delete-note"> {icons.check()} </button> </li> <li class="mute tooltipped tooltipped-w" aria-label={`${type === 'issue' ? 'Issue' : 'PR'} manually marked as unread`}> {icons.info()} </li> <li class="age"> <relative-time datetime={date} title={dateTitle}/> </li> <div class="AvatarStack AvatarStack--three-plus AvatarStack--right clearfix d-inline-block" style={{marginTop: 1}}> <div class="AvatarStack-body tooltipped tooltipped-sw tooltipped-align-right-1" aria-label={usernames}> {avatars} </div> </div> </ul> </li> ); }
observeEl(select('#partial-discussion-header').parentNode, () => { const title = select('.js-issue-title:not(.refined-linkified-title)'); if (title) { title.classList.add('refined-linkified-title'); editTextNodes(linkifyIssues, title); } });
it('hides the given element', function(){ var div = select('div'); hide(div); expect(div.style.position).to.equal('absolute'); expect(div.style.top).to.equal('-9999px'); expect(div.style.left).to.equal('-9999px'); });
function byId(root, id, el) { // if doc, query on it, else query the parent doc or if a detached fragment rewrite the query and run on the fragment return root[nodeType] === 9 ? root.getElementById(id) : root.ownerDocument && (((el = root.ownerDocument.getElementById(id)) && isAncestor(el, root) && el) || (!isAncestor(root, root.ownerDocument) && select('[id="' + id + '"]', root)[0])) }
function createButton() { const existingButton = select('#pagination-totop') const container = ( (select('#stream') && select('#stream').parentElement) || select('.inner-content') ) toTopButton = ( <a id="pagination-totop" onClick={clickHandler}>返回顶部</a> ) if (existingButton) existingButton.remove() container.appendChild(toTopButton) hideButton() return toTopButton }
function setup () { dom.remove(select('.waiting')); var template = format(templates['layout'], templates); dom.add(document.body, template, { grep: grep() || '', conn: '' }); on(select('.frame-button'), 'click', toggleFrame); on(select('.run-again'), 'click', run); updateConn(); setupGrep(); updateFramePosition(); }
function markUnread() { const participants = select.all('.participant-avatar').slice(0, 3).map(el => ({ username: el.getAttribute('aria-label'), avatar: el.querySelector('img').src })); const {ownerName, repoName} = pageDetect.getOwnerAndRepo(); const repository = `${ownerName}/${repoName}`; const title = select('.js-issue-title').textContent.trim(); const type = pageDetect.isPR() ? 'pull-request' : 'issue'; const url = stripHash(location.href); const stateLabel = select('.gh-header-meta .State'); let state; if (stateLabel.classList.contains('State--green')) { state = 'open'; } else if (stateLabel.classList.contains('State--purple')) { state = 'merged'; } else if (stateLabel.classList.contains('State--red')) { state = 'closed'; } const lastCommentTime = select.all('.timeline-comment-header relative-time').pop(); const dateTitle = lastCommentTime.title; const date = lastCommentTime.getAttribute('datetime'); const unreadNotifications = storage.get(); unreadNotifications.push({ participants, repository, title, state, type, dateTitle, date, url }); storage.set(unreadNotifications); updateUnreadIndicator(); this.setAttribute('disabled', 'disabled'); this.textContent = 'Marked as unread'; }
export default function () { const repoUrl = pageDetect.getRepoURL(); const readmeContainer = select('.repository-content > #readme'); if (readmeContainer && select('.branch-select-menu i').textContent === 'Branch:') { const readmeName = select('#readme > h3').textContent.trim(); const path = select('.breadcrumb').textContent.trim().split('/').slice(1).join('/'); const currentBranch = select('.branch-select-menu .select-menu-item.selected').textContent.trim(); readmeContainer.append( <div id="refined-github-readme-buttons"> <a href={`/${repoUrl}/edit/${currentBranch}/${path}${readmeName}`}> {icons.edit()} </a> </div> ); } }
delegate('.js-comment-field', 'keydown', event => { const field = event.target; // Don't do anything if the suggester box is active if (select.exists('.suggester:not([hidden])', field.form)) { return; } if (event.key === 'Tab' && !event.shiftKey) { indentTextarea(field); event.preventDefault(); } else if (event.key === 'Enter' && event.shiftKey) { const singleCommentButton = select('.review-simple-reply-button', field.form); if (singleCommentButton) { singleCommentButton.click(); event.preventDefault(); } } else if (event.key === 'Escape') { // Cancel buttons have different classes for inline comments and editable comments const cancelButton = select(` .js-hide-inline-comment-form, .js-comment-cancel-button `, field.form); // Cancel if there is a button, else blur the field if (cancelButton) { cancelButton.click(); } else { blurAccessibly(field); } event.stopImmediatePropagation(); event.preventDefault(); } else if (event.key === 'ArrowUp' && field.id === 'new_comment_field' && field.value === '') { const lastOwnComment = select.all('.js-comment.current-user').pop(); if (lastOwnComment) { select('.js-comment-edit-button', lastOwnComment).click(); // Move caret to end of field requestAnimationFrame(() => { select('.js-comment-field', lastOwnComment).selectionStart = Number.MAX_SAFE_INTEGER; }); } } });
export default function () { if (pageDetect.isRepoRoot()) { const meta = select('.repository-meta'); if (select.exists('em', meta) && !select.exists('.js-edit-repo-meta-button')) { meta.style.display = 'none'; } } }
export default () => { if (pageDetect.isRepoTree()) { const uploadFilesButton = select(`.file-navigation a[href^="/${repoUrl}/upload"]`); if (uploadFilesButton) { uploadFilesButton.remove(); } } };
allStatuses.forEach((li, index) => { const isInRange = littleIndex <= index && index <= bigIndex const isPreviousSelected = previousSelected.includes(li) const checkbox = select(`.${CLASSNAME_CHECKBOX}`, li) li.classList.toggle(CLASSNAME_MULTIPLE_SELECTION_IN_RANGE, isInRange) checkbox.checked = isInRange || isPreviousSelected })