This commit is contained in:
2024-07-16 15:07:01 +02:00
commit 2ebd0bee3a
18 changed files with 1217 additions and 0 deletions

View File

@ -0,0 +1,25 @@
<footer>
<div class="ui container">
<div class="ui left">
{{.i18n.Tr "powered_by" "Laurel"}} - {{if ShowFooterTemplateLoadTime}}{{.i18n.Tr "page"}}:
<strong>{{LoadTimes .PageStartTime}}</strong> {{.i18n.Tr "template"}}:
<strong>{{call .TmplLoadTimes}}</strong>{{end}}
</div>
<div class="ui right links">
<div class="ui language bottom floating slide up dropdown link item">
{{svg "octicon-globe"}}
<div class="text">{{.LangName}}</div>
<div class="menu language-menu">
{{range .AllLangs}}
<a lang="{{.Lang}}" data-url="{{AppSubUrl}}/?lang={{.Lang}}"
class="item {{if eq $.Lang .Lang}}active selected{{end}}">{{.Name}}</a>
{{end}}
</div>
</div>
<a target="_blank" rel="noopener noreferrer"
href="https://proglang.informatik.uni-freiburg.de">Department of Programming Languages</a>
<a target="_blank" rel="noopener noreferrer"
href="https://proglang.informatik.uni-freiburg.de/imprint.html">Imprint</a>
</div>
</div>
</footer>

View File

@ -0,0 +1,75 @@
<div class="ui container" id="navbar">
<div class="item brand" style="justify-content: space-between;">
<a href="{{AppSubUrl}}/">
<img class="ui mini image" width="30" height="30" src="{{AssetUrlPrefix}}/img/logo.svg">
</a>
<div class="ui basic icon button mobile-only" id="navbar-expand-toggle">
<i class="sidebar icon"></i>
</div>
</div>
{{if and .IsSigned .MustChangePassword}}
{{/* No links */}}
{{else if .IsSigned}}
<a class="item {{if .PageIsDashboard}}active{{end}}" href="{{AppSubUrl}}/">Home</a>
{{if .IsAdmin}}
<div class="divider"></div>
<a class="item" id="admin">Admin</a>
{{end}}
<a class="item" href="http://chat.localhost">Chat</a>
{{end}}
{{template "custom/extra_links" .}}
{{if .IsSigned}}
<div class="right stackable menu">
<div class="ui dropdown jump item poping up" tabindex="-1"
data-content="{{.i18n.Tr "user_profile_and_more"}}" data-variation="tiny inverted">
<span class="text">
{{ctx.AvatarUtils.Avatar .SignedUser 24 "tiny"}}
<span class="sr-only">{{.i18n.Tr "user_profile_and_more"}}</span>
<span class="mobile-only">{{.SignedUser.Name}}</span>
<span class="fitted not-mobile" tabindex="-1">{{svg "octicon-triangle-down"}}</span>
</span>
<div class="menu user-menu" tabindex="-1">
<div class="ui header">
{{.i18n.Tr "signed_in_as"}} <strong>{{.SignedUser.Name}}</strong>
</div>
<div class="divider"></div>
<a class="{{if .PageIsUserSettings}}active{{end}} item" href="{{AppSubUrl}}/user/settings">
{{svg "octicon-tools"}}
{{.i18n.Tr "your_settings"}}<!-- Your settings -->
</a>
{{if .IsAdmin}}
<div class="divider"></div>
<a class="{{if .PageIsAdmin}}active{{end}} item" href="{{AppSubUrl}}/admin">
{{svg "octicon-server"}}
{{.i18n.Tr "admin_panel"}}<!-- Admin Panel -->
</a>
{{end}}
<div class="divider"></div>
<a class="item link-action" href data-url="{{AppSubUrl}}/user/logout"
data-redirect="http://courses.docker.localhost/auth/logout?redirect={{AppUrl}}">
{{svg "octicon-sign-out"}}
{{.i18n.Tr "sign_out"}}<!-- Sign Out -->
</a>
</div><!-- end content ctx.AvatarUtils.Avatar menu -->
</div><!-- end dropdown ctx.AvatarUtils.Avatar menu -->
</div><!-- end signed user right menu -->
{{else}}
<div class="right stackable menu">
<a class="item{{if .PageIsSignIn}} active{{end}}" rel="nofollow"
href="http://courses.localhost/auth/login?redirect={{AppUrl}}user/oauth2/uni">
{{svg "octicon-sign-in"}} {{.i18n.Tr "sign_in"}}
</a>
</div><!-- end anonymous right menu -->
{{end}}
</div>
<script>
const COURSES_URL = "http://courses.localhost"
const BUILD_URL = "http://build.localhost"
{{if .IsAdmin}}
document.getElementById("admin").setAttribute("href", COURSES_URL + "/admin")
{{end}}
</script>

View File

