Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions components/repository/branch/actions.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{{$page := .Page}}{{$branch := .Branch}}
{{if .CanWrite}}
<button class="theme-dropdown-action show-create-branch-modal" data-branch-from="{{$branch.Name}}">{{svg "octicon-git-branch" 16}}{{ctx.Locale.Tr "repo.branch.new_branch"}}</button>
<button class="theme-dropdown-action show-rename-branch-modal" data-is-default-branch="{{if .IsDefault}}true{{else}}false{{end}}" data-old-branch-name="{{$branch.Name}}">{{svg "octicon-pencil" 16}}{{ctx.Locale.Tr "repo.settings.rename_branch"}}</button>
{{end}}
{{if and .IsDefault $page.Permission.IsAdmin (not $page.Repository.IsArchived)}}
<a class="theme-dropdown-action" href="{{$page.RepoLink}}/settings/branches">{{svg "octicon-arrow-switch" 16}}{{ctx.Locale.Tr "repo.settings.branches.switch_default_branch"}}</a>
{{end}}
{{if not $page.DisableDownloadSourceArchives}}
<a class="theme-dropdown-action archive-link" href="{{$page.RepoLink}}/archive/{{PathEscapeSegments $branch.Name}}.zip" rel="nofollow">{{svg "octicon-file-zip" 16}}{{ctx.Locale.Tr "repo.download_zip"}}</a>
<a class="theme-dropdown-action archive-link" href="{{$page.RepoLink}}/archive/{{PathEscapeSegments $branch.Name}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip" 16}}{{ctx.Locale.Tr "repo.download_tar"}}</a>
{{end}}
{{if $page.EnableFeed}}
<a class="theme-dropdown-action" href="{{$page.FeedURL}}/rss/branch/{{PathEscapeSegments $branch.Name}}">{{svg "octicon-rss" 16}}{{ctx.Locale.Tr "rss_feed"}}</a>
{{end}}
240 changes: 240 additions & 0 deletions components/repository/branch/branch.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
/* GitHub's branch tables: a 14px heading over a box whose header names the columns, and a 48px row per branch. */
.github-branches-search {
margin-bottom: 16px;
}

.github-branches-search .theme-search-field {
width: 100%;
}

.theme-branch-section + .theme-branch-section {
margin-top: 24px;
}

.theme-branch-section-title {
margin: 0 0 8px;
font-size: 14px;
font-weight: 600;
line-height: 21px;
}

.theme-branch-box {
border: 1px solid var(--color-light-border);
border-radius: var(--border-radius);
}

.theme-branch-table {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
font-size: 14px;
}

/* The box does not clip, so the row menus can open past it; the header rounds its own corners. */
.theme-branch-table th:first-child {
border-top-left-radius: var(--border-radius);
}

.theme-branch-table th:last-child {
border-top-right-radius: var(--border-radius);
}

.theme-branch-table th {
height: 40px;
padding: 0 8px;
border-bottom: 1px solid var(--color-light-border);
background: var(--color-box-header);
color: var(--color-text-light-1);
font-size: 12px;
font-weight: 600;
text-align: left;
}

.theme-branch-table td {
height: 48px;
padding: 0 8px;
border-top: 1px solid var(--color-light-border);
vertical-align: middle;
}

.theme-branch-table tbody tr:first-child td {
border-top: 0;
}

.theme-branch-table :is(th, td):first-child {
padding-left: 16px;
}

.theme-branch-table :is(th, td):last-child {
padding-right: 16px;
}

.theme-branch-table .theme-branch-updated {
width: 160px;
}

.theme-branch-table .theme-branch-status {
width: 104px;
}

.theme-branch-table .theme-branch-divergence {
width: 144px;
}

.theme-branch-table .theme-branch-pull {
width: 136px;
}

.theme-branch-table .theme-branch-actions {
width: 88px;
}

.theme-branch-name-row,
.theme-branch-updated-inner,
.theme-branch-actions-inner {
display: flex;
align-items: center;
gap: 4px;
min-width: 0;
}

.theme-branch-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

.theme-branch-copy {
flex: none;
color: var(--color-text-light-1);
}

.theme-branch-protected {
display: inline-flex;
color: var(--color-text-light-1);
}

.theme-branch-updated {
color: var(--color-text-light-1);
font-size: 12px;
}

.theme-branch-updated-inner {
gap: 8px;
}

.theme-branch-updated-inner img.ui.avatar {
border-radius: 50%;
}

.theme-branch-deleted .theme-branch-name {
color: var(--color-text-light-1);
text-decoration: line-through;
}

