diff --git a/components/project/list/list.css b/components/common/box-list/box-list.css similarity index 59% rename from components/project/list/list.css rename to components/common/box-list/box-list.css index ac1f6a8b..523d4d86 100644 --- a/components/project/list/list.css +++ b/components/common/box-list/box-list.css @@ -1,30 +1,31 @@ -/* GitHub's project list: a toolbar, then a box whose header holds the state tabs and Sort, with a row per project. */ -.theme-project-list-toolbar { +/* GitHub's Box list, as on its projects, milestones and labels pages: a toolbar, then a box whose header holds the + state tabs (or a count) and Sort, with a row per item. */ +.theme-box-list-toolbar { display: flex; align-items: center; gap: 8px; margin-bottom: 16px; } -.theme-project-list > .theme-list-heading { +.theme-box-list-page > .theme-list-heading { margin-bottom: 16px; } -.theme-project-list-search { +.theme-box-list-search { flex: 1; min-width: 0; } -.theme-project-list-search .theme-search-field { +.theme-box-list-search .theme-search-field { width: 100%; } -.theme-project-list-box { +.theme-box-list { border: 1px solid var(--color-light-border); border-radius: var(--border-radius); } -.theme-project-list-header { +.theme-box-list-header { display: flex; align-items: center; justify-content: space-between; @@ -36,14 +37,14 @@ background: var(--color-box-header); } -.theme-project-list-states { +.theme-box-list-states { display: flex; align-items: center; gap: 8px; min-width: 0; } -.theme-project-list-states > a { +.theme-box-list-states > a { display: inline-flex; align-items: center; gap: 8px; @@ -56,46 +57,55 @@ white-space: nowrap; } -.theme-project-list-states > a:hover { +.theme-box-list-states > a:hover { color: var(--color-text); background: var(--github-control-transparent-bgColor-hover); text-decoration: none; } -.theme-project-list-states > a[aria-current] { +.theme-box-list-states > a[aria-current] { color: var(--color-text); font-weight: 600; } -.theme-project-list-states .theme-counter { +/* A header that counts instead of switching state, as on GitHub's labels page. */ +.theme-box-list-title { + margin: 0; + padding: 0 8px; + font-size: 14px; + font-weight: 600; + line-height: 30px; +} + +.theme-box-list-states .theme-counter { font-weight: 600; } -.theme-project-list-header > .theme-dropdown > summary.theme-control { +.theme-box-list-header > .theme-dropdown > summary.theme-control { font-weight: 400; } -.theme-project-list-header .theme-dropdown-panel { +.theme-box-list-header .theme-dropdown-panel { width: 240px; } /* GitHub's Sort menu marks the current order with a check and keeps the labels aligned. */ -.theme-project-list-check { +.theme-box-list-check { flex: none; visibility: hidden; } -.theme-dropdown-action[aria-current] > .theme-project-list-check { +.theme-dropdown-action[aria-current] > .theme-box-list-check { visibility: visible; } -.theme-project-list-rows { +.theme-box-list-rows { margin: 0; padding: 0; list-style: none; } -.theme-project-list-row { +.theme-box-list-row { display: flex; align-items: center; gap: 16px; @@ -105,16 +115,16 @@ border-top: 1px solid var(--color-light-border); } -.theme-project-list-row:first-child { +.theme-box-list-row:first-child { border-top: 0; } -.theme-project-list-main { +.theme-box-list-main { flex: 1; min-width: 0; } -.theme-project-list-main h3 { +.theme-box-list-main h3 { margin: 0; font-size: 16px; font-weight: 500; @@ -122,47 +132,47 @@ overflow-wrap: anywhere; } -.theme-project-list-main h3 > a { +.theme-box-list-main h3 > a { color: var(--color-text); } -.theme-project-list-main h3 > a:hover { +.theme-box-list-main h3 > a:hover { color: var(--color-primary); } -.theme-project-list-meta, -.theme-project-list-description { +.theme-box-list-meta, +.theme-box-list-description { margin: 0; color: var(--color-text-light-1); font-size: 12px; line-height: 18px; } -.theme-project-list-description.markup { +.theme-box-list-description.markup { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.theme-project-list-description.markup > * { +.theme-box-list-description.markup > * { display: inline; margin: 0; font-size: inherit; } -.theme-project-list-row > .theme-dropdown > summary.theme-control { +.theme-box-list-row > .theme-dropdown > summary.theme-control { width: 32px; padding: 0; justify-content: center; color: var(--color-text-light-1); } -.theme-project-list-row > .theme-dropdown .theme-dropdown-panel { +.theme-box-list-row > .theme-dropdown .theme-dropdown-panel { width: 192px; } @media (max-width: 543.98px) { - .theme-project-list-row { + .theme-box-list-row { margin: 0 8px; } } diff --git a/components/common/box-list/states.tmpl b/components/common/box-list/states.tmpl new file mode 100644 index 00000000..85bb2555 --- /dev/null +++ b/components/common/box-list/states.tmpl @@ -0,0 +1,6 @@ +{{- /* The Open and Closed tabs of a box list header, each with its count. Label names the navigation; OpenLink, +ClosedLink, OpenCount and ClosedCount fill the tabs, and IsClosed marks the closed one as current. */ -}} + diff --git a/components/common/dropdown/dropdown.js b/components/common/dropdown/dropdown.js index 8d8b7b12..8e278383 100644 --- a/components/common/dropdown/dropdown.js +++ b/components/common/dropdown/dropdown.js @@ -11,14 +11,19 @@ dropdown.open = false; if (restoreFocus) dropdown.querySelector("summary")?.focus(); }; - document.addEventListener("click", event => { - const close = event.target.closest("[data-theme-dropdown-close]"); - if (close) closeDropdown(close.closest(".theme-dropdown"), true); - const action = event.target.closest('[data-theme-dropdown="actions"] .theme-dropdown-action'); - if (action && !action.disabled) closeDropdown(action.closest(".theme-dropdown"), false); - for (const dropdown of document.querySelectorAll(".theme-dropdown[open]")) - if (!dropdown.contains(event.target)) closeDropdown(dropdown, false); - }); + // Capture: Forgejo's own handlers on menu actions (Edit label, for one) stop the click from bubbling. + document.addEventListener( + "click", + event => { + const close = event.target.closest("[data-theme-dropdown-close]"); + if (close) closeDropdown(close.closest(".theme-dropdown"), true); + const action = event.target.closest('[data-theme-dropdown="actions"] .theme-dropdown-action'); + if (action && !action.disabled) closeDropdown(action.closest(".theme-dropdown"), false); + for (const dropdown of document.querySelectorAll(".theme-dropdown[open]")) + if (!dropdown.contains(event.target)) closeDropdown(dropdown, false); + }, + true + ); document.addEventListener( "change", event => { diff --git a/components/common/progress/progress.css b/components/common/progress/progress.css new file mode 100644 index 00000000..fea040ed --- /dev/null +++ b/components/common/progress/progress.css @@ -0,0 +1,24 @@ +/* GitHub's Progress bar: 8px on the progress track, filled with the success color. Add theme-progress to a native + , so its value stays readable without the theme. */ +progress.theme-progress { + display: block; + width: 100%; + height: 8px; + border: 0; + border-radius: var(--border-radius); + background: var(--github-progressBar-track-bgColor); + overflow: hidden; + appearance: none; +} + +progress.theme-progress::-webkit-progress-bar { + background: var(--github-progressBar-track-bgColor); +} + +progress.theme-progress::-webkit-progress-value { + background: var(--github-bgColor-success-emphasis); +} + +progress.theme-progress::-moz-progress-bar { + background: var(--github-bgColor-success-emphasis); +} diff --git a/components/project/list/list.tmpl b/components/project/list/list.tmpl index 0a2f34db..b3c2ef13 100644 --- a/components/project/list/list.tmpl +++ b/components/project/list/list.tmpl @@ -5,36 +5,33 @@ the search; without it, New project sits beside the search as on GitHub's profil {{$canWrite := and $data.CanWriteProjects (not $data.Repository.IsArchived)}} {{$newProject := ""}} {{if $canWrite}}{{$newProject = print $data.Link "/new"}}{{end}} -
+
{{if .Heading}}

{{ctx.Locale.Tr "repo.projects"}}

{{if $newProject}}{{ctx.Locale.Tr "repo.projects.new"}}{{end}}
{{end}} -
- -
-
- +
+
+ {{template "components/common/box-list/states" dict "Label" (ctx.Locale.Tr "repo.projects") "OpenLink" (print "?state=open&q=" (QueryEscape $data.Keyword) "&sort=" $data.SortType) "ClosedLink" (print "?state=closed&q=" (QueryEscape $data.Keyword) "&sort=" $data.SortType) "OpenCount" $data.OpenCount "ClosedCount" $data.ClosedCount "IsClosed" $data.IsShowClosed}} {{template "components/common/dropdown/dropdown" dict "Kind" "actions" "Label" (ctx.Locale.Tr "repo.issues.filter_sort") "Quiet" true "Items" "components/project/list/sort" "Data" $data}}
-
    +
      {{range $data.Projects}} -
    • -
      +
    • +

      {{.Title}}

      -

      #{{.ID}} {{ctx.Locale.Tr "issues.updated" (DateUtils.TimeSince .UpdatedUnix)}}

      - {{if .Description}}
      {{.RenderedContent}}
      {{end}} +

      #{{.ID}} {{ctx.Locale.Tr "issues.updated" (DateUtils.TimeSince .UpdatedUnix)}}

      + {{if .Description}}
      {{.RenderedContent}}
      {{end}}
      {{if $canWrite}} {{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/project/list/actions" "Data" (dict "Context" $data.Context "Link" (.Link ctx) "IsClosed" .IsClosed)}} diff --git a/components/project/list/sort.tmpl b/components/project/list/sort.tmpl index 22edf956..f92587bc 100644 --- a/components/project/list/sort.tmpl +++ b/components/project/list/sort.tmpl @@ -1,5 +1,5 @@ {{$query := print "?state=" .State "&q=" .Keyword}} -{{svg "octicon-check" 16 "theme-project-list-check"}}{{ctx.Locale.Tr "issues.search.sort.latest"}} -{{svg "octicon-check" 16 "theme-project-list-check"}}{{ctx.Locale.Tr "issues.search.sort.oldest"}} -{{svg "octicon-check" 16 "theme-project-list-check"}}{{ctx.Locale.Tr "issues.search.sort.recentupdate"}} -{{svg "octicon-check" 16 "theme-project-list-check"}}{{ctx.Locale.Tr "issues.search.sort.leastupdate"}} +{{svg "octicon-check" 16 "theme-box-list-check"}}{{ctx.Locale.Tr "issues.search.sort.latest"}} +{{svg "octicon-check" 16 "theme-box-list-check"}}{{ctx.Locale.Tr "issues.search.sort.oldest"}} +{{svg "octicon-check" 16 "theme-box-list-check"}}{{ctx.Locale.Tr "issues.search.sort.recentupdate"}} +{{svg "octicon-check" 16 "theme-box-list-check"}}{{ctx.Locale.Tr "issues.search.sort.leastupdate"}} diff --git a/components/repository/label/actions.tmpl b/components/repository/label/actions.tmpl new file mode 100644 index 00000000..8b8d3e0f --- /dev/null +++ b/components/repository/label/actions.tmpl @@ -0,0 +1,3 @@ +{{$label := .Label}} +{{svg "octicon-pencil" 16}}{{ctx.Locale.Tr "repo.issues.label_edit"}} +{{svg "octicon-trash" 16}}{{ctx.Locale.Tr "repo.issues.label_delete"}} diff --git a/components/repository/label/list.css b/components/repository/label/list.css new file mode 100644 index 00000000..28990e8d --- /dev/null +++ b/components/repository/label/list.css @@ -0,0 +1,86 @@ +/* GitHub's label rows: the label in a quarter of the row, the description, the open issues, then the menu. */ +.theme-label-row { + min-height: 56px; + padding: 12px 0; +} + +.theme-label-name { + display: flex; + flex: 0 0 25%; + align-items: center; + gap: 8px; + min-width: 0; +} + +.theme-label-description { + flex: 1; + min-width: 0; + color: var(--color-text-light-1); + font-size: 12px; + line-height: 18px; + overflow-wrap: anywhere; +} + +.theme-label-issues { + display: inline-flex; + flex: 0 0 auto; + align-items: center; + gap: 4px; + color: var(--color-text-light-1); + font-size: 12px; + line-height: 18px; +} + +.theme-label-issues:hover { + color: var(--github-fgColor-accent); +} + +/* Rows without the menu keep the issue links in line with those that have it. */ +.theme-label-actions-placeholder { + flex: 0 0 32px; +} + +.theme-label-list-note { + min-height: 0; + color: var(--color-text-light-1); + font-size: 12px; + line-height: 18px; +} + +.theme-label-list-template { + padding: 16px; + border-bottom: 1px solid var(--color-light-border); +} + +/* Phones: the label and its menu on one line, the description and the open issues beneath. */ +@media (max-width: 767.98px) { + .theme-label-row { + display: grid; + grid-template-areas: + "name actions" + "description description" + "issues issues"; + grid-template-columns: minmax(0, 1fr) auto; + gap: 4px 16px; + } + + .theme-label-name { + grid-area: name; + } + + .theme-label-description { + grid-area: description; + } + + .theme-label-description:empty { + display: none; + } + + .theme-label-issues { + grid-area: issues; + } + + .theme-label-row > :is(.theme-dropdown, .theme-label-actions-placeholder) { + grid-area: actions; + } +} diff --git a/components/repository/label/list.tmpl b/components/repository/label/list.tmpl new file mode 100644 index 00000000..fdc4adc9 --- /dev/null +++ b/components/repository/label/list.tmpl @@ -0,0 +1,28 @@ +{{- /* GitHub's label list: a box headed by the label count and Sort, with a row per label holding the label, its +description, its open issues and, for writers, a menu with Edit and Delete. The repository labels page and the +organization's label settings both render it; Forgejo's label scripts find the edit and delete buttons by class. */ -}} +{{$canWrite := or .PageIsOrgSettingsLabels (and (not .Repository.IsArchived) (or .CanWriteIssues .CanWritePulls))}} +{{$issuesLink := print .RepoLink "/issues"}}{{if .PageIsOrgSettingsLabels}}{{$issuesLink = print AppSubUrl "/issues"}}{{end}} +
      +
      +

      {{ctx.Locale.Tr "repo.issues.label_count" .NumLabels}}

      + {{template "components/common/dropdown/dropdown" dict "Kind" "actions" "Label" (ctx.Locale.Tr "repo.issues.filter_sort") "Quiet" true "Items" "components/repository/label/sort" "Data" .}} +
      + {{if and $canWrite (eq .NumLabels 0) (or .PageIsOrgSettingsLabels (not .OrgLabels))}} +
      {{template "repo/issue/labels/label_load_template" .}}
      + {{end}} +
        + {{range .Labels}} + {{template "components/repository/label/row" dict "Context" $.Context "Label" . "IssuesLink" $issuesLink "OpenIssues" .NumOpenIssues "CanWrite" $canWrite "DeleteLink" (print $.Link "/delete")}} + {{end}} + {{if and (not .PageIsOrgSettingsLabels) .OrgLabels}} +
      • + {{ctx.Locale.Tr "repo.org_labels_desc"}} + {{if .IsOrganizationOwner}}{{ctx.Locale.Tr "repo.org_labels_desc_manage"}}{{end}} +
      • + {{range .OrgLabels}} + {{template "components/repository/label/row" dict "Context" $.Context "Label" . "IssuesLink" $issuesLink "OpenIssues" .NumOpenRepoIssues}} + {{end}} + {{end}} +
      +
      diff --git a/components/repository/label/row.tmpl b/components/repository/label/row.tmpl new file mode 100644 index 00000000..58275d19 --- /dev/null +++ b/components/repository/label/row.tmpl @@ -0,0 +1,16 @@ +{{- /* One row of components/repository/label/list. Label is the label; IssuesLink and OpenIssues link its open issues; +CanWrite adds the Edit and Delete menu, DeleteLink being where Delete posts. */ -}} +{{$label := .Label}} +
    • +
      + {{template "components/repository/label/label" $label}} + {{template "repo/issue/labels/label_archived" $label}} +
      +
      {{if $label.Description}}{{$label.Description | RenderEmoji ctx}}{{end}}
      + {{svg "octicon-issue-opened" 16}}{{ctx.Locale.Tr "repo.issues.label_open_issues" .OpenIssues}} + {{if .CanWrite}} + {{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/label/actions" "Data" (dict "Context" .Context "Label" $label "DeleteLink" .DeleteLink)}} + {{else}} + + {{end}} +
    • diff --git a/components/repository/label/sort.tmpl b/components/repository/label/sort.tmpl new file mode 100644 index 00000000..bf19c5b1 --- /dev/null +++ b/components/repository/label/sort.tmpl @@ -0,0 +1,5 @@ +{{$query := print "?state=" .State}} +{{svg "octicon-check" 16 "theme-box-list-check"}}{{ctx.Locale.Tr "repo.issues.label.filter_sort.alphabetically"}} +{{svg "octicon-check" 16 "theme-box-list-check"}}{{ctx.Locale.Tr "repo.issues.label.filter_sort.reverse_alphabetically"}} +{{svg "octicon-check" 16 "theme-box-list-check"}}{{ctx.Locale.Tr "milestone.filter_sort.least_issues"}} +{{svg "octicon-check" 16 "theme-box-list-check"}}{{ctx.Locale.Tr "milestone.filter_sort.most_issues"}} diff --git a/components/repository/milestone/list.tmpl b/components/repository/milestone/list.tmpl new file mode 100644 index 00000000..dd7bdaa4 --- /dev/null +++ b/components/repository/milestone/list.tmpl @@ -0,0 +1,61 @@ +{{- /* GitHub's milestone list, for a repository and for the dashboard: a box with the Open and Closed tabs and Sort in +its header, and a row per milestone with its due date and description on the left and its progress on the right. +Data is the page context. Query starts every state and sort link ("?q=..." plus any page filter). ShowRepo names each +milestone's repository, as the dashboard lists several. RepoLink and CanWrite add Edit, Close and Delete for a +repository's writers. */ -}} +{{$data := .Data}} +{{$query := .Query}} +
      +
      + {{template "components/common/box-list/states" dict "Label" (ctx.Locale.Tr "repo.milestones") "OpenLink" (print $query "&state=open&sort=" $data.SortType) "ClosedLink" (print $query "&state=closed&sort=" $data.SortType) "OpenCount" .OpenCount "ClosedCount" .ClosedCount "IsClosed" $data.IsShowClosed}} + {{template "components/common/dropdown/dropdown" dict "Kind" "actions" "Label" (ctx.Locale.Tr "repo.issues.filter_sort") "Quiet" true "Items" "components/repository/milestone/sort" "Data" (dict "Context" $data.Context "Query" (print $query "&state=" $data.State) "SortType" $data.SortType)}} +
      +
        + {{range $data.Milestones}} + {{$link := print $.RepoLink "/milestone/" .ID}} + {{if $.ShowRepo}}{{$link = print .Repo.Link "/milestone/" .ID}}{{end}} +
      • +
        +

        {{if $.ShowRepo}}{{.Repo.FullName}}{{end}}{{.Name}}

        +

        + {{if .IsClosed}} + {{ctx.Locale.Tr "repo.milestones.closed" (DateUtils.TimeSince .ClosedDateUnix)}} + {{else if .DeadlineString}} + {{svg "octicon-calendar" 14}} {{DateUtils.AbsoluteShort (.DeadlineString|DateUtils.ParseLegacy)}} + {{else}} + {{ctx.Locale.Tr "repo.milestones.no_due_date"}} + {{end}} + {{if .UpdatedUnix}} {{ctx.Locale.Tr "repo.milestones.update_ago" (DateUtils.TimeSince .UpdatedUnix)}}{{end}} + {{if .TotalTrackedTime}} {{svg "octicon-clock" 14}} {{.TotalTrackedTime|Sec2Time}}{{end}} +

        + {{if .Content}}
        {{.RenderedContent}}
        {{end}} +
        +
        + +

        + {{ctx.Locale.Tr "repo.milestones.completeness" .Completeness}} + {{ctx.Locale.PrettyNumber .NumOpenIssues}} {{ctx.Locale.Tr "repo.issues.open_title"}} + {{ctx.Locale.PrettyNumber .NumClosedIssues}} {{ctx.Locale.Tr "repo.issues.closed_title"}} +

        + {{if $.CanWrite}} +

        + {{ctx.Locale.Tr "repo.issues.label_edit"}} + {{if .IsClosed}} + {{ctx.Locale.Tr "repo.milestones.open"}} + {{else}} + {{ctx.Locale.Tr "repo.milestones.close"}} + {{end}} + {{ctx.Locale.Tr "repo.issues.label_delete"}} +

        + {{end}} +
        +
      • + {{else}} +
      • + {{svg "octicon-milestone" 24}} +

        {{ctx.Locale.Tr "search.no_results"}}

        +
      • + {{end}} +
      +
      +{{template "base/paginate" $data}} diff --git a/components/repository/milestone/milestone.css b/components/repository/milestone/milestone.css new file mode 100644 index 00000000..85c3e7bb --- /dev/null +++ b/components/repository/milestone/milestone.css @@ -0,0 +1,101 @@ +/* GitHub's milestone row: the title, dates and description on the left; on the right a third of the row for the + progress bar, the completion and issue counts, and the writer's Edit, Close and Delete. */ +.theme-milestone-row { + align-items: flex-start; + gap: 24px; + padding: 16px 0; +} + +.theme-milestone-row .theme-box-list-main h3 { + font-weight: 600; +} + +.theme-milestone-repo { + margin-right: 8px; + color: var(--color-text-light-1); + font-weight: 400; +} + +.theme-milestone-row .theme-box-list-meta { + margin-top: 4px; +} + +.theme-milestone-row .theme-box-list-meta .svg { + vertical-align: text-bottom; +} + +.theme-milestone-overdue { + color: var(--color-red); +} + +.theme-milestone-row .theme-box-list-description { + margin-top: 8px; + color: var(--color-text); + font-size: 14px; + line-height: 21px; +} + +.theme-milestone-progress { + flex: 0 0 33%; + min-width: 0; + padding-top: 4px; +} + +.theme-milestone-stats, +.theme-milestone-actions { + display: flex; + flex-wrap: wrap; + gap: 4px 16px; + margin: 8px 0 0; + color: var(--color-text-light-1); + font-size: 14px; + line-height: 21px; +} + +.theme-milestone-stats strong { + color: var(--color-text); + font-weight: 600; +} + +.theme-milestone-actions > a { + color: var(--github-fgColor-accent); +} + +.theme-milestone-actions > .theme-milestone-delete { + color: var(--color-red); +} + +@media (max-width: 767.98px) { + .theme-milestone-row { + flex-direction: column; + gap: 12px; + } + + .theme-milestone-progress { + flex-basis: auto; + width: 100%; + } +} + +/* One milestone's page: its due date, description and progress between the heading and the issue list. */ +.theme-milestone-summary { + margin-bottom: 16px; +} + +.theme-milestone-summary-meta { + display: flex; + flex-wrap: wrap; + gap: 4px 16px; + margin: 0 0 8px; + color: var(--color-text-light-1); + font-size: 14px; + line-height: 21px; +} + +.theme-milestone-summary-meta .svg { + vertical-align: text-bottom; +} + +.theme-milestone-summary > .markup { + margin-bottom: 16px; +} diff --git a/components/repository/milestone/sort.tmpl b/components/repository/milestone/sort.tmpl new file mode 100644 index 00000000..d0e8430a --- /dev/null +++ b/components/repository/milestone/sort.tmpl @@ -0,0 +1,8 @@ +{{$query := .Query}} +{{svg "octicon-check" 16 "theme-box-list-check"}}{{ctx.Locale.Tr "milestone.filter_sort.earliest_due_data"}} +{{svg "octicon-check" 16 "theme-box-list-check"}}{{ctx.Locale.Tr "milestone.filter_sort.latest_due_date"}} +{{svg "octicon-check" 16 "theme-box-list-check"}}{{ctx.Locale.Tr "milestone.filter_sort.least_complete"}} +{{svg "octicon-check" 16 "theme-box-list-check"}}{{ctx.Locale.Tr "milestone.filter_sort.most_complete"}} +{{svg "octicon-check" 16 "theme-box-list-check"}}{{ctx.Locale.Tr "milestone.filter_sort.most_issues"}} +{{svg "octicon-check" 16 "theme-box-list-check"}}{{ctx.Locale.Tr "milestone.filter_sort.least_issues"}} +{{svg "octicon-check" 16 "theme-box-list-check"}}{{ctx.Locale.Tr "milestone.filter_sort.name"}} diff --git a/src/build-output.test.ts b/src/build-output.test.ts index 1e069fb5..296e530d 100644 --- a/src/build-output.test.ts +++ b/src/build-output.test.ts @@ -457,7 +457,7 @@ describe("Forgejo 15 native integration", () => { const compactMenu = fs.readFileSync(path.join(ROOT_DIR, "styles", "public", "menu", "compact_menu.ts"), "utf-8"); const notification = fs.readFileSync(path.join(ROOT_DIR, "styles", "components", "notification.ts"), "utf-8"); - expect(compactMenu).toContain(".page-content.user.notification:not(#notification_div) > .ui.container,"); + expect(compactMenu).toContain(".page-content.user.notification:not(#notification_div) > .ui.container {"); expect(compactMenu).not.toContain(".ui.container:has("); expect(compactMenu).not.toContain(".page-content.user.notification > .ui.container,"); expect(notification).toContain("--switch-item-min-height: 32px;"); @@ -511,6 +511,39 @@ describe("Forgejo 15 native integration", () => { expect(read(...page)).not.toContain('template "repo/header"'); }); + it("lists labels and milestones in the issue sidebar layout with the shared box list", () => { + const read = (...parts: string[]) => fs.readFileSync(path.join(ROOT_DIR, ...parts), "utf-8"); + const sidebar = read("templates", "repo", "issue", "sidebar.tmpl"); + expect(sidebar).toContain('{{if $isMilestonePage}}active{{end}}" href="{{.RepoLink}}/milestones"'); + expect(sidebar).toContain('{{if .PageIsLabels}}active{{end}}" href="{{.RepoLink}}/labels"'); + for (const page of ["labels.tmpl", "milestones.tmpl", "milestone_issues.tmpl", "milestone_new.tmpl"]) { + const template = read("templates", "repo", "issue", page); + expect(template, page).toContain('{{template "repo/issue/sidebar" .}}'); + expect(template, page).not.toContain('template "repo/issue/navbar"'); + } + expect(read("templates", "repo", "issue", "milestones.tmpl")).toContain( + 'template "components/repository/milestone/list"' + ); + expect(read("templates", "user", "dashboard", "milestones.tmpl")).toContain( + 'template "components/repository/milestone/list"' + ); + expect(read("templates", "repo", "issue", "labels", "label_list.tmpl")).toContain( + 'template "components/repository/label/list"' + ); + expect(read("templates", "repo", "issue", "milestone_issues.tmpl")).toContain('"themeList" "issues"'); + // The project, milestone and label lists share GitHub's Box list and its Open/Closed tabs. + for (const list of [ + ["components", "project", "list", "list.tmpl"], + ["components", "repository", "milestone", "list.tmpl"], + ["components", "repository", "label", "list.tmpl"], + ]) + expect(read(...list)).toContain('class="theme-box-list'); + expect(read("components", "repository", "label", "actions.tmpl")).toContain("edit-label-button"); + expect(read("templates", "base", "component_assets.tmpl")).toContain( + "/css/components/common/box-list/box-list.css" + ); + }); + 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"); diff --git a/src/types/color/github.ts b/src/types/color/github.ts index 8e671727..fc44c463 100644 --- a/src/types/color/github.ts +++ b/src/types/color/github.ts @@ -73,6 +73,7 @@ export const github = { /** 成功的背景颜色 * @issue `babel` 重新开启图标背景颜色 * @issue `prMerge` 合并提交的图标背景色 + * @components `common/progress` GitHub 进度条的填充色 */ emphasis: null, }, @@ -383,6 +384,7 @@ export const github = { track: { /** 进度条轨道背景色, 默认为 #0000, 高对比度专属颜色 * @repo_sidebar `repoSidebarBottom` 语言统计条的背景色 + * @components `common/progress` GitHub 进度条的轨道色 */ bgColor: null, /** 进度条轨道边框色, 默认为 #0000, 高对比度专属颜色 diff --git a/styles/components/issues/issue_list.ts b/styles/components/issues/issue_list.ts index 3e6fff93..66eb880e 100644 --- a/styles/components/issues/issue_list.ts +++ b/styles/components/issues/issue_list.ts @@ -21,8 +21,6 @@ import { css, cssCombine, extractVarName, otherThemeVars, themeVars } from "@lut // 工单&PR 列表 const issueList = css` - /* 仓库页面的里程碑列表菜单栏 */ - .page-content.repository.milestones, .page-content.repository.milestone-issue-list, .page-content.repository.issue-list { ${extractVarName(otherThemeVars.checkbox.size)}: 16px; @@ -109,39 +107,11 @@ const issueList = css` } } } - - /* 里程碑列表 */ - /* [TODO] 暂时排除项目的列表 */ - .page-content.repository.milestones:not(.projects) .milestone-list, - /* 仪表板的里程碑列表 */ - .page-content.repository.milestones.dashboard .milestone-list { - border: 1px solid ${themeVars.color.light.border}; - border-bottom-left-radius: ${otherThemeVars.border.radius}; - border-bottom-right-radius: ${otherThemeVars.border.radius}; - .milestone-card { - padding: 8px 16px 10px; - .milestone-header h3 > a { - font-size: 16px; - font-weight: 500; - } - .milestone-header > .tw-flex > span { - font-size: 14px; - font-weight: 600; - } - } - .milestone-toolbar { - font-size: 12px; - .group > a { - font-size: 13px; - } - } - } `; // 避免手机/平板下菜单错位 const issueListMobile = css` @media (max-width: 1023.98px) { - .page-content.repository.milestones, .page-content.repository.milestone-issue-list, .page-content.repository.issue-list { .issue-list-toolbar { diff --git a/styles/components/repo/index.ts b/styles/components/repo/index.ts index 33e2eece..dc0f0437 100644 --- a/styles/components/repo/index.ts +++ b/styles/components/repo/index.ts @@ -23,7 +23,6 @@ import diff from "./diff"; import fileTree from "../file_tree"; import sourceBrowser from "./source_browser"; import filePicker from "./file_picker"; -import milestones from "./milestones"; import packages from "./packages"; import release from "./release"; import repo from "./repo"; @@ -37,7 +36,6 @@ export default cssCombine( filePicker, fileTree, sourceBrowser, - milestones, packages, release, repo, diff --git a/styles/components/repo/milestones.ts b/styles/components/repo/milestones.ts deleted file mode 100644 index 73d7508c..00000000 --- a/styles/components/repo/milestones.ts +++ /dev/null @@ -1,80 +0,0 @@ -/*! - * Copyright (c) https://github.com/lutinglt - * - * See the NOTICE file distributed with this work for additional - * information regarding copyright ownership. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { css, cssCombine, themeVars } from "@lutinglt/gitea-github-theme/core"; - -const milestone = css` - /* 里程碑头部 */ - .milestone-header { - gap: 16px; - /* 进度条 */ - progress { - height: 5px; - width: 300px; - max-width: 80vw; - } - } - /* 里程碑 Issue 列表的进度条 */ - .milestone-progress-big { - height: 8px; - } - /* 里程碑 Issue 列表 */ - .page-content.repository.milestone-issue-list { - > .ui.container { - > .flex-text-block:first-child { - margin-bottom: 16px; - > h1 { - font-size: 32px; - font-weight: 600; - line-height: 48px; - word-break: keep-all; - } - + .tw-flex { - flex-direction: row !important; - align-items: center; - gap: 8px !important; - padding-top: 8px; - padding-bottom: 10px; - font-size: 14px; - color: ${themeVars.color.text.light.num1}; - strong { - color: ${themeVars.color.text.self}; - } - > .flex-text-block { - gap: 8px !important; - } - } - } - > .divider { - border-top-color: #0000; - } - } - } -`; - -// 避免手机/平板下菜单错位 -const milestoneMobile = css` - @media (max-width: 767.98px) { - .page-content.repository.milestone-issue-list > .ui.container > .flex-text-block:first-child + .tw-flex { - flex-direction: column !important; - } - } -`; - -export default cssCombine(milestone, milestoneMobile); diff --git a/styles/pages/dashboard-lists.css b/styles/pages/dashboard-lists.css index 60dfc133..26822b8e 100644 --- a/styles/pages/dashboard-lists.css +++ b/styles/pages/dashboard-lists.css @@ -1,4 +1,4 @@ -/* The dashboard's own lists: issues, pull requests, milestones and notifications. */ +/* The dashboard's own lists: issues, pull requests and notifications. */ /* The context switcher sits at the top of the sidebar instead of in a bar across the page. */ .dashboard-list-context .secondary-nav { @@ -20,27 +20,6 @@ padding: 6px 8px; } -/* Milestones: the cards form one bordered list under the Open/Closed bar. */ -.github-milestones .milestone-list { - margin: 0; - padding: 0; - border: 1px solid var(--color-light-border); - border-top: 0; - border-radius: 0 0 var(--border-radius) var(--border-radius); - list-style: none; -} - -.github-milestones .milestone-list > .milestone-card { - margin: 0; - padding: 12px 16px; - border: 0; - border-radius: 0; -} - -.github-milestones .milestone-list > .milestone-card + .milestone-card { - border-top: 1px solid var(--color-light-border); -} - /* Notifications */ .github-notifications .theme-list-heading { margin-bottom: 16px; @@ -105,10 +84,3 @@ .dashboard-list-context .ui.secondary.menu > .item { align-self: flex-start; } - -/* Forgejo's milestone page gives the list header its own box; the shared heading has none. */ -.github-milestones .list-header.list-header-issues { - padding: 0 !important; - border: 0 !important; - background: transparent !important; -} diff --git a/styles/public/menu/compact_menu.ts b/styles/public/menu/compact_menu.ts index 414d4be2..6b0e9452 100644 --- a/styles/public/menu/compact_menu.ts +++ b/styles/public/menu/compact_menu.ts @@ -25,13 +25,7 @@ const smallCompactMenu = css` .page-content.repository.file.editor #editor-bar, /* 订阅/关注切换菜单(应只选中订阅/关注页面, 不能选中通知页面) */ /* No :has() on the container: Chrome would then track every .ui.container and restyle the whole page on editor tab changes. */ - .page-content.user.notification:not(#notification_div) > .ui.container, - /* 里程碑/标签切换菜单(里程碑页) */ - .page-content.repository.milestones .list-header, - /* 里程碑/标签切换菜单(新建里程碑页) */ - .page-content.repository.new.milestone .issue-navbar, - /* 里程碑/标签切换菜单(标签页) */ - .page-content.repository.labels .issue-navbar { + .page-content.user.notification:not(#notification_div) > .ui.container { .switch { --switch-item-min-height: 30px; --switch-padding-inline: 12px; diff --git a/templates/base/component_assets.tmpl b/templates/base/component_assets.tmpl index 399b3ba2..cf2f498e 100644 --- a/templates/base/component_assets.tmpl +++ b/templates/base/component_assets.tmpl @@ -1,3 +1,4 @@ + @@ -5,6 +6,7 @@ + @@ -16,7 +18,8 @@ - + + diff --git a/templates/repo/issue/labels.tmpl b/templates/repo/issue/labels.tmpl new file mode 100644 index 00000000..0f731e3f --- /dev/null +++ b/templates/repo/issue/labels.tmpl @@ -0,0 +1,23 @@ +{{/* GitHub lists a repository's labels inside the issue sidebar layout: a heading with New label, then the label box. */}} +{{template "base/head" .}} +{{$canWrite := and (or .CanWriteIssues .CanWritePulls) (not .Repository.IsArchived)}} +
      + {{template "repo/issue/sidebar" .}} +
      +
      +
      +

      {{ctx.Locale.Tr "repo.labels"}}

      + {{if $canWrite}}{{end}} +
      + {{if $canWrite}} + {{template "repo/issue/labels/label_new" .}} + {{end}} + {{template "base/alert" .}} + {{template "repo/issue/labels/label_list" .}} +
      +
      +
      +{{if $canWrite}} + {{template "repo/issue/labels/edit_delete_label" .}} +{{end}} +{{template "base/footer" .}} diff --git a/templates/repo/issue/labels/label_list.tmpl b/templates/repo/issue/labels/label_list.tmpl new file mode 100644 index 00000000..f82ef116 --- /dev/null +++ b/templates/repo/issue/labels/label_list.tmpl @@ -0,0 +1 @@ +{{template "components/repository/label/list" .}} diff --git a/templates/repo/issue/milestone_issues.tmpl b/templates/repo/issue/milestone_issues.tmpl new file mode 100644 index 00000000..3c64e862 --- /dev/null +++ b/templates/repo/issue/milestone_issues.tmpl @@ -0,0 +1,44 @@ +{{/* One milestone, as GitHub shows it inside the issue sidebar layout: its name with Close, Edit and New issue, its due +date and progress, then the issue list with the issue page's filters. */}} +{{template "base/head" .}} +
      + {{template "repo/issue/sidebar" .}} +
      + {{template "base/alert" .}} +
      +

      {{.Milestone.Name}}

      + {{if not .Repository.IsArchived}} +
      + {{if or .CanWriteIssues .CanWritePulls}} + {{if .Milestone.IsClosed}} + {{ctx.Locale.Tr "repo.milestones.open"}} + {{else}} + {{ctx.Locale.Tr "repo.milestones.close"}} + {{end}} + {{ctx.Locale.Tr "repo.milestones.edit"}} + {{end}} + {{ctx.Locale.Tr "repo.issues.new"}} +
      + {{end}} +
      +
      +

      + {{if .Milestone.IsClosed}} + {{ctx.Locale.Tr "repo.milestones.closed" (DateUtils.TimeSince .Milestone.ClosedDateUnix)}} + {{else if .Milestone.DeadlineString}} + {{svg "octicon-calendar" 16}} {{DateUtils.AbsoluteShort (.Milestone.DeadlineString|DateUtils.ParseLegacy)}} + {{else}} + {{ctx.Locale.Tr "repo.milestones.no_due_date"}} + {{end}} + {{if .TotalTrackedTime}}{{svg "octicon-clock" 16}} {{.TotalTrackedTime | Sec2Time}}{{end}} +

      + {{if .Milestone.RenderedContent}}
      {{.Milestone.RenderedContent}}
      {{end}} + +

      {{ctx.Locale.Tr "repo.milestones.completeness" .Milestone.Completeness}}

      +
      + {{template "repo/issue/filters" dict "." . "themeList" "issues"}} + {{template "shared/issuelist" dict "." . "listType" "milestone"}} +
      +
      + +{{template "base/footer" .}} diff --git a/templates/repo/issue/milestone_new.tmpl b/templates/repo/issue/milestone_new.tmpl new file mode 100644 index 00000000..0015fc94 --- /dev/null +++ b/templates/repo/issue/milestone_new.tmpl @@ -0,0 +1,58 @@ +{{/* Forgejo 17's milestone form inside the issue sidebar layout, where GitHub shows it; Milestones in the sidebar +replaces the Labels and Milestones switch. */}} +{{template "base/head" .}} +
      + {{template "repo/issue/sidebar" .}} +
      +

      + {{if .PageIsEditMilestone}} + {{ctx.Locale.Tr "repo.milestones.edit"}} +
      {{ctx.Locale.Tr "repo.milestones.edit_subheader"}}
      + {{else}} + {{ctx.Locale.Tr "repo.milestones.new"}} +
      {{ctx.Locale.Tr "repo.milestones.new_subheader"}}
      + {{end}} +

      + {{template "base/alert" .}} +
      +
      + + +
      +
      + + +
      +
      + {{template "shared/combomarkdowneditor" (dict + "MarkdownPreviewUrl" (print .Repository.Link "/markup") + "MarkdownPreviewContext" .RepoLink + "TextareaName" "content" + "TextareaPlaceholder" (ctx.Locale.Tr "repo.milestones.desc") + "TextareaAriaLabel" (ctx.Locale.Tr "repo.milestones.desc") + "TextareaContent" .content + "EasyMDE" true + )}} +
      +
      +
      + {{if .PageIsEditMilestone}} + + {{ctx.Locale.Tr "repo.milestones.cancel"}} + + + {{else}} + + {{end}} +
      +
      +
      +
      +{{template "base/footer" .}} diff --git a/templates/repo/issue/milestones.tmpl b/templates/repo/issue/milestones.tmpl new file mode 100644 index 00000000..4ad9a095 --- /dev/null +++ b/templates/repo/issue/milestones.tmpl @@ -0,0 +1,37 @@ +{{/* GitHub lists a repository's milestones inside the issue sidebar layout: a heading with New milestone, the search, +then the milestone box. */}} +{{template "base/head" .}} +{{$canWrite := and (or .CanWriteIssues .CanWritePulls) (not .Repository.IsArchived)}} +
      + {{template "repo/issue/sidebar" .}} +
      + {{template "base/alert" .}} +
      +
      +

      {{ctx.Locale.Tr "repo.milestones"}}

      + {{if $canWrite}}{{ctx.Locale.Tr "repo.milestones.new"}}{{end}} +
      +
      + +
      + {{template "components/repository/milestone/list" dict "Data" . "Query" (print "?q=" (QueryEscape .Keyword)) "OpenCount" .OpenCount "ClosedCount" .ClosedCount "RepoLink" .RepoLink "CanWrite" $canWrite}} +
      +
      +
      +{{if $canWrite}} + +{{end}} +{{template "base/footer" .}} diff --git a/templates/repo/issue/sidebar.tmpl b/templates/repo/issue/sidebar.tmpl index c712497a..e167db6a 100644 --- a/templates/repo/issue/sidebar.tmpl +++ b/templates/repo/issue/sidebar.tmpl @@ -1,15 +1,18 @@ {{/* The issue list and the pull request list share this sidebar; only the first entry and "Review requested" differ. */}} {{$list := "issues"}}{{if .PageIsPullList}}{{$list = "pulls"}}{{end}} +{{/* Labels, milestones (the list, one milestone and its form) and projects are pages of their own, not a filter of the list. */}} +{{$isMilestonePage := or .PageIsMilestones .Milestone}} +{{$isListPage := not (or .IsProjectsPage $isMilestonePage .PageIsLabels)}}