@ -0,0 +1,58 @@
<div class="ui repository list">
{{range .Repos}}
<div class="item">
<div class="ui header df ac">
<div class="repo-title">
{{$avatar := (ctx.AvatarUtils .Repo 32 "mr-3")}}
{{if $avatar}}
{{$avatar}}
{{end}}
<a class="name" href="{{.Link}}">
{{if or $.PageIsExplore $.PageIsProfileStarList }}{{if .Owner}}{{.Owner.Name}} / {{end}}{{end}}{{.Name}}
</a>
<div class="labels df ac fw">
{{if .IsArchived}}
<span class="ui basic label">{{$.i18n.Tr "repo.desc.archived"}}</span>
{{end}}
{{if .IsTemplate}}
{{if .IsPrivate}}
<span class="ui basic label">{{$.i18n.Tr "repo.desc.private_template"}}</span>
{{else}}
{{if .Owner.Visibility.IsPrivate}}
<span class="ui basic label">{{$.i18n.Tr "repo.desc.internal_template"}}</span>
{{end}}
{{end}}
{{else}}
{{if .Owner.Visibility.IsPrivate}}
<span class="ui basic label">{{$.i18n.Tr "repo.desc.internal"}}</span>
{{end}}
{{end}}
{{if .IsFork}}
{{svg "octicon-repo-forked"}}
{{else if .IsMirror}}
{{svg "octicon-mirror"}}
{{end}}
</div>
</div>
</div>
<div class="description">
{{if .DescriptionHTML}}<p>{{.DescriptionHTML}}</p>{{end}}
{{if .Topics }}
<div class="ui tags">
{{range .Topics}}
{{if ne . "" }}<a href="{{AppSubUrl}}/explore/repos?q={{.}}&topic=1">
<div class="ui small label topic">{{.}}</div>
</a>{{end}}
{{end}}
</div>
{{end}}
<p class="time">{{$.i18n.Tr "org.repo_updated"}} {{TimeSinceUnix .UpdatedUnix $.i18n.Lang}}</p>
</div>
</div>
{{else}}
<div>
{{$.i18n.Tr "explore.repo_no_results"}}
</div>
{{end}}
</div>

17
templates/home.tmpl Normal file
View File

@ -0,0 +1,17 @@
{{template "base/head" .}}
<div class="page-content home">
<div class="ui stackable middle very relaxed page grid">
<div class="sixteen wide center aligned centered column">
<div>
<img class="logo" width="220" height="220" src="{{AssetUrlPrefix}}/img/logo.svg"/>
</div>
<div class="hero">
<h1 class="ui icon header title">
Progλang Git
</h1>
<h2>Student Code Submission</h2>
</div>
</div>
</div>
</div>
{{template "base/footer" .}}

95
templates/org/home.tmpl Normal file
View File

@ -0,0 +1,95 @@
{{template "base/head" .}}
<div class="page-content organization profile">
<div class="ui container df">
{{ctx.AvatarUtils .Org 140 "org-avatar"}}
<div id="org-info">
<div class="ui header">
{{.Org.DisplayName}}
<span class="org-visibility">
{{if .Org.Visibility.IsLimited}}
<div class="ui large basic horizontal label">{{.i18n.Tr "org.settings.visibility.limited_shortname"}}</div>{{end}}
{{if .Org.Visibility.IsPrivate}}
<div class="ui large basic horizontal label">{{.i18n.Tr "org.settings.visibility.private_shortname"}}</div>{{end}}
</span>
{{if .IsOrganizationOwner}}<a class="middle text grey"
href="{{.OrgLink}}/settings">{{svg "octicon-gear" 16 "mb-3"}}</a>{{end}}
</div>
{{if $.RenderedDescription}}<p class="render-content markup">{{$.RenderedDescription|Str2html}}</p>{{end}}
<div class="text grey meta">
{{if .Org.Location}}
<div class="item">{{svg "octicon-location"}} <span>{{.Org.Location}}</span></div>{{end}}
{{if .Org.Website}}
<div class="item">{{svg "octicon-link"}} <a target="_blank" rel="noopener noreferrer"
href="{{.Org.Website}}">{{.Org.Website}}</a></div>{{end}}
</div>
</div>
</div>
<div class="ui divider"></div>
<div class="ui container">
<div class="ui mobile reversed stackable grid">
<div class="ui eleven wide column">
{{template "explore/repo_search" .}}
{{template "explore/repo_list" .}}
{{template "base/paginate" .}}
</div>
{{ if .IsAdmin }}
<div class="ui five wide column">
<h4 class="ui top attached header df">
<strong class="f1">{{.i18n.Tr "org.people"}}</strong>
{{if .IsOrganizationMember}}
<div class="ui">
<a class="text grey dif ac"
href="{{.OrgLink}}/members"><span>{{.Org.NumMembers}}</span> {{svg "octicon-chevron-right"}}
</a>
</div>
{{end}}
</h4>
<div class="ui attached segment members">
{{$isMember := .IsOrganizationMember}}
{{range .Members}}
{{if or $isMember (.IsPublicMember $.Org.ID)}}
<a href="{{.HomeLink}}" title="{{.Name}}{{if .FullName}} ({{.FullName}}){{end}}">
{{ctx.AvatarUtils .}}
</a>
{{end}}
{{end}}
</div>
{{if .IsOrganizationMember}}
<div class="ui top attached header df">
<strong class="f1">{{.i18n.Tr "org.teams"}}</strong>
<div class="ui">
<a class="text grey dif ac"
href="{{.OrgLink}}/teams"><span>{{.Org.NumTeams}}</span> {{svg "octicon-chevron-right"}}
</a>
</div>
</div>
<div class="ui attached table segment teams">
{{range .Teams}}
<div class="item">
<a href="{{$.OrgLink}}/teams/{{.LowerName}}"><strong
class="team-name">{{.Name}}</strong></a>
<p class="text grey">
<a href="{{$.OrgLink}}/teams/{{.LowerName}}"><strong>{{.NumMembers}}</strong> {{$.i18n.Tr "org.lower_members"}}
</a> ·
<a href="{{$.OrgLink}}/teams/{{.LowerName}}/repositories"><strong>{{.NumRepos}}</strong> {{$.i18n.Tr "org.lower_repositories"}}
</a>
</p>
</div>
{{end}}
</div>
{{if .IsOrganizationOwner}}
<div class="ui bottom attached segment">
<a class="ui blue small button"
href="{{.OrgLink}}/teams/new">{{.i18n.Tr "org.create_new_team"}}</a>
</div>
{{end}}
{{end}}
</div>
{{ end }}
</div>
</div>
</div>
{{template "base/footer" .}}

View File