/* Behind and ahead: the counts meet at a divider, each bar growing away from it in proportion to the commits. */
.theme-branch-table th.theme-branch-divergence {
text-align: center;
}

.theme-branch-table th.theme-branch-divergence > span + span::before {
content: "|";
margin: 0 6px;
color: var(--color-light-border);
}

.theme-branch-divergence-graph {
display: grid;
grid-template-columns: 1fr 1fr;
font-size: 12px;
}

.theme-branch-behind,
.theme-branch-ahead {
display: flex;
flex-direction: column;
gap: 2px;
padding: 0 6px;
}

.theme-branch-behind {
align-items: flex-end;
border-right: 1px solid var(--color-light-border);
}

.theme-branch-bar {
display: block;
width: 0;
max-width: 100%;
height: 4px;
background: var(--github-progressBar-track-bgColor);
}

.theme-branch-behind .theme-branch-bar {
border-radius: 6px 0 0 6px;
}

.theme-branch-ahead .theme-branch-bar {
border-radius: 0 6px 6px 0;
}

.theme-branch-pull-link {
display: inline-flex;
align-items: center;
gap: 4px;
color: var(--color-text-light-1);
}

.theme-branch-pull-open .svg {
color: var(--github-fgColor-success);
}

.theme-branch-pull-merged .svg {
color: var(--github-fgColor-done);
}

.theme-branch-pull-closed .svg {
color: var(--color-red);
}

.theme-branch-pull-draft .svg {
color: var(--color-text-light-1);
}

.theme-branch-included {
display: inline-flex;
align-items: center;
gap: 4px;
color: var(--color-text-light-1);
font-size: 12px;
}

.theme-branch-actions-inner {
justify-content: flex-end;
}

.theme-branch-delete:hover {
color: var(--color-red);
}

.theme-branch-actions .theme-dropdown > summary.theme-control {
width: 32px;
padding: 0;
justify-content: center;
color: var(--color-text-light-1);
}

.theme-branch-actions .theme-dropdown-panel {
width: 224px;
}

