// SPDX-License-Identifier: AGPL-3.0-or-later package web import ( "strconv" "strings" ) // MRList is the list of the merge requests of a repository: the open ones, // then the merged and closed ones. templ MRList(l Layout, p MRListPage) { @Shell(l, mrListRail(p)) { if p.Live { @LiveSlot("mr-notice") }
if p.Newest == "" {

Open{ itoa(len(p.Open)) }

@mrRows(p.Open, "Open merge requests") if len(p.Open) == 0 {

no open merge requests.

} if p.OpenNote != "" {

{ p.OpenNote }

} }

Merged and closed{ itoa(len(p.Done)) }

@mrRows(p.Done, "Merged and closed merge requests") if len(p.Done) == 0 {

nothing merged or closed yet.

} if p.Older != "" || p.Newest != "" { } } } templ mrRows(rows []MRRow, label string) { if len(rows) > 0 { for _, m := range rows { }
{ m.Number } { m.State } { m.Title } { m.Branches } { m.Author } if m.WhenAt != "" { }
} } templ mrListRail(p MRListPage) {

States

open
awaits review
merged
the target took the branch
closed
refused, branch kept

Open one

if len(p.Branches) > 0 {

compare a branch with { p.Default }:

} else {

there is no other branch to merge yet.

} @CloneBlock(p.CLI)
} // MergeRequest is a merge request: its facts, its description, the diff with // the notes under their lines, the general comments; and in the rail the merge // form, the checks, the settings, the commits and the files. templ MergeRequest(l Layout, p MRPage) { @Shell(l, mrRail(p)) {

{ p.Number } { p.Title }

state
{ p.State } if p.MergedShort != "" { { " · " }{ p.MergedShort }{ " · " + p.MergeMode } }
branch
@branchLink(p.Source, p.SourceHref) { " → " } @branchLink(p.Target, p.TargetHref)
opened
{ p.Opened.Name } if p.Opened.WhenAt != "" { { " · " } }
if p.UpdatedAt != "" {
updated
}
if p.Desc != "" {
@templ.Raw(string(p.Desc))
}
if p.Deleting != "" {

merged. the branch { p.Deleting } is being deleted.

} if p.MergeError != "" { } @LiveSlot("mr-notice") if p.Since != nil { } if p.SinceNote != "" {

{ p.SinceNote }

} @mrBar(p) if p.Invisible {

this diff holds invisible characters (direction controls, zero-width characters). They are shown as <U+XXXX> markers, never applied.

} if p.Note != "" {

{ p.Note }

}
for _, f := range p.Files { @DiffFileView(f, p.DiffView) } if len(p.Files) == 0 {

no changed files.

}

Comments{ itoa(p.NUnresolved) } open / { itoa(p.NComments) }

for _, c := range p.Loose { @commentView(c, "") } for _, c := range p.General { @commentView(c, "") } if p.NComments == 0 {

no comments yet.

} if p.GeneralForm != nil { @commentForm(*p.GeneralForm, "") } } } templ branchLink(name, href string) { if href != "" { { name } } else { { name } } } // lineNotes are the comments under a line of the diff, and the form to write // one when the reader asked for it. The diff is in the page twice (unified and // split); the suffix keeps the ids unique, the unified table having none. templ lineNotes(notes []CommentView, form *CommentForm, suffix string) { for _, c := range notes { @commentView(c, suffix) } if form != nil { @commentForm(*form, suffix) } } // commentView is a note: a ruled block, under its line in the diff or in the // list of comments. Body comes only from the safe Markdown renderer. templ commentView(c CommentView, suffix string) {

{ c.Author } if c.WhenAt != "" { { " " } } if c.Resolved { { " " }resolved } if c.Outdated { { " " }outdated } if c.Where != "" { { " " }{ c.Where } }