@ -0,0 +1,27 @@
<button class="ui basic clone button no-transition" id="repo-clone-ssh"
data-link="{{if $.PageIsWiki}}{{$.WikiCloneLink.SSH}}{{else}}{{$.CloneLink.SSH}}{{end}}">
SSH
</button>
<input id="repo-clone-url" value="{{if $.PageIsWiki}}{{$.WikiCloneLink.SSH}}{{else}}{{$.CloneLink.SSH}}{{end}}"
readonly>
<button class="ui basic icon button poping up" id="clipboard-btn" data-original="{{.i18n.Tr "repo.copy_link"}}"
data-success="{{.i18n.Tr "repo.copy_link_success"}}" data-error="{{.i18n.Tr "repo.copy_link_error"}}"
data-content="{{.i18n.Tr "repo.copy_link"}}" data-variation="inverted tiny"
data-clipboard-target="#repo-clone-url">
Copy
</button>
{{if not (and $.DisableHTTP $.DisableSSH)}}
<script defer>
const isSSH = true
const sshButton = document.getElementById('repo-clone-ssh');
const input = document.getElementById('repo-clone-url');
if (input) input.value = sshButton.dataset.link;
if (sshButton) sshButton.classList['add']('primary');
setTimeout(() => {
if (sshButton) sshButton.classList.remove('no-transition');
}, 100);
</script>
{{end}}

View File

@ -0,0 +1,151 @@
<table id="repo-files-table" class="ui single line table">
<thead>
<tr class="commit-list">
<th>
{{if .LatestCommitUser}}
{{ctx.AvatarUtils .LatestCommitUser 24}}
{{if .LatestCommitUser.FullName}}
<a href="{{AppSubUrl}}/{{.LatestCommitUser.Name}}"><strong>{{.LatestCommitUser.FullName}}</strong></a>
{{else}}
<a href="{{AppSubUrl}}/{{.LatestCommitUser.Name}}"><strong>{{if .LatestCommit.Author}}{{.LatestCommit.Author.Name}}{{else}}{{.LatestCommitUser.Name}}{{end}}</strong></a>
{{end}}
{{else}}
{{if .LatestCommit.Author}}
{{ctx.AvatarUtils.AvatarByEmail .LatestCommit.Author.Email .LatestCommit.Author.Name 24}}
<strong>{{.LatestCommit.Author.Name}}</strong>
{{end}}
{{end}}
</th>
<th>{{ $commitLink:= printf "%s/commit/%s" .RepoLink .LatestCommit.ID }}
{{template "repo/commit_statuses" dict "Status" .LatestCommitStatus "Statuses" .LatestCommitStatuses "root" $}}
<span class="grey commit-summary" title="{{.LatestCommit.Summary}}"><span
class="message-wrapper">{{RenderCommitMessageLinkSubject .LatestCommit.Message $.RepoLink $commitLink $.Repository.ComposeMetas}}</span>
{{if IsMultilineCommitMessage .LatestCommit.Message}}
<button class="basic compact mini ui icon button commit-button"><i
class="ellipsis horizontal icon"></i></button>
<pre class="commit-body"
style="display: none;">{{RenderCommitBody .LatestCommit.Message $.RepoLink $.Repository.ComposeMetas}}</pre>
{{end}}
</span>
</th>
<th id="total"></th>
<th class="text grey right age">{{if .LatestCommit.Author}}{{TimeSince .LatestCommit.Author.When $.Lang}}{{end}}</th>
</tr>
</thead>
<tbody>
{{if .HasParentPath}}
<tr class="has-parent">
<td colspan="3">{{svg "octicon-reply"}}<a href="{{$.BranchLink}}{{.ParentPath}}">..</a></td>
</tr>
{{end}}
{{range $item := .Files}}
{{$entry := $item.Entry}}
{{$commit := $item.Commit}}
{{$subModuleFile := $item.SubModuleFile}}
<tr>
<td class="name four wide">
<span class="truncate">
{{if $entry.IsSubModule}}
{{svg "octicon-file-submodule"}}
{{$refURL := $subModuleFile.RefURL AppUrl $.Repository.FullName $.SSHDomain}}
{{if $refURL}}
<a href="{{$refURL}}">{{$entry.Name}}</a><span class="at">@</span><a
href="{{$refURL}}/commit/{{$subModuleFile.RefID}}">{{ShortSha $subModuleFile.RefID}}</a>
{{else}}
{{$entry.Name}}<span class="at">@</span>{{ShortSha $subModuleFile.RefID}}
{{end}}
{{else}}
{{if $entry.IsDir}}
{{$subJumpablePathName := $entry.GetSubJumpablePathName}}
{{svg "octicon-file-directory-fill"}}
<a class="muted" href="{{$.TreeLink}}/{{PathEscapeSegments $subJumpablePathName}}" title="{{$subJumpablePathName}}">
{{$subJumpablePathFields := StringUtils.Split $subJumpablePathName "/"}}
{{$subJumpablePathFieldLast := (Eval (len $subJumpablePathFields) "-" 1)}}
{{if eq $subJumpablePathFieldLast 0}}
{{$subJumpablePathName}}
{{else}}
{{$subJumpablePathPrefixes := slice $subJumpablePathFields 0 $subJumpablePathFieldLast}}
<span class="text light-2">{{StringUtils.Join $subJumpablePathPrefixes "/"}}</span>/{{index $subJumpablePathFields $subJumpablePathFieldLast}}
{{end}}
</a>
{{else}}
{{svg (printf "octicon-%s" (EntryIcon $entry))}}
<a href="{{$.TreeLink}}/{{$entry.Name}}"
title="{{$entry.Name}}">{{$entry.Name}}</a>
{{end}}
{{end}}
</span>
</td>
<td class="message five wide">
<span class="truncate">
<a href="{{$.RepoLink}}/commit/{{$commit.ID}}"
title="{{$commit.Summary}}">{{$commit.Summary | RenderEmoji}}</a>
</span>
</td>
<td class="message four wide center centered">
<span style="display: none;" class="ui basic label" id="{{$entry.Name}}">
</span>
</td>
<td class="text right age three wide">{{TimeSince $commit.Committer.When $.Lang}}</td>
</tr>
{{end}}
</tbody>
</table>
{{if .ReadmeExist}}
{{template "repo/view_file" .}}
{{end}}
<script>
if (!("{{.Name}}" === "tutors" || "{{.Name}}" === "template")) {
fetch(COURSES_URL + "/courses/{{$.Repository.Owner.Name}}/{{$.Repository.Name}}/exercises", {
referrerPolicy: "origin",
credentials: "include",
redirect: 'follow'
}).then(res => {
if (!res.ok) {
if (res.status === 401) {
const form = document.createElement("form")
form.action = "/user/logout"
form.method = "POST"
document.body.appendChild(form);
form.submit()
}
} else {
res.text().then(res => {
let json = JSON.parse(res)
let total = document.getElementById("total")
total.innerText = json["total"] + " / " + json["max_total"] + " (" + json["percentage"] + "%)"
for (let [name, info] of Object.entries(json["exercises"])) {
let el = document.getElementById(name)
if (el !== null) {
if (info["finished"]) {
if (info["points"] !== null) {
el.innerText = info["points"].toString() + " / " + info["max_points"].toString()
el.classList.add("black")
} else {
el.innerText = "grading in process"
el.classList.add("black")
}
} else {
el.innerText = info["message"]
if (info["important_message"]) {
el.classList.add("red")
}
}
el.style.removeProperty("display")
}
}
})
}
}).catch(_ => {
{{if .IsAdmin}}
{{else}}
const form = document.createElement("form")
form.action = "/user/logout"
form.method = "POST"
document.body.appendChild(form);
form.submit()
{{end}}
})
}
</script>

