Modernize repo summary page with GitHub-style layout

Two-column grid: main content + About sidebar.
Extract clone_url_bar component from old summary_detail.
Add repo stats bar (branches, tags, commits, forks).
Move repo metadata (owner, description, dates) into visible
About sidebar instead of hidden behind Show More toggle.
Auto-load language stats when enabled.
This commit is contained in:
russell@unturf.com 2026-03-04 18:48:47 -05:00
parent ec33c57d29
commit be07de2f57
3 changed files with 372 additions and 78 deletions

View file

@ -3358,3 +3358,208 @@ i[class*=" icon-"] {
grid-column: 1 / -1; grid-column: 1 / -1;
} }
} }
/* ================================================================
REPO SUMMARY PAGE
================================================================ */
.repo-summary-grid {
display: grid;
grid-template-columns: 1fr 280px;
gap: var(--space-5);
align-items: start;
}
@media (max-width: 960px) {
.repo-summary-grid {
grid-template-columns: 1fr;
}
}
/* Clone URL bar */
.clone-url-bar {
display: grid;
grid-template-columns: auto 1fr;
gap: var(--space-2);
align-items: center;
padding: var(--space-3);
background: var(--surface);
border: var(--border-width) solid var(--border);
border-radius: var(--radius);
margin-bottom: var(--space-4);
}
.clone-url-select {
padding: var(--space-1) var(--space-2);
border: var(--border-width) solid var(--border);
border-radius: var(--radius);
background: var(--surface-alt);
font-size: 0.8125rem;
font-family: var(--font-sans);
}
.clone-url-input-wrap {
display: grid;
grid-template-columns: 1fr auto;
align-items: center;
gap: var(--space-2);
}
.clone-url-input-wrap .clone_url_input {
width: 100%;
padding: var(--space-1) var(--space-2);
font-family: var(--font-mono);
font-size: 0.8125rem;
border: var(--border-width) solid var(--border);
border-radius: var(--radius);
background: var(--surface-alt);
color: var(--text);
}
.clone-url-input-wrap .clipboard-action {
cursor: pointer;
color: var(--text-muted);
font-size: 1rem;
}
.clone-url-input-wrap .clipboard-action:hover {
color: var(--accent);
}
/* Repo stats bar (branches, tags, commits, forks) */
.repo-stats-bar {
display: grid;
grid-auto-flow: column;
grid-auto-columns: auto;
gap: var(--space-3);
justify-content: start;
padding: var(--space-3) 0;
margin-bottom: var(--space-4);
border-bottom: var(--border-width) solid var(--border);
font-size: 0.875rem;
}
.summary-tag {
color: var(--text-muted);
}
.summary-tag a {
color: var(--text-muted);
text-decoration: none;
}
.summary-tag a:hover {
color: var(--accent);
}
.summary-tag i {
margin-right: var(--space-1);
}
/* Repo commits section */
.repo-commits-section {
margin-bottom: var(--space-4);
}
/* README container */
.readme-container {
border: var(--border-width) solid var(--border);
border-radius: var(--radius);
overflow: hidden;
}
.readme-title {
padding: var(--space-3);
background: var(--surface-alt);
border-bottom: var(--border-width) solid var(--border);
font-size: 0.875rem;
font-weight: 600;
}
.readme-title i {
margin-right: var(--space-1);
color: var(--text-muted);
}
.readme-title a {
color: var(--text);
text-decoration: none;
}
.readme-title a:hover {
color: var(--accent);
}
.readme-container .readme_box {
padding: var(--space-4);
}
/* Repo summary sidebar (About, Downloads, Languages) */
.repo-summary-sidebar {
position: sticky;
top: var(--space-4);
}
.repo-about {
margin-bottom: var(--space-5);
}
.repo-about .sidebar-heading {
font-size: 0.875rem;
font-weight: 700;
color: var(--text);
margin-bottom: var(--space-2);
padding-bottom: var(--space-2);
border-bottom: var(--border-width) solid var(--border);
}
.repo-description {
font-size: 0.875rem;
color: var(--text-muted);
margin-bottom: var(--space-3);
line-height: 1.5;
}
.repo-meta-list {
display: grid;
gap: var(--space-2);
}
.repo-meta-item {
display: grid;
grid-template-columns: 20px 1fr;
gap: var(--space-2);
align-items: center;
font-size: 0.8125rem;
color: var(--text-muted);
}
.repo-meta-item i {
color: var(--text-muted);
font-size: 0.875rem;
justify-self: center;
}
.repo-meta-item a {
color: var(--accent);
text-decoration: none;
}
.repo-meta-item a:hover {
text-decoration: underline;
}
.repo-downloads {
display: grid;
gap: var(--space-2);
}
.repo-downloads .archive_link {
display: grid;
grid-template-columns: auto 1fr;
gap: var(--space-2);
align-items: center;
padding: var(--space-2) var(--space-3);
font-size: 0.8125rem;
text-decoration: none;
}

View file