/* Phones keep the name, the pull request and the actions; GitHub drops the other columns too. */
@media (max-width: 767.98px) {
.theme-branch-table :is(.theme-branch-updated, .theme-branch-status, .theme-branch-divergence) {
display: none;
}

.theme-branch-table .theme-branch-pull {
width: 88px;
}
}
74 changes: 74 additions & 0 deletions components/repository/branch/row.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
{{- /* One branch of components/repository/branch/table: its name, who pushed when, its checks, how far it is behind
and ahead of the default branch, its pull request, then delete or restore and a menu with Forgejo's other actions. */ -}}
{{$page := .Page}}{{$branch := .Branch}}{{$db := $branch.DBBranch}}
{{$canWrite := and $page.IsWriter (not $page.Repository.IsArchived) (not $page.IsMirror)}}
{{$compareLink := ""}}
{{if $page.DefaultBranchBranch}}
{{$headOwner := ""}}{{if ne $page.Repository.Owner.Name $page.Owner.Name}}{{$headOwner = print (PathEscape $page.Owner.Name) ":"}}{{end}}
{{$compareLink = print $page.RepoLink "/compare/" (PathEscapeSegments $page.DefaultBranchBranch.DBBranch.Name) "..." $headOwner (PathEscapeSegments $db.Name)}}
{{end}}
<tr{{if $db.IsDeleted}} class="theme-branch-deleted"{{end}}>
<td class="theme-branch-name-cell">
<div class="theme-branch-name-row">
{{if $db.IsDeleted}}
<span class="theme-branch-name">{{$db.Name}}</span>
{{else}}
<a class="theme-branch-name" href="{{$page.RepoLink}}/src/branch/{{PathEscapeSegments $db.Name}}" title="{{$db.Name}}">{{$db.Name}}</a>
{{end}}
<button class="theme-icon-button theme-branch-copy" data-clipboard-text="{{$db.Name}}" data-tooltip-content="{{ctx.Locale.Tr "copy_branch"}}" aria-label="{{ctx.Locale.Tr "copy_branch"}}">{{svg "octicon-copy" 16}}</button>
{{if $branch.IsProtected}}<span class="theme-branch-protected" data-tooltip-content="{{ctx.Locale.Tr "repo.settings.protected_branch"}}">{{svg "octicon-shield-lock" 16}}</span>{{end}}
</div>
</td>
<td class="theme-branch-updated">
{{if $db.IsDeleted}}
{{ctx.Locale.Tr "repo.branch.deleted_by" $db.DeletedBy.Name}} {{DateUtils.TimeSince $db.DeletedUnix}}
{{else}}
<span class="theme-branch-updated-inner">{{if $db.Pusher}}{{ctx.AvatarUtils.Avatar $db.Pusher 16}}{{end}}<a class="muted" href="{{$page.RepoLink}}/commit/{{PathEscape $db.CommitID}}" title="{{$db.CommitMessage}}">{{DateUtils.TimeSince $db.CommitTime}}</a></span>
{{end}}
</td>
<td class="theme-branch-status">
{{if not $db.IsDeleted}}{{template "repo/commit_statuses" dict "Status" (index $page.CommitStatus $db.CommitID) "Statuses" (index $page.CommitStatuses $db.CommitID)}}{{end}}
</td>
<td class="theme-branch-divergence">
{{if and (not .IsDefault) (not $db.IsDeleted) $page.DefaultBranchBranch}}
{{$total := Eval $branch.CommitsBehind "+" $branch.CommitsAhead}}
<div class="theme-branch-divergence-graph" data-tooltip-content="{{$branch.CommitsAhead}} ahead, {{$branch.CommitsBehind}} behind {{$page.DefaultBranchBranch.DBBranch.Name}}">
<span class="theme-branch-behind"><span class="theme-branch-count">{{$branch.CommitsBehind}}</span><span class="theme-branch-bar"{{if $total}} style="width: {{Eval 100 "*" $branch.CommitsBehind "/" $total}}%"{{end}}></span></span>
<span class="theme-branch-ahead"><span class="theme-branch-count">{{$branch.CommitsAhead}}</span><span class="theme-branch-bar"{{if $total}} style="width: {{Eval 100 "*" $branch.CommitsAhead "/" $total}}%"{{end}}></span></span>
</div>
{{end}}
</td>
<td class="theme-branch-pull">
{{if .IsDefault}}
{{else if not ($page.Permission.CanRead $page.UnitTypePullRequests)}}
{{if $compareLink}}<a class="button theme-control theme-control-small" href="{{$compareLink}}">{{ctx.Locale.Tr "compare.branches.title"}}</a>{{end}}
{{else if or (not $branch.LatestPullRequest) (and $branch.LatestPullRequest.HasMerged $branch.MergeMovedOn)}}
{{if $branch.IsIncluded}}
<span class="theme-branch-included" data-tooltip-content="{{ctx.Locale.Tr "repo.branch.included_desc"}}">{{svg "octicon-git-pull-request" 16}}{{ctx.Locale.Tr "repo.branch.included"}}</span>
{{else if and (not $db.IsDeleted) $page.AllowsPulls (gt $branch.CommitsAhead 0) $compareLink}}
<a class="button theme-control theme-control-small" href="{{$compareLink}}">{{if $page.CanPull}}{{ctx.Locale.Tr "repo.pulls.compare_changes"}}{{else}}{{ctx.Locale.Tr "action.compare_branch"}}{{end}}</a>
{{end}}
{{else}}
{{$pull := $branch.LatestPullRequest}}
{{$state := "open"}}{{$icon := "octicon-git-pull-request"}}{{$label := ctx.Locale.Tr "repo.issues.open_title"}}
{{if $pull.HasMerged}}{{$state = "merged"}}{{$icon = "octicon-git-merge"}}{{$label = ctx.Locale.Tr "repo.pulls.merged"}}
{{else if $pull.Issue.IsClosed}}{{$state = "closed"}}{{$icon = "octicon-git-pull-request-closed"}}{{$label = ctx.Locale.Tr "repo.issues.closed_title"}}
{{else if $pull.IsWorkInProgress ctx}}{{$state = "draft"}}{{$icon = "octicon-git-pull-request-draft"}}{{$label = ctx.Locale.Tr "repo.issues.draft_title"}}{{end}}
<a class="theme-branch-pull-link theme-branch-pull-{{$state}}" href="{{$pull.Issue.Link}}" data-tooltip-content="{{$label}}">{{svg $icon 16}}{{if not $pull.IsSameRepo}}{{$pull.BaseRepo.FullName}}{{end}}#{{$pull.Issue.Index}}</a>
{{end}}
</td>
<td class="theme-branch-actions">
<div class="theme-branch-actions-inner">
{{if and $canWrite (not .IsDefault) (not $branch.IsProtected)}}
{{if $db.IsDeleted}}
<button class="theme-icon-button link-action restore-branch-button" data-url="{{$page.Link}}/restore?branch_id={{$db.ID}}&name={{$db.Name}}&page={{$page.Page.Paginater.Current}}" data-tooltip-content="{{ctx.Locale.Tr "repo.branch.restore" $db.Name}}" aria-label="{{ctx.Locale.Tr "repo.branch.restore" $db.Name}}">{{svg "octicon-reply" 16}}</button>
{{else}}
<button class="theme-icon-button theme-branch-delete delete-button delete-branch-button" data-url="{{$page.Link}}/delete?name={{$db.Name}}&page={{$page.Page.Paginater.Current}}" data-tooltip-content="{{ctx.Locale.Tr "repo.branch.delete" $db.Name}}" aria-label="{{ctx.Locale.Tr "repo.branch.delete" $db.Name}}" data-name="{{$db.Name}}" data-modal-id="delete-branch">{{svg "octicon-trash" 16}}</button>
{{end}}
{{end}}
{{if not $db.IsDeleted}}
{{template "components/common/dropdown/dropdown" dict "Kind" "actions" "Icon" "octicon-kebab-horizontal" "IconOnly" true "Quiet" true "Label" (ctx.Locale.Tr "profile.actions.tooltip") "Items" "components/repository/branch/actions" "Data" (dict "Context" $page.Context "Page" $page "Branch" $db "IsDefault" .IsDefault "CanWrite" $canWrite)}}
{{end}}
</div>
</td>
</tr>
28 changes: 28 additions & 0 deletions components/repository/branch/table.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{{- /* GitHub's branch table: a heading, then a box with column headers and a row per branch. Page is the page context,
Title the heading, and either Default the default branch or Branches the other rows. */ -}}
<section class="theme-branch-section">
<h2 class="theme-branch-section-title">{{.Title}}</h2>
<div class="theme-branch-box">
<table class="theme-branch-table">
<thead>
<tr>
<th scope="col">Branch</th>
<th scope="col" class="theme-branch-updated">Updated</th>
<th scope="col" class="theme-branch-status">Check status</th>
<th scope="col" class="theme-branch-divergence"><span>Behind</span><span>Ahead</span></th>
<th scope="col" class="theme-branch-pull">Pull request</th>
<th scope="col" class="theme-branch-actions"><span class="tw-sr-only">{{ctx.Locale.Tr "profile.actions.tooltip"}}</span></th>
</tr>
</thead>
<tbody>
{{if .Default}}
{{template "components/repository/branch/row" dict "Page" .Page "Branch" .Default "IsDefault" true}}
{{else}}
{{range .Branches}}
{{template "components/repository/branch/row" dict "Page" $.Page "Branch" .}}
{{end}}
{{end}}
</tbody>
</table>
</div>
</section>
17 changes: 17 additions & 0 deletions src/build-output.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,23 @@ describe("Forgejo 15 native integration", () => {
expect(read("templates", "base", "component_assets.tmpl")).toContain("/css/components/pages/commits.css");
});