View File

@ -0,0 +1,69 @@
<div class="header-wrapper">
{{with .Repository}}
<div class="ui container">
<div class="repo-header">
<div class="repo-title-wrap df fc mb-4">
<div class="repo-title">
<a href="{{AppSubUrl}}/{{.Owner.Name}}" id="repo_owner">{{.Owner.FullName}}</a>
<div class="mx-2" style="display: none" id="slash">/</div>
<a href="{{$.RepoLink}}" id="repo_name" style="display: none">{{.Name}}</a>
</div>
</div>
<div class="ui labeled button mb-4" data-position="top center" data-variation="tiny" tabindex="0">
<a class="ui compact small basic button disabled" id="tutor">
Tutor
</a>
<a class="ui basic label disabled" id="tutor_name">
Not found
</a>
</div>
</div>
</div><!-- end grid -->
</div><!-- end container -->
<div class="ui tabs divider"></div>
<script>
function no_course_repo() {
document.getElementById("slash").style.removeProperty("display")
document.getElementById("repo_name").style.removeProperty("display")
document.getElementById("repo_owner").innerText = "{{.Owner.Name}}"
document.getElementById("tutor").style.display = "none"
document.getElementById("tutor_name").style.display = "none"
setTimeout(function () {
document.getElementById("builds").style.display = "none"
document.getElementById("chat").style.display = "none"
}, 1)
}
if ("{{.Name}}" === "tutors" || "{{.Name}}" === "template") {
no_course_repo()
} else {
fetch(COURSES_URL + "/courses/{{.Owner.Name}}/{{.Name}}/tutor", {
referrerPolicy: "origin",
credentials: "include"
}).then(res => {
if (!res.ok) {
if (res.status === 401) {
const form = document.createElement("form")
form.action = "/user/logout"
form.method = "POST"
document.body.appendChild(form);
form.submit()
} else if (res.status === 500) {
no_course_repo()
}
} else {
res.text().then(res => {
let tutor = JSON.parse(res)
let el = document.getElementById("tutor_name")
el.innerText = tutor["name"]
el.setAttribute("href", "/" + tutor["username"])
el.classList.remove("disabled")
})
}
}).catch(_ => {
no_course_repo()
})
}
</script>
{{end}}

124
templates/repo/home.tmpl Normal file
View File