@ -1,5 +1,41 @@
<%namespace name="base" file="/base/base.mako"/> <%namespace name="base" file="/base/base.mako"/>
<%def name="clone_url_bar()">
<%
maybe_disabled = ''
if h.is_svn_without_proxy(c.rhodecode_db_repo):
maybe_disabled = 'disabled'
%>
<div class="clone-url-bar">
<select id="clone_option" name="clone_option" class="clone-url-select">
<option value="http" selected="selected">HTTP</option>
<option value="http_id">HTTP UID</option>
% if c.ssh_enabled:
<option value="ssh">SSH</option>
% endif
</select>
<span id="clone_option_http" class="clone-url-input-wrap">
<input type="text" class="input-monospace clone_url_input" ${maybe_disabled} readonly="readonly" value="${c.clone_repo_url}"/>
<i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.clone_repo_url}" title="${_('Copy the clone url')}"></i>
</span>
<span style="display: none;" id="clone_option_http_id" class="clone-url-input-wrap">
<input type="text" class="input-monospace clone_url_input" ${maybe_disabled} readonly="readonly" value="${c.clone_repo_url_id}"/>
<i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.clone_repo_url_id}" title="${_('Copy the clone by id url')}"></i>
</span>
<span style="display: none;" id="clone_option_ssh" class="clone-url-input-wrap">
<input type="text" class="input-monospace clone_url_input" ${maybe_disabled} readonly="readonly" value="${c.clone_repo_url_ssh}"/>
<i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.clone_repo_url_ssh}" title="${_('Copy the clone by ssh url')}"></i>
</span>
% if maybe_disabled:
<p class="help-block">${_('SVN Protocol is disabled. To enable it, see the')} <a href="${h.route_url('enterprise_svn_setup')}" target="_blank">${_('documentation here')}</a>.</p>
% endif
</div>
</%def>
<%def name="refs_counters(branches, closed_branches, tags, bookmarks)"> <%def name="refs_counters(branches, closed_branches, tags, bookmarks)">
<span class="summary-branchtag summary-tag"> <span class="summary-branchtag summary-tag">
<a href="${h.route_path('branches_home',repo_name=c.repo_name)}" class="childs"> <a href="${h.route_path('branches_home',repo_name=c.repo_name)}" class="childs">

View file

