import React from 'dom-chef'; import cache from 'webext-storage-cache'; import select from 'select-dom'; import TagIcon from 'octicon/tag.svg'; import features from '../libs/features'; import * as pageDetect from '../libs/page-detect'; import * as api from '../libs/api'; import {getOwnerAndRepo, getRepoURL, getRepoGQL} from '../libs/utils'; interface CommitTags { [name: string]: string[]; } type BaseTarget = { commitResourcePath: string; }; type TagTarget = { tagger: { date: Date; }; } & BaseTarget; type CommitTarget = { committedDate: Date; } & BaseTarget; type CommonTarget = TagTarget | CommitTarget; interface TagNode { name: string; target: CommonTarget; } const {ownerName, repoName} = getOwnerAndRepo(); const cacheKey = `tags:${ownerName!}/${repoName!}`; function mergeTags(oldTags: CommitTags, newTags: CommitTags): CommitTags { const result: CommitTags = {...oldTags}; for (const commit in newTags) { if (result[commit]) { result[commit] = [...new Set(result[commit].concat(newTags[commit]))]; } else { result[commit] = newTags[commit]; } } return result; } function isTagTarget(target: CommonTarget): target is TagTarget { return 'tagger' in target; } async function getTags(lastCommit: string, after?: string): Promise { const {repository} = await api.v4(` repository(${getRepoGQL()}) { refs( first: 100, refPrefix: "refs/tags/", orderBy: { field: TAG_COMMIT_DATE, direction: DESC } ${after ? `, after: "${after}"` : ''} ) { pageInfo { hasNextPage endCursor } nodes { name target { commitResourcePath ... on Tag { tagger { date } } ... on Commit { committedDate } } } } object(expression: "${lastCommit}") { ... on Commit { committedDate } } } `); const {nodes}: {nodes: TagNode[]} = repository.refs; let tags = nodes.reduce((tags: CommitTags, node: TagNode) => { const commit = node.target.commitResourcePath.split('/')[4]; const {name} = node; if (!tags[commit]) { tags[commit] = []; } tags[commit].push(name); return tags; }, {}); // If there are no tags in the repository if (nodes.length === 0) { return tags; } const lastTag = nodes[nodes.length - 1].target; const lastTagIsYounger = new Date(repository.object.committedDate) < new Date(isTagTarget(lastTag) ? lastTag.tagger.date : lastTag.committedDate); // If the last tag is younger than last commit on the page, then not all commits are accounted for, keep looking if (lastTagIsYounger && repository.refs.pageInfo.hasNextPage) { tags = mergeTags(tags, await getTags(lastCommit, repository.refs.pageInfo.endCursor)); } // There are no tags for this commit return tags; } async function init(): Promise { const commitsOnPage = select.all('li.commit'); const lastCommitOnPage = (commitsOnPage[commitsOnPage.length - 1].dataset.channel as string).split(':')[3]; let cached = await cache.get<{[commit: string]: string[]}>(cacheKey) ?? {}; const commitsWithNoTags = []; for (const commit of commitsOnPage) { const targetCommit = (commit.dataset.channel as string).split(':')[3]; let targetTags = cached[targetCommit]; if (!targetTags) { // No tags for this commit found in the cache, check in github cached = mergeTags(cached, await getTags(lastCommitOnPage)); // eslint-disable-line no-await-in-loop targetTags = cached[targetCommit]; } if (!targetTags) { // There was no tags for this commit, save that info to the cache commitsWithNoTags.push(targetCommit); } else if (targetTags.length > 0) { select('.commit-meta', commit)!.append(
{targetTags.map((tags, i) => ( <> {tags} {(i + 1) === targetTags.length ? '' : ', '} ))}
); } } if (commitsWithNoTags.length > 0) { for (const commit of commitsWithNoTags) { cached[commit] = []; } } await cache.set(cacheKey, cached, 1); } features.add({ id: __filebasename, description: 'Display the corresponding tags next to commits', screenshot: 'https://user-images.githubusercontent.com/14323370/66400400-64ba7280-e9af-11e9-8d6c-07b35afde91f.png' }, { include: [ pageDetect.isRepoCommitList ], init });