@ -0,0 +1,124 @@
{{template "base/head" .}}
{{ $n := len .TreeNames}}
{{$l := Eval $n "-" 1}}
<div class="page-content repository file list {{if .IsBlame}}blame{{end}}">
{{template "repo/header" .}}
<div class="ui container {{if .IsBlame}}fluid padded{{end}}">
{{template "base/alert" .}}
{{if and .Permission.IsAdmin (not .Repository.IsArchived)}}
<div class="ui repo-topic-edit grid form" id="topic_edit" style="display:none">
<div class="fourteen wide column">
<div class="field">
<div class="ui fluid multiple search selection dropdown">
<input type="hidden" name="topics"
value="{{range $i, $v := .Topics}}{{.Name}}{{if lt (Eval $i "+" 1) (len $.Topics)}},{{end}}{{end}}">
{{range .Topics}}
<div class="ui small label topic transition visible" data-value="{{.Name}}"
style="display: inline-block !important; cursor: default;">{{.Name}}{{svg "octicon-x" 16 "delete icon ml-3 mt-1"}}</div>
{{end}}
<div class="text"></div>
</div>
</div>
</div>
</div>
{{end}}
{{if .Repository.IsArchived}}
<div class="ui warning message">
{{.i18n.Tr "repo.archive.title"}}
</div>
{{end}}
<div class="ui stackable secondary menu mobile--margin-between-items mobile--no-negative-margins">
{{if eq $n 0}}
<div class="left fitted item mr-0">
<div class="ui tiny primary buttons">
{{if .Permission.IsAdmin}}
<a class="ui button" href="{{.RepoLink}}/settings">
{{.i18n.Tr "repo.settings"}}
</a>
{{end}}
<a class="ui button" target="_blank" id="builds">
Builds
</a>
<a class="ui button" href="{{.RepoLink}}/commits/branch/master">
Commits
</a>
</div>
</div>
{{else}}
<div class="fitted item"><span class="ui breadcrumb repo-path"><a class="section"
href="{{.RepoLink}}/src/{{.BranchNameSubURL}}"
title="{{.Repository.Name}}">{{StringUtils.EllipsisString .Repository.Name 30}}</a>{{range $i, $v := .TreeNames}}<span
class="divider">/</span>{{if eq $i $l}}<span class="active section"
title="{{$v}}">{{StringUtils.EllipsisString $v 30}}</span>{{else}}{{ $p := index $.Paths $i}}
<span class="section"><a href="{{$.BranchLink}}/{{$p}}"
title="{{$v}}">{{StringUtils.EllipsisString $v 30}}</a></span>{{end}}{{end}}</span>
</div>
{{end}}
<!-- If home page, show new PR. If not, show breadcrumb -->
<div class="right fitted item mr-0" id="file-buttons">
<div class="ui tiny primary buttons">
{{if .Repository.CanEnableEditor}}
{{if .CanAddFile}}
<a href="{{.RepoLink}}/_new/{{.BranchName}}/{{.TreePath}}"
class="ui button">
{{.i18n.Tr "repo.editor.new_file"}}
</a>
{{end}}
{{if .CanUploadFile}}
<a href="{{.RepoLink}}/_upload/{{.BranchName}}/{{.TreePath}}"
class="ui button">
{{.i18n.Tr "repo.editor.upload_file"}}
</a>
{{end}}
{{end}}
{{if and (ne $n 0) (not .IsViewFile) (not .IsBlame) }}
<a href="{{.RepoLink}}/commits/{{.BranchNameSubURL}}/{{.TreePath}}"
class="ui button">
{{.i18n.Tr "repo.file_history"}}
</a>
{{end}}
</div>
</div>
<div class="fitted item">
<!-- Only show clone panel in repository home page -->
{{if eq $n 0}}
<div class="ui action tiny input" id="clone-panel">
{{template "repo/clone_buttons" .}}
<button id="download-btn" class="ui basic jump dropdown icon button poping up"
data-content="{{.i18n.Tr "repo.download_archive"}}" data-variation="tiny inverted"
data-position="top right">
{{svg "octicon-download"}}
<div class="menu">
<a class="item archive-link"
data-url="{{$.RepoLink}}/archive/{{$.BranchName}}.zip">{{svg "octicon-file-zip"}}
&nbsp;{{.i18n.Tr "repo.download_zip"}}</a>
<a class="item archive-link"
data-url="{{$.RepoLink}}/archive/{{$.BranchName}}.tar.gz">{{svg "octicon-file-zip"}}
&nbsp;{{.i18n.Tr "repo.download_tar"}}</a>
<a class="item archive-link"
data-url="{{$.RepoLink}}/archive/{{$.BranchName}}.bundle">{{svg "octicon-package"}}
&nbsp;{{.i18n.Tr "repo.download_bundle"}}</a>
</div>
</button>
</div>
{{end}}
</div>
</div>
{{if .IsViewFile}}
{{template "repo/view_file" .}}
{{else if .IsBlame}}
{{template "repo/blame" .}}
{{else}}
{{if eq $n 0}}
{{template "repo/course_view_list" .}}
{{else}}
{{template "repo/view_list" .}}
{{end}}
{{end}}
</div>
</div>
{{template "base/footer" .}}
<script>
document.getElementById("builds").setAttribute("href", BUILD_URL + "/builds/{{ .Repository.Owner.Name }}/{{.Repository.Name}}")
</script>

View File

@ -0,0 +1,38 @@
<div class="ui segments repository-summary{{if and (.Permission.CanRead $.UnitTypeCode) (not .IsEmptyRepo) .LanguageStats }} repository-summary-language-stats{{end}} mt-3">
<div class="ui segment sub-menu repository-menu">
<div class="ui two horizontal center link list">
{{if and (.Permission.CanRead $.UnitTypeCode) (not .IsEmptyRepo)}}
<div class="item{{if .PageIsCommits}} active{{end}}">
<a class="ui" href="{{.RepoLink}}/commits{{if .IsViewBranch}}/branch{{else if .IsViewTag}}/tag{{else if .IsViewCommit}}/commit{{end}}/{{.BranchName}}">{{svg "octicon-history"}} <b>{{.CommitsCount}}</b> {{.i18n.Tr (ctx.Locale.TrN .i18n.Lang .CommitsCount "repo.commit" "repo.commits") }}</a>
</div>
<div class="item">
<span class="ui">{{svg "octicon-database"}} <b>{{FileSize .Repository.Size}}</b></span>
</div>
{{end}}
</div>
</div>
{{if and (.Permission.CanRead $.UnitTypeCode) (not .IsEmptyRepo) .LanguageStats }}
<div class="ui segment sub-menu language-stats-details" style="display: none">
<div class="ui horizontal center link list">
{{range .LanguageStats}}
<div class="item df ac jc">
<i class="color-icon mr-3" style="background-color: {{ .Color }}"></i>
<span class="bold mr-3">
{{if eq .Language "other" }}
{{ $.i18n.Tr "repo.language_other" }}
{{else}}
{{ .Language }}
{{end}}
</span>
{{ .Percentage }}%
</div>
{{end}}
</div>
</div>
<a class="ui segment language-stats">
{{range .LanguageStats}}
<div class="bar" style="width: {{ .Percentage }}%; background-color: {{ .Color }}">&nbsp;</div>
{{end}}
</a>
{{end}}
</div>

View File