if len(c.Parts) > 0 {

@userText(c.Parts)

} else if c.Deleted { if c.Verdict != "" {

text deleted

} else {

deleted note

} } else {
if suffix == "-s" { @templ.Raw(string(c.BodySplit)) } else { @templ.Raw(string(c.Body)) }
} if c.Edited { edited } if c.Verdict != "" { { c.Verdict } } if c.Range != "" { { c.Range } } if c.Quote != "" { } if c.Edit != nil {
Edit
} if c.Delete != nil {
Delete

Delete this note?

} for _, reply := range c.Replies { @commentView(reply, suffix) } if c.Reply != nil {
Reply@commentForm(*c.Reply, suffix)
} if c.Resolve != nil {
if c.Resolve.From > 0 { }
}
} func boolText(b bool) string { if b { return "true" } return "false" } // commentForm writes a comment, general or on a line. templ commentForm(f CommentForm, suffix string) {
if f.Path != "" { } if f.ReplyTo != "" { } if f.Path == "" && f.ReplyTo == "" && !f.Plain {
Verdict
} if f.From > 0 { }
if f.Cancel != "" { Cancel }
} templ mrRail(p MRPage) {

Files { itoa(len(p.Files)) }

@FileList(p.Files)
if len(p.Verdicts) > 0 {

Verdicts

} @MRChecks(p.Checks)

Commits { itoa(len(p.Commits)) }

if len(p.Commits) == 0 {

no commit to merge.

} if p.CommitsNote != "" {

{ p.CommitsNote }

}

Settings

modes
{ strings.Join(p.Modes, ", ") }
checks
if p.ChecksBlock { failing ones block the merge } else { do not block the merge }
} // mrBar is the bar above the diff, which follows the review: the totals, the // layout of the diff, the state of the checks and, for the owner of an open // request, the merge menu and the other actions. Each menu is a
: it // works without scripts; with them, M and the keys of the menu drive it. templ mrBar(p MRPage) {
{ plural(p.NFiles, "file", "files") } · +{ itoa(p.Adds) } -{ itoa(p.Dels) } @diffViewMenu(p.DiffView) @MRChecksSum(p.ChecksSum) if p.Box != nil { @mergeMenu(p) @moreMenu(p) }
} // mergeMenu is the merge of an open request, for the owner: the form (it // carries the head that was read), or "Ready for review" for a draft. The // mode and the branch deletion have keys (1-3, D) while it is open. // // The badge is a live slot (mr-mergeability); the modes and the button read // its data-* attributes (hx-live bindings), so that they follow the slot when // the stream replaces its content. The server renders the same state in the // attributes, for a page without scripts. templ mergeMenu(p MRPage) { } // MRMergeability is the content of the mergeability slot: the badge, and the // state the controls of the merge box read. templ MRMergeability(v MergeabilityView) { { v.Label } if v.Detail != "" { { " " }{ v.Detail } } if len(v.Paths) > 0 { } if v.PathsNote != "" {

{ v.PathsNote }

} } // mrMergeabilitySlot is the status region of the badge, with its content: the // stream replaces the children, never the element (see LiveSlot). templ mrMergeabilitySlot(v MergeabilityView) {
@MRMergeability(v)
} // MRMergeabilityFragment is the slot as the stream sends it. templ MRMergeabilityFragment(v MergeabilityView) { @mrMergeabilitySlot(v) } // moreMenu is what else the owner does with an open request: mark the head // read, close it, merge it from a terminal. templ moreMenu(p MRPage) { } templ Compare(l Layout, p ComparePage) { @Shell(l, compareRail(p)) {

{ p.Source } → { p.Target }

branch
{ p.Source }
target
{ p.Target }
ahead
{ p.AheadText }
if p.Note != "" {

{ p.Note }

} if p.Existing != nil {

an open merge request already covers these branches: { p.Existing.Label }

} if p.Create != nil {
} if p.HasDiff {
{ plural(p.NFiles, "file", "files") } · +{ itoa(p.Adds) } -{ itoa(p.Dels) } @diffViewMenu(p.DiffView)
if p.Invisible {

this diff holds invisible characters (direction controls, zero-width characters). They are shown as <U+XXXX> markers, never applied.

}
for _, f := range p.Files { @DiffFileView(f, p.DiffView) }
} } } templ compareRail(p ComparePage) {

Commits { itoa(len(p.Ahead)) }

if len(p.Ahead) == 0 {

no commit ahead of { p.Target }.

}
if p.HasDiff {

Files { itoa(len(p.Files)) }

@FileList(p.Files)
}

From a terminal

@CloneBlock(p.CLI)
}