@ -1,6 +1,7 @@
<%inherit file="/summary/summary_base.mako"/> <%inherit file="/summary/summary_base.mako"/>
<%namespace name="components" file="/summary/components.mako"/> <%namespace name="components" file="/summary/components.mako"/>
<%namespace name="base" file="/base/base.mako"/>
<%def name="menu_bar_subnav()"> <%def name="menu_bar_subnav()">
@ -9,47 +10,140 @@
<%def name="main()"> <%def name="main()">
<div id="repo-summary" class="summary"> <div class="repo-summary-grid">
${components.summary_detail(breadcrumbs_links=self.breadcrumbs_links(), show_downloads=True)}
</div><!--end repo-summary-->
<div class="repo-summary-main">
<div class="box"> ## Clone URL bar
%if not c.repo_commits: ${components.clone_url_bar()}
<div class="empty-repo">
<div class="title"> ## Branch/tag stats bar
<h3>${_('Quick start')}</h3> <div class="repo-stats-bar">
${components.refs_counters(
c.rhodecode_repo.branches if c.rhodecode_repo else [],
c.rhodecode_repo.branches_closed if c.rhodecode_repo else [],
c.rhodecode_repo.tags if c.rhodecode_repo else [],
c.rhodecode_repo.bookmarks if c.rhodecode_repo else [])}
<% commit_rev = h.safe_int(c.rhodecode_db_repo.changeset_cache.get('revision'), 0) + 1 %>
<span class="summary-tag">
% if commit_rev > 0:
<a href="${h.route_path('repo_commits', repo_name=c.repo_name)}">
<i class="icon-history"></i>
<span>${commit_rev}</span> ${_ungettext('Commit', 'Commits', commit_rev)}
</a>
% else:
<i class="icon-history"></i>
<span>0</span> ${_('Commits')}
% endif
</span>
<span class="summary-tag">
<a href="${h.route_path('repo_forks_show_all', repo_name=c.repo_name)}">
<i class="icon-code-fork"></i>
<span>${c.repository_forks}</span> ${_ungettext('Fork', 'Forks', c.repository_forks)}
</a>
</span>
</div>
## Recent commits
<div class="repo-commits-section">
%if c.repo_commits:
<div class="table">
<div id="shortlog_data">
<%include file='summary_commits.mako'/>
</div>
</div> </div>
<div class="clear-fix"></div> %else:
</div>
%endif
<div class="table">
<div id="shortlog_data">
<%include file='summary_commits.mako'/> <%include file='summary_commits.mako'/>
%endif
</div>
## README
%if c.readme_data:
<div id="readme" class="anchor">
<div class="readme-container">
<div class="readme-title">
<i class="icon-file-text"></i>
<a href="${h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.rhodecode_db_repo.landing_ref_name,f_path=c.readme_file)}">
${c.readme_file}
</a>
</div>
<div class="readme codeblock">
<div class="readme_box">
${c.readme_data|n}
</div>
</div> </div>
</div> </div>
</div>
%if c.readme_data:
<div id="readme" class="anchor">
<div class="box">
<div class="readme-title" title="${h.tooltip(_('Readme file from commit %s:%s') % (c.rhodecode_db_repo.landing_ref_type, c.rhodecode_db_repo.landing_ref_name))}">
<div>
<i class="icon-file-text"></i>
<a href="${h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.rhodecode_db_repo.landing_ref_name,f_path=c.readme_file)}">
${c.readme_file}
</a>
</div> </div>
%endif
</div> </div>
<div class="readme codeblock">
<div class="readme_box"> <div class="repo-summary-sidebar">
${c.readme_data|n} ## About section
</div> <div class="repo-about">
<h4 class="sidebar-heading">${_('About')}</h4>
% if c.rhodecode_db_repo.description:
<p class="repo-description">
<%namespace name="dt" file="/data_table/_dt_elements.mako"/>
${dt.repo_desc(c.rhodecode_db_repo.description_safe, c.visual.stylify_metatags)}
</p>
% endif
<div class="repo-meta-list">
<div class="repo-meta-item">
<i class="icon-user"></i>
${base.gravatar_with_user(c.rhodecode_db_repo.user.email, 16, tooltip=True)}
</div>
% if c.rhodecode_db_repo.landing_ref_name:
<div class="repo-meta-item">
<i class="icon-branch"></i>
<span>${c.rhodecode_db_repo.landing_ref_name}</span>
</div>
% endif
<div class="repo-meta-item">
<i class="icon-calendar"></i>
<span>${h.format_date(c.rhodecode_db_repo.created_on)}</span>
</div>
% if c.rhodecode_db_repo.fork:
<div class="repo-meta-item">
<i class="icon-code-fork"></i>
<a href="${h.route_path('repo_summary',repo_name=c.rhodecode_db_repo.fork.repo_name)}">${_('Forked from')} ${c.rhodecode_db_repo.fork.repo_name}</a>
</div>
% endif
</div>
</div>
## Downloads section
% if c.enable_downloads and c.rhodecode_repo and len(c.rhodecode_repo.commit_ids) > 0:
<div class="repo-about">
<h4 class="sidebar-heading">${_('Downloads')}</h4>
<div class="repo-downloads">
% for a_type, content_type, extension in h.ARCHIVE_SPECS:
<a class="archive_link btn btn-small btn-default" data-ext="${extension}" href="${h.route_path('repo_archivefile',repo_name=c.rhodecode_db_repo.repo_name, fname=c.rhodecode_db_repo.landing_ref_name+extension, _query={'with_hash': '1'})}" rel="nofollow">
<i class="icon-download"></i> ${c.rhodecode_db_repo.landing_ref_name}${extension}
</a>
% endfor
</div>
</div>
% endif
## Statistics
% if c.show_stats:
<div class="repo-about">
<h4 class="sidebar-heading">${_('Languages')}</h4>
<div id="lang_stats"></div>
<div id="repo_size_container"></div>
</div>
% endif
</div> </div>
</div> </div>
</div>
%endif
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function(){ $(document).ready(function(){
@ -64,62 +158,21 @@ $(document).ready(function(){
} }
}); });
}; };
// default taken from session
showCloneField(templateContext.session_attrs.clone_url_format); showCloneField(templateContext.session_attrs.clone_url_format);
$('#clone_option').on('change', function(e) { $('#clone_option').on('change', function(e) {
var selected = $(this).val(); var selected = $(this).val();
storeUserSessionAttr('rc_user_session_attr.clone_url_format', selected); storeUserSessionAttr('rc_user_session_attr.clone_url_format', selected);
showCloneField(selected) showCloneField(selected)
}); });
var initialCommitData = { % if c.show_stats:
id: null, // auto-load stats
text: '${c.rhodecode_db_repo.landing_ref_name}', var callback = function (data) {
type: '${c.rhodecode_db_repo.landing_ref_type}', showRepoStats('lang_stats', data);
raw_id: null,
files_url: null
}; };
showRepoSize('repo_size_container', templateContext.repo_name, templateContext.repo_landing_commit, callback);
select2RefSwitcher('#download_options', initialCommitData); % endif
// on change of download options
$('#download_options').on('change', function(e) {
// format of Object {text: "v0.0.3", type: "tag", id: "rev"}
var selectedReference = e.added;
var ico = '<i class="icon-download"></i>';
$.each($('.archive_link'), function (key, val) {
var ext = $(this).data('ext');
var fname = selectedReference.raw_id + ext;
var href = pyroutes.url('repo_archivefile', {
'repo_name': templateContext.repo_name,
'fname': fname,
'with_hash': '1'
});
// set new label
$(this).html(ico + ' {0}{1}'.format(escapeHtml(e.added.text), ext));
// set new url to button,
$(this).attr('href', href).attr('rel', 'nofollow')
});
});
// calculate size of repository
calculateSize = function () {
var callback = function (data) {
% if c.show_stats:
showRepoStats('lang_stats', data);
% endif
};
showRepoSize('repo_size_container', templateContext.repo_name, templateContext.repo_landing_commit, callback);
}
}) })
</script> </script>