@ -0,0 +1,183 @@
<div class="ui container">
<div class="ui container" style="margin-bottom: 2vh; display: none" id="info">
<h4 class="ui top attached header">
Information
</h4>
<div class="ui attached segment" id="info_content">
Currently there are no courses available, check back later.
</div>
</div>
<div class="ui container" style="margin-bottom: 2vh; display: none" id="my_courses">
<h4 class="ui top attached header">
Your Courses
</h4>
<div class="ui attached table segment">
<div class="ui attached table segment">
<table class="ui very basic striped table">
<thead>
<tr>
<th>UID</th>
<th>Name</th>
<th>Website</th>
<th>Role</th>
<th>Action</th>
</tr>
</thead>
<tbody id="my_courses_table">
</tbody>
</table>
</div>
</div>
</div>
<div class="ui container" style="margin-bottom: 2vh; display: none" id="courses">
<h4 class="ui top attached header">
Available Courses
</h4>
<div class="ui attached table segment">
<div class="ui attached table segment">
<table class="ui very basic striped table">
<thead>
<tr>
<th>UID</th>
<th>Name</th>
<th>Website</th>
<th>Action</th>
</tr>
</thead>
<tbody id="courses_table"></tbody>
</table>
</div>
</div>
</div>
</div>
<script>
fetch(COURSES_URL + "/courses/list", {
referrerPolicy: "origin",
credentials: "include",
redirect: 'follow',
}).then(res => res.text()).then(res => {
for (let [name, info] of Object.entries(JSON.parse(res))) {
if (info["role"] !== null && info["role"] !== "admin" && (!info["restricted"] || info["role"] !== "student")) {
const tr = document.createElement("tr")
const uid = document.createElement("td")
const link = document.createElement("a")
link.href = "/" + name
link.innerText = name
uid.appendChild(link)
tr.appendChild(uid)
const display = document.createElement("td")
display.innerText = info["display_name"]
tr.appendChild(display)
const website = document.createElement("td")
const link2 = document.createElement("a")
link2.href = info["website"]
link2.innerText = info["website"]
website.appendChild(link2)
tr.appendChild(website)
const role = document.createElement("td")
role.innerText = info["role"]
tr.appendChild(role)
const repo = document.createElement("td")
const form2 = document.createElement("form")
if (info["role"] === "student") {
form2.action = "/" + name + "/{{.SignedUser.Name}}"
} else {
form2.action = "/" + name
}
form2.method = "GET"
const btn2 = document.createElement("button")
btn2.type = "submit"
btn2.classList.add("ui", "button", "green")
btn2.innerText = "OPEN"
form2.appendChild(btn2)
repo.appendChild(form2)
tr.appendChild(repo)
document.getElementById("my_courses_table").appendChild(tr)
} else if (info["open"] || info["role"] === "admin") {
const tr = document.createElement("tr")
const uid = document.createElement("td")
uid.innerText = name
tr.appendChild(uid)
const display = document.createElement("td")
display.innerText = info["display_name"]
tr.appendChild(display)
const website = document.createElement("td")
const link = document.createElement("a")
link.href = info["website"]
link.innerText = info["website"]
website.appendChild(link)
tr.appendChild(website)
const join = document.createElement("td")
const form = document.createElement("form")
if (info["role"] !== "admin") {
form.method = "POST"
form.action = COURSES_URL + "/courses/join"
const hidden = document.createElement("input")
hidden.type = "hidden"
hidden.name = "course"
hidden.value = name
form.appendChild(hidden)
} else {
form.method = "GET"
form.action = "/" + name
}
const btn = document.createElement("button")
btn.type = "submit"
btn.classList.add("ui", "button", "red")
if (info["role"] !== "admin") {
btn.innerText = "JOIN"
btn.id = name
btn.onsubmit = function () {
document.getElementById(name).disabled = true
}
} else {
btn.innerText = "OPEN"
}
form.appendChild(btn)
join.appendChild(form)
tr.appendChild(join)
document.getElementById("courses_table").appendChild(tr)
}
}
let my_c = document.getElementById("my_courses_table").childElementCount
let c = document.getElementById("courses_table").childElementCount
if (my_c !== 0) {
document.getElementById("my_courses").style.removeProperty("display")
}
if (c !== 0) {
document.getElementById("courses").style.removeProperty("display")
}
if (my_c === 0 && c === 0) {
document.getElementById("info").style.removeProperty("display")
}
}).catch(_ => {
{{if .IsAdmin}}
document.getElementById("info").style.removeProperty("display")
document.getElementById("info_content").innerText = "you are admin. if you are non-oauth-user -> no courses, otherwise re-login to gitea."
{{else}}
const form = document.createElement("form")
form.action = "/user/logout"
form.method = "POST"
document.body.appendChild(form);
form.submit()
{{end}}
});
</script>

View File

@ -0,0 +1,15 @@
{{template "base/head" .}}
<div class="page-content dashboard feeds">
<div class="ui container">
{{template "base/alert" .}}
<div class="ui stackable grid">
<div class="ui row">
<div class="ui container ten wide column">
{{template "user/dashboard/courses" .}}
</div>
{{template "user/dashboard/named_feeds" .}}
</div>
</div>
</div>
</div>
{{template "base/footer" .}}

View File

@ -0,0 +1,11 @@
<div class="ui container six wide column">
<h4 class="ui top attached header">
Feed
</h4>
<div class="ui attached segment">
{{template "user/dashboard/feeds" .}}
{{ $length := len .Feeds }} {{ if eq $length 0 }}
Nothing to see here, at least yet.
{{ end }}
</div>
</div>

View File

