diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 07082f99aec24..3aa3dbc05a6b1 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -144,6 +144,10 @@ confirm_delete_selected = Confirm to delete all selected items? name = Name value = Value +readme = Readme +contributors = Contributors +latest = Latest + filter = Filter filter.clear = Clear Filter filter.is_archived = Archived @@ -1019,7 +1023,7 @@ generate_repo = Generate Repository generate_from = Generate From repo_desc = Description repo_desc_helper = Enter short description (optional) -repo_lang = Language +repo_lang = Languages repo_gitignore_helper = Select .gitignore templates. repo_gitignore_helper_desc = Choose which files not to track from a list of templates for common languages. Typical artifacts generated by each language's build tools are included on .gitignore by default. issue_labels = Issue Labels diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index 73a7be4e892f4..84c044a8ea38e 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -1054,6 +1054,7 @@ func renderHomeCode(ctx *context.Context) { } ctx.Data["Paths"] = paths + ctx.Data["IsHomePage"] = len(paths) == 0 branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL() treeLink := branchLink diff --git a/services/context/repo.go b/services/context/repo.go index 56e9fada0e935..57542c386bed4 100644 --- a/services/context/repo.go +++ b/services/context/repo.go @@ -533,7 +533,7 @@ func RepoAssignment(ctx *Context) context.CancelFunc { ctx.ServerError("GetReleaseCountByRepoID", err) return nil } - ctx.Data["NumReleases"], err = db.Count[repo_model.Release](ctx, repo_model.FindReleasesOptions{ + numReleases, err := db.Count[repo_model.Release](ctx, repo_model.FindReleasesOptions{ // only show draft releases for users who can write, read-only users shouldn't see draft releases. IncludeDrafts: ctx.Repo.CanWrite(unit_model.TypeReleases), RepoID: ctx.Repo.Repository.ID, @@ -542,6 +542,22 @@ func RepoAssignment(ctx *Context) context.CancelFunc { ctx.ServerError("GetReleaseCountByRepoID", err) return nil } + ctx.Data["NumReleases"] = numReleases + + if numReleases > 0 { + release, err := repo_model.GetLatestReleaseByRepoID(ctx, ctx.Repo.Repository.ID) + if err != nil && !repo_model.IsErrReleaseNotExist(err) { + ctx.ServerError("GetLatestReleaseByRepoID", err) + return nil + } + if release != nil { + if err = release.LoadAttributes(ctx); err != nil { + ctx.ServerError("release.LoadAttributes", err) + return nil + } + ctx.Data["LatestRelease"] = release + } + } ctx.Data["Title"] = owner.Name + "/" + repo.Name ctx.Data["Repository"] = repo diff --git a/templates/base/head_navbar.tmpl b/templates/base/head_navbar.tmpl index addff22c49774..5f0cac45d9c24 100644 --- a/templates/base/head_navbar.tmpl +++ b/templates/base/head_navbar.tmpl @@ -44,7 +44,6 @@ {{else}} {{ctx.Locale.Tr "explore"}} {{end}} - {{template "custom/extra_links" .}} {{if not .IsSigned}} diff --git a/templates/repo/cite/cite_buttons.tmpl b/templates/repo/cite/cite_buttons.tmpl index 426ca3858e507..0052b2058533b 100644 --- a/templates/repo/cite/cite_buttons.tmpl +++ b/templates/repo/cite/cite_buttons.tmpl @@ -5,7 +5,7 @@ APA BibTeX - + diff --git a/templates/repo/clone_buttons.tmpl b/templates/repo/clone_buttons.tmpl index 89daba9dc9827..96651ffadd06f 100644 --- a/templates/repo/clone_buttons.tmpl +++ b/templates/repo/clone_buttons.tmpl @@ -9,7 +9,7 @@ SSH {{end}} - + diff --git a/templates/repo/home.tmpl b/templates/repo/home.tmpl index 4241f77eaddde..488986ebf6c0f 100644 --- a/templates/repo/home.tmpl +++ b/templates/repo/home.tmpl @@ -4,43 +4,6 @@
{{template "base/alert" .}} {{template "repo/code/recently_pushed_new_branches" .}} - {{if and (not .HideRepoInfo) (not .IsBlame)}} -
-
- {{$description := .Repository.DescriptionHTML $.Context}} - {{if $description}}{{$description | RenderCodeBlock}}{{else if .IsRepositoryAdmin}}{{ctx.Locale.Tr "repo.no_desc"}}{{end}} - {{.Repository.Website}} -
-
-
- - {{template "shared/search/button"}} -
-
-
-
- {{range .Topics}}{{.Name}}{{end}} - {{if and .Permission.IsAdmin (not .Repository.IsArchived)}}{{end}} -
- {{end}} - {{if and .Permission.IsAdmin (not .Repository.IsArchived)}} -
-
- -
-
- - -
-
- {{end}} {{if .Repository.IsArchived}}
{{if .Repository.ArchivedUnix.IsZero}} @@ -50,107 +13,206 @@ {{end}}
{{end}} - {{template "repo/sub_menu" .}} -
-
- {{template "repo/branch_dropdown" dict "root" . "ContainerClasses" "tw-mr-1"}} - {{if and .CanCompareOrPull .IsViewBranch (not .Repository.IsArchived)}} - {{$cmpBranch := ""}} - {{if ne .Repository.ID .BaseRepo.ID}} - {{$cmpBranch = printf "%s/%s:" (.Repository.OwnerName|PathEscape) (.Repository.Name|PathEscape)}} - {{end}} - {{$cmpBranch = print $cmpBranch (.BranchName|PathEscapeSegments)}} - {{$compareLink := printf "%s/compare/%s...%s" .BaseRepo.Link (.BaseRepo.DefaultBranch|PathEscapeSegments) $cmpBranch}} - - {{svg "octicon-git-pull-request"}} - - {{end}} - - {{$n := len .TreeNames}} - {{$l := Eval $n "-" 1}} - {{if eq $n 0}} - {{ctx.Locale.Tr "repo.find_file.go_to_file"}} +
+
+ {{if .RepoSearchEnabled}} + {{end}} - - {{if and .CanWriteCode .IsViewBranch (not .Repository.IsMirror) (not .Repository.IsArchived) (not .IsViewFile)}} - - {{end}} + {{end}} + + {{$n := len .TreeNames}} + {{$l := Eval $n "-" 1}} + {{if eq $n 0}} + {{ctx.Locale.Tr "repo.find_file.go_to_file"}} + {{end}} - {{if and (eq $n 0) (.Repository.IsTemplate)}} - - {{ctx.Locale.Tr "repo.use_template"}} - - {{end}} - {{if ne $n 0}} - - {{StringUtils.EllipsisString .Repository.Name 30}} - {{- range $i, $v := .TreeNames -}} - / - {{- if eq $i $l -}} - {{StringUtils.EllipsisString $v 30}} - {{- else -}} - {{$p := index $.Paths $i}}{{StringUtils.EllipsisString $v 30}} - {{- end -}} - {{- end -}} - + {{if and .CanWriteCode .IsViewBranch (not .Repository.IsMirror) (not .Repository.IsArchived) (not .IsViewFile)}} + + {{end}} + + {{if and (eq $n 0) (.Repository.IsTemplate)}} + + {{ctx.Locale.Tr "repo.use_template"}} + + {{end}} + {{if ne $n 0}} + + {{StringUtils.EllipsisString .Repository.Name 30}} + {{- range $i, $v := .TreeNames -}} + / + {{- if eq $i $l -}} + {{StringUtils.EllipsisString $v 30}} + {{- else -}} + {{$p := index $.Paths $i}}{{StringUtils.EllipsisString $v 30}} + {{- end -}} + {{- end -}} + + {{end}} +
+
+ + {{if eq $n 0}} +
+ {{template "repo/clone_buttons" .}} + + {{template "repo/clone_script" .}}{{/* the script will update `.js-clone-url` and related elements */}} +
+ {{template "repo/cite/cite_modal" .}} + {{end}} + {{if and (ne $n 0) (not .IsViewFile) (not .IsBlame)}} + + {{svg "octicon-history" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.file_history"}} + + {{end}} +
+
+ {{if .IsViewFile}} + {{template "repo/view_file" .}} + {{else if .IsBlame}} + {{template "repo/blame" .}} + {{else}} + {{template "repo/view_list" .}} {{end}}
-
- - {{if eq $n 0}} -
- {{template "repo/clone_buttons" .}} - +
+
+ +
+ {{end}} + {{template "repo/sidebar/repo_info" dict "ctxData" . "HiddenInMobileView" true}} + + {{if .LatestRelease}} +
+
+ - - {{template "repo/clone_script" .}}{{/* the script will update `.js-clone-url` and related elements */}} +
+
+ + {{TimeSinceUnix .LatestRelease.CreatedUnix ctx.Locale}} +
+
+
- {{template "repo/cite/cite_modal" .}} - {{end}} - {{if and (ne $n 0) (not .IsViewFile) (not .IsBlame)}} - - {{svg "octicon-history" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.file_history"}} - - {{end}} + {{end}} + + {{if and (.Permission.CanRead $.UnitTypeCode) (not .IsEmptyRepo) .LanguageStats}} +
+
+
+ {{ctx.Locale.Tr "repo.repo_lang"}} +
+ +
+
+ {{range .LanguageStats}} +
+ {{end}} +
+
+ {{range .LanguageStats}} +
+ + + {{if eq .Language "other"}} + {{ctx.Locale.Tr "repo.language_other"}} + {{else}} + {{.Language}} + {{end}} + + {{.Percentage}}% +
+ {{end}} +
+
+
+
+ {{end}} +
- {{if .IsViewFile}} - {{template "repo/view_file" .}} - {{else if .IsBlame}} - {{template "repo/blame" .}} - {{else}} - {{template "repo/view_list" .}} - {{end}} {{template "base/footer" .}} diff --git a/templates/repo/sidebar/repo_info.tmpl b/templates/repo/sidebar/repo_info.tmpl new file mode 100644 index 0000000000000..b2a1f49866ea6 --- /dev/null +++ b/templates/repo/sidebar/repo_info.tmpl @@ -0,0 +1,64 @@ +{{$viewType := "desktop"}} +{{if .HiddenInMobileView}} +{{$viewType = "mobile"}} +{{end}} +{{if and (not .ctxData.HideRepoInfo) (not .ctxData.IsBlame)}} +
+
+ {{if .HiddenInMobileView}} +
+ {{ctx.Locale.Tr "repo.repo_desc"}} +
+ {{end}} + +
+
+
+ {{$description := .ctxData.Repository.DescriptionHTML $.ctxData.Context}} + {{if $description}}{{$description | RenderCodeBlock}}{{else if .ctxData.IsRepositoryAdmin}}{{ctx.Locale.Tr "repo.no_desc"}}{{end}} + {{.ctxData.Repository.Website}} +
+
+
+ +
+
+ {{range .ctxData.Topics}}{{.Name}}{{end}} + {{if and .ctxData.Permission.IsAdmin (not .ctxData.Repository.IsArchived)}}{{end}} +
+ {{if and .ctxData.Permission.IsAdmin (not .ctxData.Repository.IsArchived)}} +
+
+ +
+
+ + +
+
+ {{end}} +
+ + {{if and .ctxData.ReadmeExist .HiddenInMobileView}} + + {{end}} + + {{if .ctxData.CitiationExist}} + + {{end}} +
+
+{{end}} diff --git a/templates/repo/sub_menu.tmpl b/templates/repo/sub_menu.tmpl index 000e0a10c5be7..25184efc18562 100644 --- a/templates/repo/sub_menu.tmpl +++ b/templates/repo/sub_menu.tmpl @@ -20,27 +20,5 @@ {{end}} - {{if and (.Permission.CanRead $.UnitTypeCode) (not .IsEmptyRepo) .LanguageStats}} - - - {{range .LanguageStats}} -
- {{end}} -
- {{end}} {{end}} diff --git a/tests/integration/repo_test.go b/tests/integration/repo_test.go index 06c55b1e8a235..f31dfd7760891 100644 --- a/tests/integration/repo_test.go +++ b/tests/integration/repo_test.go @@ -28,13 +28,18 @@ func TestViewRepo(t *testing.T) { resp := session.MakeRequest(t, req, http.StatusOK) htmlDoc := NewHTMLParser(t, resp.Body) - noDescription := htmlDoc.doc.Find("#repo-desc").Children() - repoTopics := htmlDoc.doc.Find("#repo-topics").Children() - repoSummary := htmlDoc.doc.Find(".repository-summary").Children() - assert.True(t, noDescription.HasClass("no-description")) - assert.True(t, repoTopics.HasClass("repo-topic")) - assert.True(t, repoSummary.HasClass("repository-menu")) + components := map[string]*goquery.Selection{ + "no-description": htmlDoc.doc.Find("#repo-desc").Children(), + "repo-topic": htmlDoc.doc.Find("#repo-topics").Children(), + "repository-menu": htmlDoc.doc.Find(".repository-summary").Children(), + } + + for className, selection := range components { + selection.Each(func(_ int, s *goquery.Selection) { + assert.True(t, s.HasClass(className)) + }) + } req = NewRequest(t, "GET", "/org3/repo3") MakeRequest(t, req, http.StatusNotFound) @@ -191,12 +196,16 @@ func TestViewAsRepoAdmin(t *testing.T) { resp := session.MakeRequest(t, req, http.StatusOK) htmlDoc := NewHTMLParser(t, resp.Body) - noDescription := htmlDoc.doc.Find("#repo-desc").Children() - repoTopics := htmlDoc.doc.Find("#repo-topics").Children() + noDescriptionMobile := htmlDoc.doc.Find("#repo-desc-mobile").Children() + repoTopicsMobile := htmlDoc.doc.Find("#repo-topics-mobile").Children() + noDescriptionDesktop := htmlDoc.doc.Find("#repo-desc-desktop").Children() + repoTopicsDesktop := htmlDoc.doc.Find("#repo-topics-desktop").Children() repoSummary := htmlDoc.doc.Find(".repository-summary").Children() - assert.Equal(t, expectedNoDescription, noDescription.HasClass("no-description")) - assert.True(t, repoTopics.HasClass("repo-topic")) + assert.Equal(t, expectedNoDescription, noDescriptionMobile.HasClass("no-description")) + assert.True(t, repoTopicsMobile.HasClass("repo-topic")) + assert.Equal(t, expectedNoDescription, noDescriptionDesktop.HasClass("no-description")) + assert.True(t, repoTopicsDesktop.HasClass("repo-topic")) assert.True(t, repoSummary.HasClass("repository-menu")) } } diff --git a/web_src/css/repo.css b/web_src/css/repo.css index 18f28dc4a6e0d..f277673e4e34c 100644 --- a/web_src/css/repo.css +++ b/web_src/css/repo.css @@ -79,6 +79,66 @@ white-space: nowrap; } +.repo-content { + display: flex; + align-items: flex-start; + gap: 16px; +} + +@media (max-width: 767.98px) { + .repo-content { + flex-direction: column; + } +} + +.repo-content-main[data-home="true"] { + margin: 0 !important; + width: calc(100% - 316px); +} + +.repo-content-main[data-home="false"] { + margin: 0 !important; + width: 100%; +} + +@media (max-width: 767.98px) { + .repo-content-main[data-home="true"] { + margin: 0 !important; + width: 100%; + } +} + +.repo-content-sidebar[data-home="true"] { + margin: 0 !important; + width: 300px; +} + +.repo-content-sidebar[data-home="false"] { + display: none; +} + +@media (max-width: 767.98px) { + .repo-content-sidebar[data-home="true"] { + margin: 0 !important; + width: 100%; + } +} + +.latest-release { + text-decoration-line: none; + margin: 1rem; +} + +.latest-release-tag { + display: flex; + align-items: center; + gap: .5rem; +} + +.latest-release .time { + margin-left: 1.6rem; +} + .repository .filter.menu.labels .label-filter .menu .info { display: inline-block; padding: 0.5rem 0; @@ -167,7 +227,6 @@ justify-content: space-between; align-items: center; gap: 5px; - margin-bottom: 5px; } @media (max-width: 767.98px) { @@ -1999,7 +2058,14 @@ background: var(--color-secondary); } -.repository .repository-summary .segment.language-stats { +.contributors-stats { + display: flex; + flex-flow: row wrap; + padding: 10px; + gap: 5px; +} + +.language-stats { display: flex; gap: 2px; padding: 0; @@ -2007,6 +2073,25 @@ white-space: nowrap; border-radius: 0 0 3px 3px !important; overflow: hidden; + width: 100%; + margin-top: 1rem; + margin-bottom: 5px; +} + +.language-stats-details { + display: flex; + flex-wrap: wrap; +} + +.language-stats-details .item { + height: 30px; + line-height: var(--line-height-default); + display: flex; + align-items: center; + justify-content: center; + gap: 0.25em; + padding: 0 0.5em; /* make the UI look better for narrow (mobile) view */ + text-decoration: none; } #cite-repo-modal #citation-panel { @@ -3016,3 +3101,8 @@ tbody.commit-list { margin-top: -1px; border-top: 1px solid var(--color-secondary); } + +#repo-desc-mobile .description, +#repo-desc-desktop .description { + max-height: 150px; +} diff --git a/web_src/js/features/citation.js b/web_src/js/features/citation.js index 918a4671364c2..a734a828d22fe 100644 --- a/web_src/js/features/citation.js +++ b/web_src/js/features/citation.js @@ -30,6 +30,7 @@ export async function initCitationFileCopyContent() { const citationCopyApa = document.getElementById('citation-copy-apa'); const citationCopyBibtex = document.getElementById('citation-copy-bibtex'); const inputContent = document.getElementById('citation-copy-content'); + const modal = document.getElementById('cite-repo-modal'); if ((!citationCopyApa && !citationCopyBibtex) || !inputContent) return; @@ -41,36 +42,36 @@ export async function initCitationFileCopyContent() { citationCopyApa.classList.toggle('primary', !isBibtex); }; - document.getElementById('cite-repo-button')?.addEventListener('click', async (e) => { - const dropdownBtn = e.target.closest('.ui.dropdown.button'); - dropdownBtn.classList.add('is-loading'); + for (const button of document.getElementsByClassName('cite-repo-button')) { + button.addEventListener('click', async () => { + $(modal).modal('show'); + $(modal).addClass('is-loading'); - try { try { - await initInputCitationValue(citationCopyApa, citationCopyBibtex); - } catch (e) { - console.error(`initCitationFileCopyContent error: ${e}`, e); - return; - } - updateUi(); - - citationCopyApa.addEventListener('click', () => { - localStorage.setItem('citation-copy-format', 'apa'); + try { + await initInputCitationValue(citationCopyApa, citationCopyBibtex); + } catch (e) { + console.error(`initCitationFileCopyContent error: ${e}`, e); + return; + } updateUi(); - }); - citationCopyBibtex.addEventListener('click', () => { - localStorage.setItem('citation-copy-format', 'bibtex'); - updateUi(); - }); + citationCopyApa.addEventListener('click', () => { + localStorage.setItem('citation-copy-format', 'apa'); + updateUi(); + }); - inputContent.addEventListener('click', () => { - inputContent.select(); - }); - } finally { - dropdownBtn.classList.remove('is-loading'); - } + citationCopyBibtex.addEventListener('click', () => { + localStorage.setItem('citation-copy-format', 'bibtex'); + updateUi(); + }); - $('#cite-repo-modal').modal('show'); - }); + inputContent.addEventListener('click', () => { + inputContent.select(); + }); + } finally { + $(modal).removeClass('is-loading'); + } + }); + } } diff --git a/web_src/js/features/repo-home.js b/web_src/js/features/repo-home.js index e195c23c37278..2e18b739cc263 100644 --- a/web_src/js/features/repo-home.js +++ b/web_src/js/features/repo-home.js @@ -5,12 +5,21 @@ import {POST} from '../modules/fetch.js'; const {appSubUrl} = window.config; -export function initRepoTopicBar() { - const mgrBtn = document.getElementById('manage_topic'); - if (!mgrBtn) return; - const editDiv = document.getElementById('topic_edit'); - const viewDiv = document.getElementById('repo-topics'); - const saveBtn = document.getElementById('save_topic'); +export function initRepoTopicBars() { + for (const mgrBtn of document.querySelectorAll('.manage-topic')) { + const viewType = mgrBtn.getAttribute('data-view-type'); + if (!viewType.length) { + throw new Error('no view type defined in attributes for manage topic button'); + } + initRepoTopicBar(mgrBtn, viewType); + } +} + +function initRepoTopicBar(mgrBtn, viewType) { + const editDiv = document.getElementById(`topic_edit_${viewType}`); + const viewDiv = document.getElementById(`repo-topics-${viewType}`); + const saveBtn = document.getElementById(`save_topic_${viewType}`); + const cancelBtn = document.getElementById(`cancel_topic_edit_${viewType}`); const topicDropdown = editDiv.querySelector('.dropdown'); const $topicDropdown = $(topicDropdown); const $topicForm = $(editDiv); @@ -26,14 +35,14 @@ export function initRepoTopicBar() { $topicDropdownSearch.trigger('focus'); }); - $('#cancel_topic_edit').on('click', () => { + $(cancelBtn).on('click', () => { hideElem(editDiv); showElem(viewDiv); mgrBtn.focus(); }); saveBtn.addEventListener('click', async () => { - const topics = $('input[name=topics]').val(); + const topics = $(`input[name=topics-${viewType}]`).val(); const data = new FormData(); data.append('topics', topics); diff --git a/web_src/js/index.js b/web_src/js/index.js index abf0d469d18ed..ed23a42ce620a 100644 --- a/web_src/js/index.js +++ b/web_src/js/index.js @@ -45,7 +45,7 @@ import { initGlobalLinkActions, initHeadNavbarContentToggle, } from './features/common-global.js'; -import {initRepoTopicBar} from './features/repo-home.js'; +import {initRepoTopicBars} from './features/repo-home.js'; import {initAdminEmails} from './features/admin/emails.js'; import {initAdminCommon} from './features/admin/common.js'; import {initRepoTemplateSearch} from './features/repo-template.js'; @@ -167,7 +167,7 @@ onDomReady(() => { initRepoSettingSearchTeamBox(); initRepoSettingsCollaboration(); initRepoTemplateSearch(); - initRepoTopicBar(); + initRepoTopicBars(); initRepoWikiForm(); initRepository(); initRepositoryActionView();