fix: support new pull request experience new data structure

This commit is contained in:
EnixCoda 2026-01-14 21:33:12 +08:00
parent f67065bcf4
commit ecaf9087a3
6 changed files with 77 additions and 45 deletions

View file

@ -2,7 +2,11 @@ import { errors } from 'platforms'
import { isEnterprise } from '.'
import { is } from '../../utils/is'
import { gitakoServiceHost } from '../../utils/networkService'
import { continuousLoadFragmentedPages, getDOM, resolveHeaderLink } from './utils'
import {
continuousLoadFragmentedPages,
continuousLoadFragmentedPagesFromUrl,
resolveHeaderLink,
} from './utils'
function isAPIRateLimitExceeded(content: JSONValue) {
return (
@ -145,15 +149,15 @@ export async function getPullPageDocuments(
repoName: string,
pullId: string,
document?: Document,
): Promise<Document[]> {
// Response of this API contains view of few files but is not complete.
return continuousLoadFragmentedPages(
document ||
(await getDOM(`${window.location.origin}/${userName}/${repoName}/pull/${pullId}/files`)),
)
) {
if (document) {
return continuousLoadFragmentedPages(document)
}
// Response of this contains view of few files but is not complete.
return continuousLoadFragmentedPagesFromUrl(`/${userName}/${repoName}/pull/${pullId}/files`)
}
export async function getCommitPageDocuments(): Promise<Document[]> {
export async function getCommitPageDocuments() {
/* userName: string,
repoName: string,
commitId: string, */

View file

@ -1,6 +1,6 @@
import * as s from 'superstruct'
const repo = s.object({
const repo = s.type({
id: s.number(),
defaultBranch: s.string(),
name: s.string(),
@ -15,13 +15,13 @@ const repo = s.object({
isOrgOwned: s.boolean(),
})
const user = s.object({
const user = s.type({
id: s.number(),
login: s.string(),
userEmail: s.string(),
})
const rel = s.object({
const rel = s.type({
name: s.string(),
listCacheKey: s.string(),
canEdit: s.boolean(),
@ -29,13 +29,13 @@ const rel = s.object({
currentOid: s.string(),
})
const treeItem = s.object({
const treeItem = s.type({
name: s.string(),
path: s.string(),
contentType: s.string(),
})
const tree = s.object({
const tree = s.type({
items: s.array(treeItem),
templateDirectorySuggestionUrl: s.nullable(s.never()),
readme: s.nullable(s.never()),
@ -43,7 +43,7 @@ const tree = s.object({
showBranchInfobar: s.boolean(),
})
const repoPayload = s.object({
const repoPayload = s.type({
allShortcutsEnabled: s.boolean(),
path: s.string(),
repo: repo,
@ -59,18 +59,18 @@ const repoPayload = s.object({
overview: s.unknown(),
})
const reposOverview = s.object({
props: s.object({
const reposOverview = s.type({
props: s.type({
initialPayload: repoPayload,
appPayload: s.unknown(),
}),
})
const app = s.object({
const app = s.type({
payload: repoPayload,
})
const diffSummary = s.object({
const diffSummary = s.type({
changeType: s.string(),
highestAnnotationLevel: s.nullable(s.string()),
isCodeowner: s.nullable(s.boolean()),
@ -87,11 +87,18 @@ const diffSummary = s.object({
export type DiffSummary = s.Infer<typeof diffSummary>
const pullRequest = s.object({
payload: s.object({
pullRequestsFilesRoute: s.object({
diffSummaries: s.array(diffSummary),
}),
const pullRequest = s.type({
payload: s.type({
pullRequestsFilesRoute: s.optional(
s.type({
diffSummaries: s.array(diffSummary),
}),
),
pullRequestsChangesRoute: s.optional(
s.type({
diffSummaries: s.array(diffSummary),
}),
),
}),
})

View file

@ -15,7 +15,7 @@ export async function getCommitTreeData(
.map(({ files }) => files)
.flat()
const documents = await API.getCommitPageDocuments(/* userName, repoName, commitSHA */)
const [, documents] = await API.getCommitPageDocuments(/* userName, repoName, commitSHA */)
const getItemURL = (path: string) => {
for (const doc of documents) {

View file

@ -38,7 +38,7 @@ export async function getPullRequestTreeData(
API.getPullComments(userName, repoName, pullId, accessToken),
])
const docs = await API.getPullPageDocuments(
const [fileChangesPagePath, docs] = await API.getPullPageDocuments(
userName,
repoName,
pullId,
@ -53,7 +53,7 @@ export async function getPullRequestTreeData(
: resolveFileHashMap(docs)
const url = new URL(sanitizedLocation.href)
url.pathname = `/${userName}/${repoName}/pull/${pullId}/files`
url.pathname = fileChangesPagePath
const commentsMap = getCommentsMap(commentData)
const nodes: TreeNode[] = treeData.map(
({
@ -117,12 +117,15 @@ function resolveDiffSummaryMap(docs: Document[]) {
return docs
.map(resolveEmbeddedPullRequestData)
.map(json => {
try {
return json?.payload.pullRequestsFilesRoute.diffSummaries
} catch (error) {
return null
const payload = json?.payload
if (!payload) return null
if ('pullRequestsFilesRoute' in payload) {
return payload.pullRequestsFilesRoute
} else if ('pullRequestsChangesRoute' in payload) {
return payload.pullRequestsChangesRoute
}
})
.map(pullRequests => pullRequests?.diffSummaries)
.reduce((map, curr) => {
curr?.forEach(record => {
map.set(record.path, record)

View file

@ -1,3 +1,5 @@
import { findMapFirst } from '../../utils/findMapFirst'
/**
* Resolved from response header `link`
*
@ -75,10 +77,19 @@ export function resolveHeaderLink(raw: string) {
}
export async function getDOM(url: string) {
return new DOMParser().parseFromString(await (await fetch(url)).text(), 'text/html')
const res = await fetch(url)
const content = await res.text()
return new DOMParser().parseFromString(content, 'text/html')
}
export async function continuousLoadFragmentedPages(doc: Document) {
export async function continuousLoadFragmentedPages(
doc: Document,
docs: Document[] = [],
): Promise<[string, Document[]]> {
docs.push(doc)
// const data = resolveEmbeddedPullRequestData(doc)
/**
* <include-fragment
* src="..."
@ -92,22 +103,22 @@ export async function continuousLoadFragmentedPages(doc: Document) {
'.js-diff-progressive-container include-fragment[src]', // legacy support
]
const documents: Document[] = [doc]
const selector = fragmentSelectors.find(selector => doc.querySelector(selector))
if (selector) {
// eslint-disable-next-line no-constant-condition
while (true) {
const fragment = doc.querySelector(selector)
if (!(fragment instanceof HTMLElement)) break
const src = fragment.getAttribute('src')
if (!src) break
const fragment = findMapFirst(fragmentSelectors, selector => doc.querySelector(selector))
if (fragment instanceof HTMLElement) {
const src = fragment.getAttribute('src')
if (src) {
// Using `src` without origin below would fail in Firefox if the src is an absolute path
doc = await getDOM(new URL(src, window.location.origin).href)
documents.push(doc)
await continuousLoadFragmentedPagesFromUrl(src, docs)
}
}
return documents
return [new URL(doc.baseURI).pathname, docs]
}
export async function continuousLoadFragmentedPagesFromUrl(url: string, docs: Document[] = []) {
return continuousLoadFragmentedPages(
await getDOM(new URL(url, window.location.origin).href),
docs,
)
}
export function getCommentsMap(commentData: GitHubAPI.PullComments) {

View file

@ -0,0 +1,7 @@
export const findMapFirst = <T, R>(array: T[], map: (item: T) => R): R | null => {
for (const item of array) {
const result = map(item)
if (result) return result
}
return null
}