@ -0,0 +1,78 @@
{{template "base/head" .}}
<div class="page-content user profile">
<div class="ui container">
<div class="ui stackable middle very relaxed page grid">
<div class="ui seven wide center aligned centered column">
<div class="ui card">
{{if eq .SignedUserName .Owner.Name}}
<a class="image poping up" href="{{AppSubUrl}}/user/settings" id="profile-avatar"
data-content="{{.i18n.Tr "user.change_avatar"}}" data-variation="inverted tiny"
data-position="bottom center">
{{ctx.AvatarUtils.Owner 290}}
</a>
{{else}}
<span class="image" id="profile-avatar">
{{ctx.AvatarUtils.Owner 290}}
</span>
{{end}}
<div class="content word-break profile-avatar-name">
{{if .Owner.FullName}}<span class="header text center">{{.Owner.FullName}}</span>{{end}}
<span class="username text center">{{.Owner.Name}}</span>
</div>
<div class="extra content word-break">
<ul>
{{if .Owner.Location}}
<li>{{svg "octicon-location"}} {{.Owner.Location}}</li>
{{end}}
{{if .ShowUserEmail }}
<li>
{{svg "octicon-mail"}}
<a href="mailto:{{.Owner.Email}}" rel="nofollow">{{.Owner.Email}}</a>
</li>
{{end}}
{{if .Owner.Website}}
<li>
{{svg "octicon-link"}}
<a target="_blank" rel="noopener noreferrer me"
href="{{.Owner.Website}}">{{.Owner.Website}}</a>
</li>
{{end}}
{{if $.RenderedDescription}}
<li>
<div class="render-content markup">{{$.RenderedDescription|Str2html}}</div>
</li>
{{end}}
{{range .OpenIDs}}
{{if .Show}}
<li>
{{svg "fontawesome-openid"}}
<a target="_blank" rel="noopener noreferrer" href="{{.URI}}">{{.URI}}</a>
</li>
{{end}}
{{end}}
<li>{{svg "octicon-clock"}} {{.i18n.Tr "user.join_on"}} {{.Owner.CreatedUnix.FormatShort}}</li>
{{if .Orgs}}
<li>
<ul class="user-orgs">
{{range .Orgs}}
{{if or (.Visibility.IsPublic) ($.IsAdmin)}}
<li>
<a class="poping up" href="{{.HomeLink}}" data-content="{{.Name}}"
data-position="top center" data-variation="tiny inverted">
{{ctx.AvatarUtils .}}
</a>
</li>
{{end}}
{{end}}
</ul>
</li>
{{end}}
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
{{template "base/footer" .}}

View File

@ -0,0 +1,124 @@
{{template "base/head" .}}
<div class="page-content user settings account">
{{template "user/settings/navbar" .}}
<div class="ui container">
{{template "base/alert" .}}
<h4 class="ui top attached header">
{{.i18n.Tr "settings.manage_emails"}}
</h4>
<div class="ui attached segment">
<div class="ui email list">
{{range .Emails}}
<div class="item">
{{if not .IsPrimary}}
<div class="right floated content">
<button class="ui red tiny button delete-button" data-modal-id="delete-email" data-url="{{AppSubUrl}}/user/settings/account/email/delete" data-id="{{.ID}}">
{{$.i18n.Tr "settings.delete_email"}}
</button>
</div>
{{if .CanBePrimary}}
<div class="right floated content">
<form action="{{AppSubUrl}}/user/settings/account/email" method="post">
{{$.CsrfTokenHtml}}
<input name="_method" type="hidden" value="PRIMARY">
<input name="id" type="hidden" value="{{.ID}}">
<button class="ui blue tiny button">{{$.i18n.Tr "settings.primary_email"}}</button>
</form>
</div>
{{end}}
{{end}}
{{if not .IsActivated}}
<div class="right floated content">
<form action="{{AppSubUrl}}/user/settings/account/email" method="post">
{{$.CsrfTokenHtml}}
<input name="_method" type="hidden" value="SENDACTIVATION">
<input name="id" type="hidden" value="{{.ID}}">
{{if $.ActivationsPending}}
<button disabled class="ui blue tiny button">{{$.i18n.Tr "settings.activations_pending"}}</button>
{{else}}
<button class="ui blue tiny button">{{$.i18n.Tr "settings.activate_email"}}</button>
{{end}}
</form>
</div>
{{end}}
<div class="content">
<strong>{{.Email}}</strong>
{{if .IsPrimary}}
<div class="ui blue label">{{$.i18n.Tr "settings.primary"}}</div>
{{end}}
{{if .IsActivated}}
<div class="ui green label">{{$.i18n.Tr "settings.activated"}}</div>
{{else}}
<div class="ui label">{{$.i18n.Tr "settings.requires_activation"}}</div>
{{end}}
</div>
</div>
{{end}}
</div>
</div>
<div class="ui attached bottom segment">
<form class="ui form" action="{{AppSubUrl}}/user/settings/account/email" method="post">
{{.CsrfTokenHtml}}
<div class="required field {{if .Err_Email}}error{{end}}">
<label for="email">{{.i18n.Tr "settings.add_new_email"}}</label>
<input id="email" name="email" type="email" required {{if not .CanAddEmails}}disabled{{end}}>
</div>
<button class="ui green button" {{if not .CanAddEmails}}disabled{{end}}>
{{.i18n.Tr "settings.add_email"}}
</button>
</form>
</div>
<h4 class="ui top attached header">
{{.i18n.Tr "settings.manage_themes"}}
</h4>
<div class="ui attached segment">
<div class="ui email list">
<div class="item">
{{.i18n.Tr "settings.theme_desc"}}
</div>
<form class="ui form" action="{{.Link}}/theme" method="post">
{{.CsrfTokenHtml}}
<div class="field">
<label for="ui">{{.i18n.Tr "settings.ui"}}</label>
<div class="ui selection dropdown" id="ui">
<input name="theme" type="hidden" value="{{.SignedUser.Theme}}">
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
<div class="text">
{{range $i,$a := .AllThemes}}
{{if eq $.SignedUser.Theme $a}}{{$a}}{{end}}
{{end}}
</div>
<div class="menu">
{{range $i,$a := .AllThemes}}
<div class="item{{if eq $.SignedUser.Theme $a}} active selected{{end}}" data-value="{{$a}}">
{{$a}}
</div>
{{end}}
</div>
</div>
</div>
<div class="field">
<button class="ui green button">{{$.i18n.Tr "settings.update_theme"}}</button>
</div>
</form>
</div>
</div>
</div>
</div>
<div class="ui small basic delete modal" id="delete-email">
<div class="ui icon header">
{{svg "octicon-trash"}}
{{.i18n.Tr "settings.email_deletion"}}
</div>
<div class="content">
<p>{{.i18n.Tr "settings.email_deletion_desc"}}</p>
</div>
{{template "base/delete_modal_actions" .}}
</div>
{{template "base/footer" .}}