it("lists branches in GitHub's branch tables with Forgejo's branch actions", () => {
const read = (...parts: string[]) => fs.readFileSync(path.join(ROOT_DIR, ...parts), "utf-8");
const page = read("templates", "repo", "branch", "list.tmpl");
expect(page).not.toContain('template "repo/sub_menu"');
expect(page).toContain('"Default" .DefaultBranchBranch');
expect(page).toContain('id="create-branch-modal"');
expect(page).toContain('id="rename-branch-modal"');
const row = read("components", "repository", "branch", "row.tmpl");
expect(row).toContain("delete-button delete-branch-button");
expect(row).toContain("restore-branch-button");
expect(row).toContain('class="theme-branch-name"');
const actions = read("components", "repository", "branch", "actions.tmpl");
expect(actions).toContain("show-create-branch-modal");
expect(actions).toContain("show-rename-branch-modal");
expect(read("styles", "components", "issues", "issue.ts")).toContain(".theme-branch-name {");
});

it("matches GitHub's repository About, visibility, and code-search details", () => {
const headerStyle = fs.readFileSync(path.join(ROOT_DIR, "styles", "templates", "repo", "header.ts"), "utf-8");
const headerTemplate = fs.readFileSync(path.join(ROOT_DIR, "templates", "repo", "home_header.tmpl"), "utf-8");
Expand Down
5 changes: 3 additions & 2 deletions styles/components/issues/issue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,11 @@ const babel = css`
}
`;

// PR 分支标签
// PR 分支标签, and GitHub's branch name on the branches page
const prBranch = css`
.repository.view.issue .pull-desc code,
#issue-list .branches .branch {
#issue-list .branches .branch,
.theme-branch-name {
color: ${themeVars.github.fgColor.accent};
background-color: ${themeVars.github.bgColor.accent.muted};
border-radius: ${otherThemeVars.border.radius};
Expand Down
Loading
Loading