View File

@ -0,0 +1,12 @@
{{template "base/head" .}}
<div class="page-content user settings sshkeys">
{{template "user/settings/navbar" .}}
<div class="ui container">
{{template "base/alert" .}}
{{template "user/settings/keys_ssh" .}}
{{template "user/settings/keys_principal" .}}
{{template "user/settings/keys_gpg" .}}
</div>
</div>
{{template "base/footer" .}}

View File

@ -0,0 +1,13 @@
<div class="ui secondary pointing tabular top attached borderless menu stackable new-menu navbar">
<div class="new-menu-inner">
<a class="{{if .PageIsSettingsProfile}}active{{end}} item" href="{{AppSubUrl}}/user/settings">
Profile
</a>
<a class="{{if .PageIsSettingsAccount}}active{{end}} item" href="{{AppSubUrl}}/user/settings/account">
Account
</a>
<a class="{{if .PageIsSettingsKeys}}active{{end}} item" href="{{AppSubUrl}}/user/settings/keys">
Keys
</a>
</div>
</div>

View File

@ -0,0 +1,102 @@
{{template "base/head" .}}
<div class="page-content user settings profile">
{{template "user/settings/navbar" .}}
<div class="ui container">
{{template "base/alert" .}}
<h4 class="ui top attached header">
{{.i18n.Tr "settings.public_profile"}}
</h4>
<div class="ui attached segment">
<p>{{.i18n.Tr "settings.profile_desc"}}</p>
<form class="ui form" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}}
<div class="field {{if .Err_FullName}}error{{end}}">
<label for="full_name">{{.i18n.Tr "settings.full_name"}}</label>
<input id="full_name" name="full_name" value="{{.SignedUser.FullName}}">
</div>
<div class="field {{if .Err_Email}}error{{end}}">
<label for="email">{{.i18n.Tr "email"}}</label>
<p>{{.SignedUser.Email}}</p>
</div>
<div class="inline field">
<div class="ui checkbox" id="keep-email-private">
<label class="poping up" data-content="{{.i18n.Tr "settings.keep_email_private_popup"}}"><strong>{{.i18n.Tr "settings.keep_email_private"}}</strong></label>
<input name="keep_email_private" type="checkbox" {{if .SignedUser.KeepEmailPrivate}}checked{{end}}>
</div>
</div>
<div class="field {{if .Err_Description}}error{{end}}">
<label for="description">{{$.i18n.Tr "user.user_bio"}}</label>
<textarea id="description" name="description" rows="2" placeholder="{{.i18n.Tr "settings.biography_placeholder"}}">{{.SignedUser.Description}}</textarea>
</div>
<div class="field {{if .Err_Website}}error{{end}}">
<label for="website">{{.i18n.Tr "settings.website"}}</label>
<input id="website" name="website" type="url" value="{{.SignedUser.Website}}">
</div>
<div class="field">
<label for="location">{{.i18n.Tr "settings.location"}}</label>
<input id="location" name="location" value="{{.SignedUser.Location}}">
</div>
<div class="field">
<label for="language">{{.i18n.Tr "settings.language"}}</label>
<div class="ui language selection dropdown" id="language">
<input name="language" type="hidden" value="{{.SignedUser.Language}}">
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
<div class="text">{{range .AllLangs}}{{if eq $.SignedUser.Language .Lang}}{{.Name}}{{end}}{{end}}</div>
<div class="menu">
{{range .AllLangs}}
<div class="item{{if eq $.SignedUser.Language .Lang}} active selected{{end}}" data-value="{{.Lang}}">{{.Name}}</div>
{{end}}
</div>
</div>
</div>
<div class="ui divider"></div>
<!-- private block -->
<div class="field">
<button class="ui green button">{{$.i18n.Tr "settings.update_profile"}}</button>
</div>
</form>
</div>
<h4 class="ui top attached header">
{{.i18n.Tr "settings.avatar"}}
</h4>
<div class="ui attached segment">
<form class="ui form" action="{{.Link}}/avatar" method="post" enctype="multipart/form-data">
{{.CsrfTokenHtml}}
{{if not .DisableGravatar}}
<div class="inline field">
<div class="ui radio checkbox">
<input name="source" value="lookup" type="radio" {{if not .SignedUser.UseCustomAvatar}}checked{{end}}>
<label>{{.i18n.Tr "settings.lookup_avatar_by_mail"}}</label>
</div>
</div>
<div class="field {{if .Err_Gravatar}}error{{end}}">
<label for="gravatar">Avatar {{.i18n.Tr "email"}}</label>
<input id="gravatar" name="gravatar" value="{{.SignedUser.AvatarEmail}}" />
</div>
{{end}}
<div class="inline field">
<div class="ui radio checkbox">
<input name="source" value="local" type="radio" {{if .SignedUser.UseCustomAvatar}}checked{{end}}>
<label>{{.i18n.Tr "settings.enable_custom_avatar"}}</label>
</div>
</div>
<div class="inline field">
<label for="avatar">{{.i18n.Tr "settings.choose_new_avatar"}}</label>
<input name="avatar" type="file" >
</div>
<div class="field">
<button class="ui green button">{{$.i18n.Tr "settings.update_avatar"}}</button>
<a class="ui red button delete-post" data-request-url="{{.Link}}/avatar/delete" data-done-url="{{.Link}}">{{$.i18n.Tr "settings.delete_current_avatar"}}</a>
</div>
</form>
</div>
</div>
</div>
{{template "base/footer" .}}