~jan0sch/smederee
Showing details for patch 4984ab36d221ab29528b5a63a83b865840983fc8.
diff -rN -u old-smederee/modules/hub/src/main/resources/assets/css/main.css new-smederee/modules/hub/src/main/resources/assets/css/main.css --- old-smederee/modules/hub/src/main/resources/assets/css/main.css 2025-02-02 12:11:36.327101925 +0000 +++ new-smederee/modules/hub/src/main/resources/assets/css/main.css 2025-02-02 12:11:36.331101932 +0000 @@ -26,6 +26,10 @@ padding: 1em; } +.l-box-left-right { + padding: 0em 1em 0em 1em; +} + .l-box-lrg { padding: 2em; border-bottom: 1px solid rgba(0,0,0,0.1); @@ -100,6 +104,15 @@ color: #AECFE5; } +.repo-summary-description { + background-color: #eee; + padding: 0em 0.5em 0em 0.5em; +} + +.repo-summary-description code { + word-wrap: break-word; +} + pre.latest-changes { overflow-x: auto; overflow-y: hidden; diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/html/LinkTools.scala new-smederee/modules/hub/src/main/scala/de/smederee/html/LinkTools.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/html/LinkTools.scala 1970-01-01 00:00:00.000000000 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/html/LinkTools.scala 2025-02-02 12:11:36.331101932 +0000 @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2022 Contributors as noted in the AUTHORS.md file + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package de.smederee.html + +import cats.syntax.all._ +import de.smederee.hub.config.ExternalLinkConfig +import org.http4s.Uri + +object LinkTools { + + extension (linkConfig: ExternalLinkConfig) { + + /** Take the given URI path and create a full URI using the specified configuration with a possible path + * prefix and append the given path to it. + * + * @param path + * An URI containing a path with possible URL fragment and query parameters which will be used to + * construct the full URI. + * @return + * A full URI created from the values of the ExternalLinkConfig (scheme, host, port, possible path + * prefix) and the path data from the given URI. + */ + def createFullUri(path: Uri): Uri = { + val completePath = linkConfig.path match { + case None => path.path + case Some(pathPrefix) => pathPrefix.path |+| path.path + } + val baseUri = Uri( + scheme = Option(linkConfig.scheme), + authority = Option( + Uri.Authority( + userInfo = None, + host = Uri.Host.fromIp4sHost(linkConfig.host), + port = linkConfig.port.map(_.value) + ) + ), + path = completePath + ).withQueryParams(path.params) + path.fragment.fold(baseUri)(fragment => baseUri.withFragment(fragment)) + } + } + +} diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/HubServer.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/HubServer.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/HubServer.scala 2025-02-02 12:11:36.327101925 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/HubServer.scala 2025-02-02 12:11:36.331101932 +0000 @@ -109,9 +109,13 @@ authenticationRepo, signAndValidate ) - signUpRepo = new DoobieSignupRepository[IO](transactor) - signUpRoutes = new SignupRoutes[IO](configuration.service.signup, signUpRepo) - landingPages = new LandingPageRoutes[IO]() + signUpRepo = new DoobieSignupRepository[IO](transactor) + signUpRoutes = new SignupRoutes[IO]( + configuration.service.external, + configuration.service.signup, + signUpRepo + ) + landingPages = new LandingPageRoutes[IO](configuration.service.external) vcsMetadataRepo = new DoobieVcsMetadataRepository[IO](transactor) vcsRepoRoutes = new VcsRepositoryRoutes[IO]( configuration.service.darcs, diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/LandingPageRoutes.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/LandingPageRoutes.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/LandingPageRoutes.scala 2025-02-02 12:11:36.327101925 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/LandingPageRoutes.scala 2025-02-02 12:11:36.331101932 +0000 @@ -19,7 +19,9 @@ import cats.effect._ import cats.syntax.all._ +import de.smederee.html.LinkTools._ import de.smederee.hub.RequestHelpers.instances.given +import de.smederee.hub.config.ExternalLinkConfig import org.http4s._ import org.http4s.dsl.Http4sDsl import org.http4s.implicits._ @@ -30,25 +32,27 @@ * Please note that due to the routing logic of http4s catch-all pages (`-> Root`) should be put last in the * list of routes! * + * @param linkConfig + * The configuration needed to build correct links which are working from the outside. * @tparam F * A higher kinded type providing needed functionality, which is usually an IO monad like Async or Sync. */ -final class LandingPageRoutes[F[_]: Async] extends Http4sDsl[F] { - // The URL path that shall be used in the `action` field of the form. - private val signupPath = uri"/signup" +final class LandingPageRoutes[F[_]: Async](linkConfig: ExternalLinkConfig) extends Http4sDsl[F] { + // The URL that shall be used in the `action` field of the form. + private val signupUri = linkConfig.createFullUri(uri"/signup") private val mainSiteForLoggedInUsers: AuthedRoutes[Account, F] = AuthedRoutes.of { case ar @ GET -> Root as user => for { csrf <- Sync[F].delay(ar.req.getCsrfToken) - resp <- Ok(views.html.index()()(signupPath, csrf, "Welcome to the Smederee!".some, user.some)) + resp <- Ok(views.html.index()()(signupUri, csrf, "Welcome to the Smederee!".some, user.some)) } yield resp } private val mainSite: HttpRoutes[F] = HttpRoutes.of { case req @ GET -> Root => for { csrf <- Sync[F].delay(req.getCsrfToken) - resp <- Ok(views.html.index()()(signupPath, csrf, "Welcome to the Smederee!".some)) + resp <- Ok(views.html.index()()(signupUri, csrf, "Welcome to the Smederee!".some)) } yield resp } diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/SignupRoutes.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/SignupRoutes.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/SignupRoutes.scala 2025-02-02 12:11:36.327101925 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/SignupRoutes.scala 2025-02-02 12:11:36.331101932 +0000 @@ -22,6 +22,7 @@ import cats.data._ import cats.effect._ import cats.syntax.all._ +import de.smederee.html.LinkTools._ import de.smederee.hub.RequestHelpers.instances.given_RequestHelpers_Request import de.smederee.hub.config._ import de.smederee.hub.forms.types.FormErrors @@ -38,18 +39,25 @@ /** The routes for handling the user signup process. * - * @param config + * @param linkConfig + * The configuration needed to build correct links which are working from the outside. + * @param signupConfig * The configuration for the signup procedure. * @param repo * The database repository providing needed functionality for checking and creating accounts. * @tparam F * A higher kinded type providing needed functionality, which is usually an IO monad like Async or Sync. */ -final class SignupRoutes[F[_]: Async](config: SignupConfiguration, repo: SignupRepository[F]) - extends Http4sDsl[F] { +final class SignupRoutes[F[_]: Async]( + linkConfig: ExternalLinkConfig, + signupConfig: SignupConfiguration, + repo: SignupRepository[F] +) extends Http4sDsl[F] { private val log = LoggerFactory.getLogger(getClass) + // The base URI for our site which that be passed into some templates which create links themselfes. + private val baseUri = linkConfig.createFullUri(Uri()) // The URL path that shall be used in the `action` field of the form. - private val signupPath = uri"/signup" + private val signupUri = linkConfig.createFullUri(uri"/signup") // Parse the signup form and take appropriate actions like returning errors or creating an account. private val parseSignUpForm = HttpRoutes.of[F] { case request @ POST -> Root / "signup" => @@ -89,7 +97,7 @@ case Validated.Invalid(es) => BadRequest.apply( views.html - .signup()(signupPath, csrf, "Smederee - Sign up for an account".some)( + .signup()(signupUri, csrf, "Smederee - Sign up for an account".some)( formData, FormErrors.fromNec(es) ) @@ -99,7 +107,7 @@ case Validated.Invalid(es) => BadRequest.apply( views.html - .signup()(signupPath, csrf, "Smederee - Sign up for an account".some)( + .signup()(signupUri, csrf, "Smederee - Sign up for an account".some)( formData, FormErrors.fromNec(es) ) @@ -118,7 +126,7 @@ hash <- Sync[F].delay(signupForm.password.encode) _ <- Sync[F].delay(log.info(s"Going to create account for ${account.name}.")) _ <- repo.createAccount(account, PasswordHash(hash)) - redirect <- SeeOther.apply(Location(signupPath.addPath("welcome"))) + redirect <- SeeOther.apply(Location(signupUri.addPath("welcome"))) } yield redirect } } @@ -135,7 +143,7 @@ private val showSignUpForm = HttpRoutes.of[F] { case request @ GET -> Root / "signup" => for { csrf <- Sync[F].delay(request.getCsrfToken) - resp <- Ok(views.html.signup()(signupPath, csrf, "Smederee - Sign up for an account".some)()) + resp <- Ok(views.html.signup()(signupUri, csrf, "Smederee - Sign up for an account".some)()) } yield resp } @@ -148,7 +156,7 @@ case req @ GET -> Root / "signup" / "welcome" => for { csrf <- Sync[F].delay(req.getCsrfToken) - resp <- Ok(views.html.welcome()(csrf, "Thank you and welcome!".some)) + resp <- Ok(views.html.welcome(baseUri)(csrf, "Thank you and welcome!".some)) } yield resp } diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/VcsRepositoryRoutes.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/VcsRepositoryRoutes.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/VcsRepositoryRoutes.scala 2025-02-02 12:11:36.327101925 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/VcsRepositoryRoutes.scala 2025-02-02 12:11:36.331101932 +0000 @@ -28,6 +28,7 @@ import de.smederee.hub.RequestHelpers.instances.given import de.smederee.hub.config._ import de.smederee.hub.forms.types.FormErrors +import de.smederee.html.LinkTools._ import org.commonmark.parser.Parser import org.commonmark.renderer.html.HtmlRenderer import org.http4s._ @@ -63,6 +64,9 @@ private val createRepoPath = uri"/repo/create" private val MaximumFileSize = 131072L // TODO Move to configuration directive. + // The base URI for our site which that be passed into some templates which create links themselfes. + private val baseUri = linkConfig.createFullUri(Uri()) + /** Logic for rendering a list of all repositories visible to the given user account. * * @param csrf @@ -78,12 +82,55 @@ .listAllRepositories(user)(VcsMetadataRepositoriesOrdering.NameAscending) .compile .toList - actionBaseUri <- Sync[F].delay(uri"projects") + actionBaseUri <- Sync[F].delay(linkConfig.createFullUri(uri"projects")) resp <- Ok.apply( views.html.showAllRepositories()(actionBaseUri, csrf, s"Smederee - Projects".some, user)(repos) ) } yield resp + /** Logic for rendering a list of repositories of the given owner for a specific user account. This function + * takes visibility into account. + * + * @param csrf + * An optional CSRF-Token that shall be used. + * @param repositoriesOwnerName + * The name of a user whos repositories shall be listed. + * @param user + * An optional user account for whom the list of repositories shall be rendered. + * @return + * An HTTP response containing the rendered HTML or an error. + */ + private def doShowRepositories( + csrf: Option[CsrfToken] + )(repositoriesOwnerName: Username)(user: Option[Account]): F[Response[F]] = + for { + owner <- vcsMetadataRepo.findVcsRepositoryOwner(repositoriesOwnerName) + repos <- owner.traverse(owner => vcsMetadataRepo.listRepositories(user)(owner).compile.toList) + actionBaseUri <- Sync[F].delay( + linkConfig.createFullUri(Uri(path = Uri.Path.unsafeFromString(s"~$repositoriesOwnerName"))) + ) + resp <- owner match { + case None => // TODO Better error message... + NotFound.apply( + views.html.showRepositories()( + actionBaseUri, + csrf, + s"Smederee/~$repositoriesOwnerName".some, + user + )(repos.getOrElse(List.empty), repositoriesOwnerName) + ) + case Some(_) => + Ok.apply( + views.html.showRepositories()( + actionBaseUri, + csrf, + s"Smederee/~$repositoriesOwnerName".some, + user + )(repos.getOrElse(List.empty), repositoriesOwnerName) + ) + } + } yield resp + /** Logic for rendering the content of a repository directory or file visible to the given user account. * * @param csrf @@ -113,17 +160,11 @@ loadedRepo.filter(r => r.isPrivate === false || r.owner === user.toVcsRepositoryOwner) } actionBaseUri <- Sync[F].delay( - Uri( - scheme = linkConfig.scheme.some, - authority = Uri - .Authority( - userInfo = None, - host = Uri.Host.fromIp4sHost(linkConfig.host), - port = linkConfig.port.map(_.value) + linkConfig.createFullUri( + Uri(path = + Uri.Path( + Vector(Uri.Path.Segment(s"~$repositoryOwnerName"), Uri.Path.Segment(repositoryName.toString)) ) - .some, - path = Uri.Path.Root |+| Uri.Path( - Vector(Uri.Path.Segment(s"~$repositoryOwnerName"), Uri.Path.Segment(repositoryName.toString)) ) ) ) @@ -158,13 +199,13 @@ case None => NotFound("Repository not found!") case Some(repo) => Ok( - views.html.showRepositoryOverview()( + views.html.showRepositoryOverview(baseUri)( actionBaseUri, csrf, s"Smederee/~$repositoryOwnerName/$repositoryName".some, user )( - repo.some, + repo, vcsRepositoryHistory = log.stdout.toList.mkString("\n").some, vcsRepositoryReadme = readme, vcsRepositoryReadmeFilename = readmeName @@ -248,15 +289,21 @@ Option(content.mkString("\n")) } repositoryBaseUri <- Sync[F].delay( - Uri(path = - Uri.Path.Root |+| Uri.Path( - Vector(Uri.Path.Segment(s"~$repositoryOwnerName"), Uri.Path.Segment(repositoryName.toString)) + linkConfig.createFullUri( + Uri(path = + Uri.Path( + Vector(Uri.Path.Segment(s"~$repositoryOwnerName"), Uri.Path.Segment(repositoryName.toString)) + ) ) ) ) - actionBaseUri <- Sync[F].delay(Uri(path = repositoryBaseUri.path.addSegment("files") |+| filePath)) + actionBaseUri <- Sync[F].delay( + linkConfig.createFullUri(Uri(path = repositoryBaseUri.path.addSegment("files") |+| filePath)) + ) goBackUri <- Sync[F].delay( - Uri(path = Uri.Path.Root |+| Uri.Path(actionBaseUri.path.segments.reverse.drop(1).reverse)) + linkConfig.createFullUri( + Uri(path = Uri.Path(actionBaseUri.path.segments.reverse.drop(1).reverse)) + ) ) resp <- repo match { @@ -269,7 +316,7 @@ NotFound("File not found!") else Ok( - views.html.showRepositoryFiles()( + views.html.showRepositoryFiles(baseUri)( actionBaseUri, csrf, Option(goBackUri), @@ -332,29 +379,32 @@ next = if (to < numberOfChanges) Option(to + 1) else None history <- darcs.log(directory.toNIO)(repositoryName.toString)(Chain(s"--index=$from-$to", "--summary")) actionBaseUri <- Sync[F].delay( - Uri(path = - Uri.Path.Root |+| Uri.Path( - Vector( - Uri.Path.Segment(s"~$repositoryOwnerName"), - Uri.Path.Segment(repositoryName.toString) + linkConfig.createFullUri( + Uri(path = + Uri.Path( + Vector(Uri.Path.Segment(s"~$repositoryOwnerName"), Uri.Path.Segment(repositoryName.toString)) ) ) ) ) goBackUri <- Sync[F].delay( - Uri(path = - Uri.Path.Root |+| Uri.Path( - Vector( - Uri.Path.Segment(s"~$repositoryOwnerName"), - Uri.Path.Segment(repositoryName.toString) + linkConfig.createFullUri( + Uri(path = + Uri.Path( + Vector( + Uri.Path.Segment(s"~$repositoryOwnerName"), + Uri.Path.Segment(repositoryName.toString) + ) ) ) ) ) repositoryBaseUri <- Sync[F].delay( - Uri(path = - Uri.Path.Root |+| Uri.Path( - Vector(Uri.Path.Segment(s"~$repositoryOwnerName"), Uri.Path.Segment(repositoryName.toString)) + linkConfig.createFullUri( + Uri(path = + Uri.Path( + Vector(Uri.Path.Segment(s"~$repositoryOwnerName"), Uri.Path.Segment(repositoryName.toString)) + ) ) ) ) @@ -362,7 +412,7 @@ case None => NotFound() case Some(repo) => Ok.apply( - views.html.showRepositoryHistory()( + views.html.showRepositoryHistory(baseUri)( actionBaseUri, csrf, Option(goBackUri), @@ -588,36 +638,16 @@ private val showRepositories: AuthedRoutes[Account, F] = AuthedRoutes.of { case ar @ GET -> Root / UsernamePathParameter(repositoriesOwnerName) as user => for { - csrf <- Sync[F].delay(ar.req.getCsrfToken) - owner <- vcsMetadataRepo.findVcsRepositoryOwner(repositoriesOwnerName) - repos <- owner.traverse(owner => vcsMetadataRepo.listRepositories(user.some)(owner).compile.toList) - actionBaseUri <- Sync[F].delay( - Uri(path = - Uri.Path.Root |+| Uri.Path( - Vector(Uri.Path.Segment(s"~$repositoriesOwnerName")) - ) - ) - ) - resp <- owner match { - case None => // TODO Better error message... - NotFound.apply( - views.html.showRepositories()( - actionBaseUri, - csrf, - s"Smederee/~$repositoriesOwnerName".some, - user - )(repos.getOrElse(List.empty), repositoriesOwnerName) - ) - case Some(_) => - Ok.apply( - views.html.showRepositories()( - actionBaseUri, - csrf, - s"Smederee/~$repositoriesOwnerName".some, - user - )(repos.getOrElse(List.empty), repositoriesOwnerName) - ) - } + csrf <- Sync[F].delay(ar.req.getCsrfToken) + resp <- doShowRepositories(csrf)(repositoriesOwnerName)(user.some) + } yield resp + } + + private val showRepositoriesForGuests: HttpRoutes[F] = HttpRoutes.of { + case req @ GET -> Root / UsernamePathParameter(repositoriesOwnerName) => + for { + csrf <- Sync[F].delay(req.getCsrfToken) + resp <- doShowRepositories(csrf)(repositoriesOwnerName)(None) } yield resp } @@ -685,7 +715,7 @@ showAllRepositories <+> showRepositories <+> parseCreateRepositoryForm <+> showCreateRepositoryForm <+> showRepositoryOverview <+> showRepositoryHistory <+> showRepositoryFiles val routes = - cloneRepository <+> showAllRepositoriesForGuests <+> showRepositoryOverviewForGuests <+> showRepositoryHistoryForGuests <+> showRepositoryFilesForGuests + cloneRepository <+> showAllRepositoriesForGuests <+> showRepositoriesForGuests <+> showRepositoryOverviewForGuests <+> showRepositoryHistoryForGuests <+> showRepositoryFilesForGuests } diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/createFullPath.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/createFullPath.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/createFullPath.scala.html 2025-02-02 12:11:36.327101925 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/createFullPath.scala.html 1970-01-01 00:00:00.000000000 +0000 @@ -1,2 +0,0 @@ -@(pathPrefix: Option[Uri] = None)(path: Uri) -@{path.copy(path = pathPrefix.map(_.path).getOrElse(Uri.Path.empty) |+| path.path)} diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/createRepository.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/createRepository.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/createRepository.scala.html 2025-02-02 12:11:36.327101925 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/createRepository.scala.html 2025-02-02 12:11:36.331101932 +0000 @@ -1,55 +1,57 @@ @import NewVcsRepositoryForm._ -@(lang: LanguageCode = LanguageCode("en"), pathPrefix: Option[Uri] = None)(action: Uri, csrf: Option[CsrfToken] = None, title: Option[String] = None, user: Account)(formData: Map[String, String] = Map.empty, formErrors: FormErrors = FormErrors.empty) -@main(lang, pathPrefix)()(csrf, title, user.some) { +@(baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en"))(action: Uri, csrf: Option[CsrfToken] = None, title: Option[String] = None, user: Account)(formData: Map[String, String] = Map.empty, formErrors: FormErrors = FormErrors.empty) +@main(baseUri, lang)()(csrf, title, user.some) { @defining(lang.toLocale) { implicit locale => <div class="content"> <div class="pure-g"> - <div class="l-box pure-u-1-1 pure-u-md-1-1"> - <div class="form-errors"> - @formErrors.get(fieldGlobal).map { es => - @for(error <- es) { - <p class="alert alert-error"> - <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> - <span class="sr-only">Fehler:</span> - @error - </p> + <div class="pure-u-1-1 pure-u-md-1-1"> + <div class="l-box"> + <div class="form-errors"> + @formErrors.get(fieldGlobal).map { es => + @for(error <- es) { + <p class="alert alert-error"> + <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> + <span class="sr-only">Fehler:</span> + @error + </p> + } } - } - </div> - <div class="create-repo-form"> - <form action="@createFullPath(pathPrefix)(action)" method="POST" accept-charset="UTF-8" class="pure-form pure-form-aligned"> - <fieldset id="repository-data"> - <div class="pure-control-group"> - <label for="@{fieldName}">@Messages("form.create-repo.name")</label> - <input class="pure-input-1-2" id="@{fieldName}" name="@{fieldName}" placeholder="@Messages("form.create-repo.name.placeholder")" maxlength="64" required="" type="text" value="@{formData.get(fieldName)}" autofocus> - <small class="pure-form-message" id="@{fieldName}.help">@Messages("form.create-repo.name.help")</small> - @renderFormErrors(fieldName, formErrors) - </div> - <div class="pure-control-group"> - <label for="@{fieldIsPrivate}">@Messages("form.create-repo.is-private")</label> - <input id="@{fieldIsPrivate}" name="@{fieldIsPrivate}" type="checkbox" value="true" @if(formData.get(fieldIsPrivate).map(_ === "true").getOrElse(false)){ checked="" } else { }> - <span class="pure-form-message-inline" id="@{fieldIsPrivate}.help">@Messages("form.create-repo.is-private.help")</span> - @renderFormErrors(fieldIsPrivate, formErrors) - </div> - <div class="pure-control-group"> - <label for="@{fieldDescription}">@Messages("form.create-repo.description")</label> - <textarea class="pure-input-1-2" id="@{fieldDescription}" name="@{fieldDescription}" placeholder="@Messages("form.create-repo.description.placeholder")" maxlength="254" rows="3">@{formData.get(fieldDescription)}</textarea> - <span class="pure-form-message" id="@{fieldDescription}.help">@Messages("form.create-repo.description.help")</span> - @renderFormErrors(fieldDescription, formErrors) - </div> - <div class="pure-control-group"> - <label for="@{fieldWebsite}">@Messages("form.create-repo.website")</label> - <input id="@{fieldWebsite}" name="@{fieldWebsite}" maxlength="128" placeholder="https://example.com" type="text" value="@{formData.get(fieldWebsite)}"> - <span class="pure-form-message" id="@{fieldWebsite}.help">@Messages("form.create-repo.website.help")</span> - @renderFormErrors(fieldWebsite, formErrors) - </div> - @csrfToken(csrf) - <div class="pure-controls"> - <button type="submit" class="pure-button">@Messages("form.create-repo.button.submit")</button> - </div> - </fieldset> - </form> + </div> + <div class="create-repo-form"> + <form action="@action" method="POST" accept-charset="UTF-8" class="pure-form pure-form-aligned"> + <fieldset id="repository-data"> + <div class="pure-control-group"> + <label for="@{fieldName}">@Messages("form.create-repo.name")</label> + <input class="pure-input-1-2" id="@{fieldName}" name="@{fieldName}" placeholder="@Messages("form.create-repo.name.placeholder")" maxlength="64" required="" type="text" value="@{formData.get(fieldName)}" autofocus> + <small class="pure-form-message" id="@{fieldName}.help">@Messages("form.create-repo.name.help")</small> + @renderFormErrors(fieldName, formErrors) + </div> + <div class="pure-control-group"> + <label for="@{fieldIsPrivate}">@Messages("form.create-repo.is-private")</label> + <input id="@{fieldIsPrivate}" name="@{fieldIsPrivate}" type="checkbox" value="true" @if(formData.get(fieldIsPrivate).map(_ === "true").getOrElse(false)){ checked="" } else { }> + <span class="pure-form-message-inline" id="@{fieldIsPrivate}.help">@Messages("form.create-repo.is-private.help")</span> + @renderFormErrors(fieldIsPrivate, formErrors) + </div> + <div class="pure-control-group"> + <label for="@{fieldDescription}">@Messages("form.create-repo.description")</label> + <textarea class="pure-input-1-2" id="@{fieldDescription}" name="@{fieldDescription}" placeholder="@Messages("form.create-repo.description.placeholder")" maxlength="254" rows="3">@{formData.get(fieldDescription)}</textarea> + <span class="pure-form-message" id="@{fieldDescription}.help">@Messages("form.create-repo.description.help")</span> + @renderFormErrors(fieldDescription, formErrors) + </div> + <div class="pure-control-group"> + <label for="@{fieldWebsite}">@Messages("form.create-repo.website")</label> + <input id="@{fieldWebsite}" name="@{fieldWebsite}" maxlength="128" placeholder="https://example.com" type="text" value="@{formData.get(fieldWebsite)}"> + <span class="pure-form-message" id="@{fieldWebsite}.help">@Messages("form.create-repo.website.help")</span> + @renderFormErrors(fieldWebsite, formErrors) + </div> + @csrfToken(csrf) + <div class="pure-controls"> + <button type="submit" class="pure-button">@Messages("form.create-repo.button.submit")</button> + </div> + </fieldset> + </form> + </div> </div> </div> </div> diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/index.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/index.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/index.scala.html 2025-02-02 12:11:36.327101925 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/index.scala.html 2025-02-02 12:11:36.331101932 +0000 @@ -1,6 +1,6 @@ @import SignupForm._ -@(lang: LanguageCode = LanguageCode("en"), pathPrefix: Option[Uri] = None, tags: MetaTags = MetaTags.empty)(customFooters: Html = Html(""), customHeaders: Html = Html(""))(actionSignup: Uri, csrf: Option[CsrfToken] = None, title: Option[String] = None, user: Option[Account] = None) +@(baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en"), tags: MetaTags = MetaTags.empty)(customFooters: Html = Html(""), customHeaders: Html = Html(""))(actionSignup: Uri, csrf: Option[CsrfToken] = None, title: Option[String] = None, user: Option[Account] = None) @defining(lang.toLocale) { implicit locale => <!DOCTYPE html> <html lang="@lang"> @@ -9,16 +9,16 @@ <meta name="viewport" content="width=device-width, initial-scale=1"> @meta(tags) <title>@title</title> - <link rel="stylesheet" href="@pathPrefix/assets/purecss/2.1.0/pure-min.css" /> - <link rel="stylesheet" href="@pathPrefix/assets/purecss/2.1.0/grids-responsive-min.css" /> - <link rel="stylesheet" href="@pathPrefix/assets/fontawesome/css/all.min.css" /> - <link rel="stylesheet" href="@pathPrefix/assets/css/main.css" /> - <link rel="stylesheet" href="@pathPrefix/assets/css/landingpage.css" /> + <link rel="stylesheet" href="@{baseUri.addPath("assets/purecss/2.1.0/pure-min.css")}" /> + <link rel="stylesheet" href="@{baseUri.addPath("assets/purecss/2.1.0/grids-responsive-min.css")}" /> + <link rel="stylesheet" href="@{baseUri.addPath("assets/fontawesome/css/all.min.css")}" /> + <link rel="stylesheet" href="@{baseUri.addPath("assets/css/main.css")}" /> + <link rel="stylesheet" href="@{baseUri.addPath("assets/css/landingpage.css")}" /> @customHeaders </head> <body> <navbar class="header navbar" id="navbar-top"> - @navbar(lang, pathPrefix)(csrf, Option("pure-menu-fixed"), user) + @navbar(baseUri, lang)(csrf, Option("pure-menu-fixed"), user) </navbar> <div class="splash-container"> @@ -56,7 +56,7 @@ <div class="ribbon l-box-lrg pure-g"> <div class="l-box-lrg is-center pure-u-1 pure-u-md-1-2 pure-u-lg-2-5"> - <img width="300" alt="A smithy in which a smith is working on an anvil." class="pure-img-responsive" src="@pathPrefix/assets/img/malcolm-lightbody-gPRvTP0sZ2M-unsplash.jpg"> + <img width="300" alt="A smithy in which a smith is working on an anvil." class="pure-img-responsive" src="@{baseUri.addPath("assets/img/malcolm-lightbody-gPRvTP0sZ2M-unsplash.jpg")}"> </div> <div class="pure-u-1 pure-u-md-1-2 pure-u-lg-3-5"> <h2 class="content-head content-head-ribbon">@Messages("landingpage.index.ribbon.title")</h2> @@ -70,7 +70,7 @@ <div class="pure-g"> <div class="l-box-lrg pure-u-1 pure-u-md-2-5"> <div class="signup-form"> - <form action="@createFullPath(pathPrefix)(actionSignup)" class="pure-form pure-form-stacked" method="POST" accept-charset="UTF-8" class="pure-form pure-form-aligned" autocomplete="on"> + <form action="@actionSignup" class="pure-form pure-form-stacked" method="POST" accept-charset="UTF-8" class="pure-form pure-form-aligned" autocomplete="on"> <fieldset id="signup-data" @{user.map(_ => "disabled")}> <div class="pure-control-group"> <label for="@{fieldName}">@Messages("form.signup.username")</label> @@ -116,9 +116,9 @@ <div class="pure-menu pure-menu-horizontal"> <a class="pure-menu-heading pure-menu-link" href="https://www.wegtam.com" target="_blank">@Messages("global.copyright")</a> <ul class="pure-menu-list"> - <li class="pure-menu-item"><a class="pure-menu-link" href="@pathPrefix/privacy">@Messages("global.privacy")</a> - <li class="pure-menu-item"><a class="pure-menu-link" href="@pathPrefix/terms">@Messages("global.terms.of.use")</a> - <li class="pure-menu-item"><a class="pure-menu-link" href="@pathPrefix/imprint">@Messages("global.imprint")</a> + <li class="pure-menu-item"><a class="pure-menu-link" href="@{baseUri.addPath("privacy")}">@Messages("global.privacy")</a> + <li class="pure-menu-item"><a class="pure-menu-link" href="@{baseUri.addPath("terms")}">@Messages("global.terms.of.use")</a> + <li class="pure-menu-item"><a class="pure-menu-link" href="@{baseUri.addPath("imprint")}">@Messages("global.imprint")</a> </ul> </div> </footer> diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/login.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/login.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/login.scala.html 2025-02-02 12:11:36.327101925 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/login.scala.html 2025-02-02 12:11:36.331101932 +0000 @@ -1,41 +1,43 @@ @import LoginForm._ -@(lang: LanguageCode = LanguageCode("en"), pathPrefix: Option[Uri] = None)(action: Uri, csrf: Option[CsrfToken] = None, title: Option[String] = None)(formData: Map[String, String] = Map.empty, formErrors: FormErrors = FormErrors.empty) -@main(lang, pathPrefix)()(csrf, title) { +@(baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en"))(action: Uri, csrf: Option[CsrfToken] = None, title: Option[String] = None)(formData: Map[String, String] = Map.empty, formErrors: FormErrors = FormErrors.empty) +@main(baseUri, lang)()(csrf, title) { @defining(lang.toLocale) { implicit locale => <div class="content"> <div class="pure-g"> - <div class="l-box pure-u-1-1 pure-u-md-1-1"> - <div class="form-errors"> - @formErrors.get(fieldGlobal).map { es => - @for(error <- es) { - <p class="alert alert-error"> - <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> - <span class="sr-only">Fehler:</span> - @error - </p> - } - } - </div> - <div class="login-form"> - <form action="@createFullPath(pathPrefix)(action)" method="POST" accept-charset="UTF-8" class="pure-form pure-form-aligned" autocomplete="on"> - <fieldset id="login-data"> - <div class="pure-control-group"> - <label for="@{fieldName}">@Messages("form.login.username")</label> - <input class="pure-input-1-2" id="@{fieldName}" name="@{fieldName}" placeholder="@Messages("form.login.username.placeholder")" maxlength="31" required="" type="text" value="@{formData.get(fieldName)}" autocomplete="username" autofocus> - @renderFormErrors(fieldName, formErrors) - </div> - <div class="pure-control-group"> - <label for="@{fieldPassword}">@Messages("form.login.password")</label> - <input class="pure-input-1-2" id="@{fieldPassword}" name="@{fieldPassword}" placeholder="@Messages("form.login.password.placeholder")" maxlength="128" required="" type="password" value="" autocomplete="password"> - @renderFormErrors(fieldPassword, formErrors) - </div> - @csrfToken(csrf) - <div class="pure-controls"> - <button type="submit" class="pure-button">@Messages("form.login.button.submit")</button> - </div> - </fieldset> - </form> + <div class="pure-u-1-1 pure-u-md-1-1"> + <div class="l-box"> + <div class="form-errors"> + @formErrors.get(fieldGlobal).map { es => + @for(error <- es) { + <p class="alert alert-error"> + <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> + <span class="sr-only">Fehler:</span> + @error + </p> + } + } + </div> + <div class="login-form"> + <form action="@action" method="POST" accept-charset="UTF-8" class="pure-form pure-form-aligned" autocomplete="on"> + <fieldset id="login-data"> + <div class="pure-control-group"> + <label for="@{fieldName}">@Messages("form.login.username")</label> + <input class="pure-input-1-2" id="@{fieldName}" name="@{fieldName}" placeholder="@Messages("form.login.username.placeholder")" maxlength="31" required="" type="text" value="@{formData.get(fieldName)}" autocomplete="username" autofocus> + @renderFormErrors(fieldName, formErrors) + </div> + <div class="pure-control-group"> + <label for="@{fieldPassword}">@Messages("form.login.password")</label> + <input class="pure-input-1-2" id="@{fieldPassword}" name="@{fieldPassword}" placeholder="@Messages("form.login.password.placeholder")" maxlength="128" required="" type="password" value="" autocomplete="password"> + @renderFormErrors(fieldPassword, formErrors) + </div> + @csrfToken(csrf) + <div class="pure-controls"> + <button type="submit" class="pure-button">@Messages("form.login.button.submit")</button> + </div> + </fieldset> + </form> + </div> </div> </div> </div> diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/main.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/main.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/main.scala.html 2025-02-02 12:11:36.327101925 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/main.scala.html 2025-02-02 12:11:36.331101932 +0000 @@ -1,4 +1,4 @@ -@(lang: LanguageCode = LanguageCode("en"), pathPrefix: Option[Uri] = None, tags: MetaTags = MetaTags.empty)(customFooters: Html = Html(""), customHeaders: Html = Html(""))(csrf: Option[CsrfToken] = None, title: Option[String] = None, user: Option[Account] = None)(content: Html) +@(baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en"), tags: MetaTags = MetaTags.empty)(customFooters: Html = Html(""), customHeaders: Html = Html(""))(csrf: Option[CsrfToken] = None, title: Option[String] = None, user: Option[Account] = None)(content: Html) @defining(lang.toLocale) { implicit locale => <!DOCTYPE html> <html lang="@lang"> @@ -7,14 +7,14 @@ <meta name="viewport" content="width=device-width, initial-scale=1"> @meta(tags) <title>@title</title> - <link rel="stylesheet" href="@pathPrefix/assets/purecss/2.1.0/pure-min.css" /> - <link rel="stylesheet" href="@pathPrefix/assets/purecss/2.1.0/grids-responsive-min.css" /> - <link rel="stylesheet" href="@pathPrefix/assets/fontawesome/css/all.min.css" /> - <link rel="stylesheet" href="@pathPrefix/assets/css/main.css" /> + <link rel="stylesheet" href="@{baseUri.addPath("assets/purecss/2.1.0/pure-min.css")}" /> + <link rel="stylesheet" href="@{baseUri.addPath("assets/purecss/2.1.0/grids-responsive-min.css")}" /> + <link rel="stylesheet" href="@{baseUri.addPath("assets/fontawesome/css/all.min.css")}" /> + <link rel="stylesheet" href="@{baseUri.addPath("assets/css/main.css")}" /> @customHeaders </head> <body> - <navbar class="header navbar" id="navbar-top">@navbar(lang, pathPrefix)(csrf, None, user)</navbar> + <navbar class="header navbar" id="navbar-top">@navbar(baseUri, lang)(csrf, None, user)</navbar> <main class="content-wrapper"> @content <footer class="footer">@customFooters</footer> diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/navbar.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/navbar.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/navbar.scala.html 2025-02-02 12:11:36.331101932 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/navbar.scala.html 2025-02-02 12:11:36.331101932 +0000 @@ -1,24 +1,24 @@ -@(lang: LanguageCode, pathPrefix: Option[Uri])(csrf: Option[CsrfToken] = None, extraCss: Option[String] = None, user: Option[Account] = None) +@(baseUri: Uri, lang: LanguageCode)(csrf: Option[CsrfToken] = None, extraCss: Option[String] = None, user: Option[Account] = None) @defining(lang.toLocale) { implicit locale => <nav class="home-menu pure-menu pure-menu-horizontal @extraCss"> - <a class="logo pure-menu-heading" href="@pathPrefix/">@Messages("global.navbar.top.logo")</a> + <a class="logo pure-menu-heading" href="@baseUri">@Messages("global.navbar.top.logo")</a> <ul class="pure-menu-list"> - <li class="pure-menu-item"><a class="pure-menu-link" href="@pathPrefix/projects">Projects</a></li> + <li class="pure-menu-item"><a class="pure-menu-link" href="@{baseUri.addPath("projects")}">Projects</a></li> @if(user.nonEmpty) { @for(account <- user) { - <li class="pure-menu-item"><a class="pure-menu-link" href="@pathPrefix/~@account.name">Your repositories</a></li> + <li class="pure-menu-item"><a class="pure-menu-link" href="@{baseUri.addPath(s"~${account.name}")}">Your projects</a></li> } - <li class="pure-menu-item"><a class="pure-menu-link" href="@pathPrefix/repo/create">+ @Messages("global.navbar.top.repository.new")</a></li> + <li class="pure-menu-item"><a class="pure-menu-link" href="@{baseUri.addPath("repo/create")}">+ @Messages("global.navbar.top.repository.new")</a></li> <li class="pure-menu-item"> - <form action="@pathPrefix/logout" method="POST" accept-charset="UTF-8" class="pure-form"> + <form action="@{baseUri.addPath("logout")}" method="POST" accept-charset="UTF-8" class="pure-form"> @csrfToken(csrf) <button class="pure-button pure-button-primary" type="submit">@Messages("global.logout")</button> </form> </li> } else { - <li class="pure-menu-item"><a class="pure-menu-link" href="@pathPrefix/login">@Messages("global.login")</a></li> - <li class="pure-menu-item"><a class="pure-menu-link" href="@pathPrefix/signup">@Messages("global.signup")</a></li> + <li class="pure-menu-item"><a class="pure-menu-link" href="@{baseUri.addPath("login")}">@Messages("global.login")</a></li> + <li class="pure-menu-item"><a class="pure-menu-link" href="@{baseUri.addPath("signup")}">@Messages("global.signup")</a></li> } </ul> </nav> diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showAllRepositories.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showAllRepositories.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showAllRepositories.scala.html 2025-02-02 12:11:36.331101932 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showAllRepositories.scala.html 2025-02-02 12:11:36.331101932 +0000 @@ -1,9 +1,10 @@ -@(lang: LanguageCode = LanguageCode("en"), pathPrefix: Option[Uri] = None)(actionBaseUri: Uri, csrf: Option[CsrfToken] = None, title: Option[String] = None, user: Option[Account])(listing: List[VcsRepository]) -@main(lang, pathPrefix)()(csrf, title, user) { +@(baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en"))(actionBaseUri: Uri, csrf: Option[CsrfToken] = None, title: Option[String] = None, user: Option[Account])(listing: List[VcsRepository]) +@main(baseUri, lang)()(csrf, title, user) { @defining(lang.toLocale) { implicit locale => <div class="content"> <div class="pure-g"> - <div class="l-box pure-u-1-1 pure-u-md-1-1"> + <div class="pure-u-1-1 pure-u-md-1-1"> + <div class="l-box"> @if(listing.nonEmpty) { <table class="pure-table pure-table-horizontal"> <thead> @@ -18,9 +19,9 @@ <tr> <td><i class="fa-solid fa-folder-tree"></i> @if(repo.isPrivate){<i class="fa-solid fa-lock"></i>}else{ }</td> <td> - <a href="@createFullPath(pathPrefix)(Uri(path = Uri.Path.Root).addSegment(s"~${repo.owner.name.toString}"))">~@{repo.owner.name}</a> + <a href="@{baseUri.addSegment(s"~${repo.owner.name.toString}")}">~@{repo.owner.name}</a> / - <a href="@createFullPath(pathPrefix)(Uri(path = Uri.Path.Root).addSegment(s"~${repo.owner.name.toString}").addSegment(repo.name.toString))">@{repo.name}</a> + <a href="@{baseUri.addSegment(s"~${repo.owner.name.toString}").addSegment(repo.name.toString)}">@{repo.name}</a> </td> <td>@repo.description</td> </tr> @@ -30,6 +31,7 @@ } else { <div class="alert alert-warning">No repositories found.</div> } + </div> </div> </div> </div> diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositories.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositories.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositories.scala.html 2025-02-02 12:11:36.331101932 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositories.scala.html 2025-02-02 12:11:36.331101932 +0000 @@ -1,9 +1,10 @@ -@(lang: LanguageCode = LanguageCode("en"), pathPrefix: Option[Uri] = None)(actionBaseUri: Uri, csrf: Option[CsrfToken] = None, title: Option[String] = None, user: Account)(listing: List[VcsRepository], repositoriesOwner: Username) -@main(lang, pathPrefix)()(csrf, title, user.some) { +@(baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en"))(actionBaseUri: Uri, csrf: Option[CsrfToken] = None, title: Option[String] = None, user: Option[Account])(listing: List[VcsRepository], repositoriesOwner: Username) +@main(baseUri, lang)()(csrf, title, user) { @defining(lang.toLocale) { implicit locale => <div class="content"> <div class="pure-g"> - <div class="l-box pure-u-1-1 pure-u-md-1-1"> + <div class="pure-u-1-1 pure-u-md-1-1"> + <div class="l-box"> @if(listing.nonEmpty) { <table class="pure-table pure-table-horizontal"> <thead> @@ -17,15 +18,16 @@ @for(repo <- listing) { <tr> <td><i class="fa-solid fa-folder-tree"></i> @if(repo.isPrivate){<i class="fa-solid fa-lock"></i>}else{ }</td> - <td><a href="@createFullPath(pathPrefix)(actionBaseUri.addSegment(repo.name.toString))">@{repo.name}</a></td> + <td><a href="@{actionBaseUri.addSegment(repo.name.toString)}">@{repo.name}</a></td> <td>@repo.description</td> </tr> } </tbody> </table> } else { - <div class="alert">No repositories found.</div> + <div class="alert alert-secondary">No repositories found.</div> } + </div> </div> </div> </div> diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryFiles.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryFiles.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryFiles.scala.html 2025-02-02 12:11:36.331101932 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryFiles.scala.html 2025-02-02 12:11:36.331101932 +0000 @@ -1,59 +1,69 @@ -@(lang: LanguageCode = LanguageCode("en"), pathPrefix: Option[Uri] = None)(actionBaseUri: Uri, csrf: Option[CsrfToken] = None, goBackUri: Option[Uri] = None, title: Option[String] = None, user: Option[Account])(fileContent: Option[String], listing: IndexedSeq[(os.RelPath, os.StatInfo)], repositoryBaseUri: Uri, repository: VcsRepository) -@main(lang, pathPrefix)()(csrf, title, user) { +@(baseUri: Uri, lang: LanguageCode = LanguageCode("en"))(actionBaseUri: Uri, csrf: Option[CsrfToken] = None, goBackUri: Option[Uri] = None, title: Option[String] = None, user: Option[Account])(fileContent: Option[String], listing: IndexedSeq[(os.RelPath, os.StatInfo)], repositoryBaseUri: Uri, vcsRepository: VcsRepository) +@main(baseUri, lang)()(csrf, title, user) { @defining(lang.toLocale) { implicit locale => <div class="content"> <div class="pure-g"> - <div class="l-box pure-u-1-1 pure-u-md-1-1"> - <h2>~@{repository.owner.name}/@{repository.name}</h2> - <nav class="pure-menu pure-menu-horizontal"> - <ul class="pure-menu-list"> - <li class="pure-menu-item"><a class="pure-menu-link" href="@createFullPath(pathPrefix)(repositoryBaseUri)"><i class="fa-solid fa-eye"></i> Overview</a></li> - <li class="pure-menu-item pure-menu-active"><a class="pure-menu-link" href="@createFullPath(pathPrefix)(repositoryBaseUri.addSegment("files"))"><i class="fa-solid fa-folder-tree"></i> Files</a></li> - <li class="pure-menu-item"><a class="pure-menu-link" href="@createFullPath(pathPrefix)(repositoryBaseUri.addSegment("history"))"><i class="fa-solid fa-timeline"></i> Changes</a></li> - </ul> - </nav> + <div class="pure-u-1-1 pure-u-md-1-1"> + <div class="l-box-left-right"> + <h2><a href="@{baseUri.addSegment(s"~${vcsRepository.owner.name}")}">~@vcsRepository.owner.name</a>/@vcsRepository.name</h2> + <nav class="pure-menu pure-menu-horizontal"> + <ul class="pure-menu-list"> + <li class="pure-menu-item"><a class="pure-menu-link" href="@repositoryBaseUri"><i class="fa-solid fa-eye"></i> Overview</a></li> + <li class="pure-menu-item pure-menu-active"><a class="pure-menu-link" href="@repositoryBaseUri.addSegment("files")"><i class="fa-solid fa-folder-tree"></i> Files</a></li> + <li class="pure-menu-item"><a class="pure-menu-link" href="@repositoryBaseUri.addSegment("history")"><i class="fa-solid fa-timeline"></i> Changes</a></li> + @for(website <- vcsRepository.website) { + <li class="pure-menu-item"><a class="pure-menu-link" href="@website" target="_blank" title="Click here to open the project website (@website) in a new tab or window."><i class="fa-solid fa-up-right-from-square"></i> Website</a></li> + } + </ul> + </nav> + <div class="repo-summary-description"> + <code>@{actionBaseUri.path.toString.replaceFirst("/files", "")}</code> + </div> + </div> </div> </div> </div> <div class="content"> <div class="pure-g"> - <div class="l-box pure-u-1-1 pure-u-md-1-1"> - @if(fileContent.isEmpty) { - <table class="pure-table pure-table-horizontal"> - <thead> - <tr> - <th></th> - <th>Name</th> - <th>Size</th> - <th>Modified</th> - </tr> - </thead> - <tbody> + <div class="pure-u-1-1 pure-u-md-1-1"> + <div class="l-box"> + @if(fileContent.isEmpty) { + <table class="pure-table pure-table-horizontal"> + <thead> + <tr> + <th></th> + <th>Name</th> + <th>Size</th> + <th>Modified</th> + </tr> + </thead> + <tbody> + @for(link <- goBackUri) { + <tr> + <td><a href="@link"><i class="fa-solid fa-angle-up"></i></a></td> + <td><a href="@link">..</a></td> + <td></td> + </tr> + } + @for(entry <- listing) { + <tr> + <td>@if(entry._2.isDir) { <i class="fa-solid fa-folder"></i> } else { <i class="fa-solid fa-file"></i> }</td> + <td><a href="@actionBaseUri.addSegment(entry._1.last)">@{entry._1}</a></td> + <td>@{entry._2.size}</td> + <td>@{entry._2.mtime}</td> + </tr> + } + </tbody> + </table> + } else { @for(link <- goBackUri) { - <tr> - <td><a href="@createFullPath(pathPrefix)(link)"><i class="fa-solid fa-angle-up"></i></a></td> - <td><a href="@createFullPath(pathPrefix)(link)">..</a></td> - <td></td> - </tr> + <a href="@link"><i class="fa-solid fa-angle-up"></i></a> <a href="@link">..</a> } - @for(entry <- listing) { - <tr> - <td>@if(entry._2.isDir) { <i class="fa-solid fa-folder"></i> } else { <i class="fa-solid fa-file"></i> }</td> - <td><a href="@createFullPath(pathPrefix)(actionBaseUri.addSegment(entry._1.last))">@{entry._1}</a></td> - <td>@{entry._2.size}</td> - <td>@{entry._2.mtime}</td> - </tr> + @for(content <- fileContent) { + <pre class="repository-file-content"><code>@content</code></pre> } - </tbody> - </table> - } else { - @for(link <- goBackUri) { - <a href="@createFullPath(pathPrefix)(link)"><i class="fa-solid fa-angle-up"></i></a> <a href="@createFullPath(pathPrefix)(link)">..</a> } - @for(content <- fileContent) { - <pre class="repository-file-content"><code>@content</code></pre> - } - } + </div> </div> </div> </div> diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryHistory.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryHistory.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryHistory.scala.html 2025-02-02 12:11:36.331101932 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryHistory.scala.html 2025-02-02 12:11:36.331101932 +0000 @@ -1,33 +1,40 @@ -@(lang: LanguageCode = LanguageCode("en"), pathPrefix: Option[Uri] = None)(actionBaseUri: Uri, csrf: Option[CsrfToken] = None, goBackUri: Option[Uri] = None, title: Option[String] = None, user: Option[Account])(history: String, nextEntry: Option[Int], repositoryBaseUri: Uri, repository: VcsRepository) -@main(lang, pathPrefix)()(csrf, title, user) { +@(baseUri: Uri, lang: LanguageCode = LanguageCode("en"))(actionBaseUri: Uri, csrf: Option[CsrfToken] = None, goBackUri: Option[Uri] = None, title: Option[String] = None, user: Option[Account])(history: String, nextEntry: Option[Int], repositoryBaseUri: Uri, vcsRepository: VcsRepository) +@main(baseUri, lang)()(csrf, title, user) { @defining(lang.toLocale) { implicit locale => <div class="content"> <div class="pure-g"> - <div class="l-box pure-u-1-1 pure-u-md-1-1"> - <h2>~@{repository.owner.name}/@{repository.name}</h2> - <nav class="pure-menu pure-menu-horizontal"> - <ul class="pure-menu-list"> - <li class="pure-menu-item"><a class="pure-menu-link" href="@createFullPath(pathPrefix)(actionBaseUri)"><i class="fa-solid fa-eye"></i> Overview</a></li> - <li class="pure-menu-item"><a class="pure-menu-link" href="@createFullPath(pathPrefix)(actionBaseUri.addSegment("files"))"><i class="fa-solid fa-folder-tree"></i> Files</a></li> - <li class="pure-menu-item pure-menu-active"><a class="pure-menu-link" href="@createFullPath(pathPrefix)(actionBaseUri.addSegment("history"))"><i class="fa-solid fa-timeline"></i> Changes</a></li> - </ul> - </nav> + <div class="pure-u-1-1 pure-u-md-1-1"> + <div class="l-box-left-right"> + <h2><a href="@{baseUri.addSegment(s"~${vcsRepository.owner.name}")}">~@vcsRepository.owner.name</a>/@vcsRepository.name</h2> + <nav class="pure-menu pure-menu-horizontal"> + <ul class="pure-menu-list"> + <li class="pure-menu-item"><a class="pure-menu-link" href="@actionBaseUri"><i class="fa-solid fa-eye"></i> Overview</a></li> + <li class="pure-menu-item"><a class="pure-menu-link" href="@actionBaseUri.addSegment("files")"><i class="fa-solid fa-folder-tree"></i> Files</a></li> + <li class="pure-menu-item pure-menu-active"><a class="pure-menu-link" href="@actionBaseUri.addSegment("history")"><i class="fa-solid fa-timeline"></i> Changes</a></li> + @for(website <- vcsRepository.website) { + <li class="pure-menu-item"><a class="pure-menu-link" href="@website" target="_blank" title="Click here to open the project website (@website) in a new tab or window."><i class="fa-solid fa-up-right-from-square"></i> Website</a></li> + } + </ul> + </nav> + </div> </div> </div> </div> <div class="content"> <div class="pure-g"> - <div class="l-box pure-u-1-1 pure-u-md-1-1"> - <pre><code>@history</code></pre> + <div class="pure-u-1-1 pure-u-md-1-1"> + <div class="l-box"><pre><code>@history</code></pre></div> </div> - <div class="l-box pure-u-1-1 pure-u-md-1-1"> - <nav class="pure-menu pure-menu-horizontal"> - <ul class="pure-menu-list"> - @for(next <- nextEntry) { - <li class="pure-menu-item"><a class="pure-menu-link" href="@createFullPath(pathPrefix)(repositoryBaseUri.addSegment("history").withOptionQueryParam("from", nextEntry))">Next <i class="fa-solid fa-angle-right"></i></a></li> - } - </ul> - </nav> + <div class="pure-u-1-1 pure-u-md-1-1"> + <div class="l-box"> + <nav class="pure-menu pure-menu-horizontal"> + <ul class="pure-menu-list"> + @for(next <- nextEntry) { + <li class="pure-menu-item"><a class="pure-menu-link" href="@repositoryBaseUri.addSegment("history").withOptionQueryParam("from", nextEntry)">Next <i class="fa-solid fa-angle-right"></i></a></li> + } + </ul> + </nav> + </div> </div> </div> </div> diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryOverview.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryOverview.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryOverview.scala.html 2025-02-02 12:11:36.331101932 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryOverview.scala.html 2025-02-02 12:11:36.331101932 +0000 @@ -1,22 +1,29 @@ -@(lang: LanguageCode = LanguageCode("en"), pathPrefix: Option[Uri] = None)(actionBaseUri: Uri, csrf: Option[CsrfToken] = None, title: Option[String] = None, user: Option[Account])(vcsRepository: Option[VcsRepository], vcsRepositoryHistory: Option[String], vcsRepositoryReadme: Option[String] = None, vcsRepositoryReadmeFilename: Option[String] = None) -@main(lang, pathPrefix)()(csrf, title, user) { +@(baseUri: Uri, lang: LanguageCode = LanguageCode("en"))(actionBaseUri: Uri, csrf: Option[CsrfToken] = None, title: Option[String] = None, user: Option[Account])(vcsRepository: VcsRepository, vcsRepositoryHistory: Option[String], vcsRepositoryReadme: Option[String] = None, vcsRepositoryReadmeFilename: Option[String] = None) +@main(baseUri, lang)()(csrf, title, user) { @defining(lang.toLocale) { implicit locale => - @for(repo <- vcsRepository) { <div class="content"> <div class="pure-g"> - <div class="l-box pure-u-1-1 pure-u-md-1-1"> - <h2>~@repo.owner.name/@repo.name</h2> - <nav class="pure-menu pure-menu-horizontal"> - <ul class="pure-menu-list"> - <li class="pure-menu-item pure-menu-active"><a class="pure-menu-link" href="@createFullPath(pathPrefix)(actionBaseUri)"><i class="fa-solid fa-eye"></i> Overview</a></li> - <li class="pure-menu-item"><a class="pure-menu-link" href="@createFullPath(pathPrefix)(actionBaseUri.addSegment("files"))"><i class="fa-solid fa-folder-tree"></i> Files</a></li> - <li class="pure-menu-item"><a class="pure-menu-link" href="@createFullPath(pathPrefix)(actionBaseUri.addSegment("history"))"><i class="fa-solid fa-timeline"></i> Changes</a></li> - </ul> - </nav> + <div class="pure-u-1-1 pure-u-md-1-1"> + <div class="l-box-left-right"> + <h2><a href="@{baseUri.addSegment(s"~${vcsRepository.owner.name}")}">~@vcsRepository.owner.name</a>/@vcsRepository.name</h2> + <nav class="pure-menu pure-menu-horizontal"> + <ul class="pure-menu-list"> + <li class="pure-menu-item pure-menu-active"><a class="pure-menu-link" href="@actionBaseUri"><i class="fa-solid fa-eye"></i> Overview</a></li> + <li class="pure-menu-item"><a class="pure-menu-link" href="@actionBaseUri.addSegment("files")"><i class="fa-solid fa-folder-tree"></i> Files</a></li> + <li class="pure-menu-item"><a class="pure-menu-link" href="@actionBaseUri.addSegment("history")"><i class="fa-solid fa-timeline"></i> Changes</a></li> + @for(website <- vcsRepository.website) { + <li class="pure-menu-item"><a class="pure-menu-link" href="@website" target="_blank" title="Click here to open the project website (@website) in a new tab or window."><i class="fa-solid fa-up-right-from-square"></i> Website</a></li> + } + </ul> + </nav> + @for(description <- vcsRepository.description) { + <div class="repo-summary-description"> + <strong>Summary:</strong> @description + </div> + } + </div> </div> </div> - </div> - <div class="content"> <div class="pure-g"> <div class="pure-u-3-5 pure-u-md-3-5"> <div class="l-box"> @@ -32,7 +39,7 @@ <dd> <form class="pure-form"> <fieldset> - <input class="pure-input-1" type="text" value="@{createFullPath(pathPrefix)(actionBaseUri)}.darcs" readonly="readonly"/> + <input class="pure-input-1" type="text" value="@{actionBaseUri}.darcs" readonly="readonly"/> </fieldset> </form> </dd> @@ -58,6 +65,5 @@ </div> </div> </div> - } } } diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/signup.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/signup.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/signup.scala.html 2025-02-02 12:11:36.331101932 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/signup.scala.html 2025-02-02 12:11:36.331101932 +0000 @@ -1,49 +1,51 @@ @import SignupForm._ -@(lang: LanguageCode = LanguageCode("en"), pathPrefix: Option[Uri] = None)(action: Uri, csrf: Option[CsrfToken] = None, title: Option[String] = None)(formData: Map[String, String] = Map.empty, formErrors: FormErrors = FormErrors.empty) -@main(lang, pathPrefix)()(csrf, title) { +@(baseUri: Uri = Uri(path = Uri.Path.Root), lang: LanguageCode = LanguageCode("en"))(action: Uri, csrf: Option[CsrfToken] = None, title: Option[String] = None)(formData: Map[String, String] = Map.empty, formErrors: FormErrors = FormErrors.empty) +@main(baseUri, lang)()(csrf, title) { @defining(lang.toLocale) { implicit locale => <div class="content"> <div class="pure-g"> - <div class="l-box pure-u-1-1 pure-u-md-1-1"> - <div class="form-errors"> - @formErrors.get(fieldGlobal).map { es => - @for(error <- es) { - <p class="alert alert-error"> - <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> - <span class="sr-only">Fehler:</span> - @error - </p> + <div class="pure-u-1-1 pure-u-md-1-1"> + <div class="l-box"> + <div class="form-errors"> + @formErrors.get(fieldGlobal).map { es => + @for(error <- es) { + <p class="alert alert-error"> + <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> + <span class="sr-only">Fehler:</span> + @error + </p> + } } - } - </div> - <div class="signup-form"> - <form action="@createFullPath(pathPrefix)(action)" method="POST" accept-charset="UTF-8" class="pure-form pure-form-aligned" autocomplete="on"> - <fieldset id="signup-data"> - <div class="pure-control-group"> - <label for="@{fieldName}">@Messages("form.signup.username")</label> - <input class="pure-input-1-2" id="@{fieldName}" name="@{fieldName}" placeholder="@Messages("form.signup.username.placeholder")" maxlength="31" required="" type="text" value="@{formData.get(fieldName)}" autocomplete="username" autofocus> - <small class="pure-form-message" id="@{fieldName}.help">@Messages("form.signup.username.help")</small> - @renderFormErrors(fieldName, formErrors) - </div> - <div class="pure-control-group"> - <label for="@{fieldEmail}">Email address</label> - <input class="pure-input-1-2" id="@{fieldEmail}" name="@{fieldEmail}" placeholder="some@@somewhere.org" maxlength="128" required="" type="email" value="@{formData.get(fieldEmail)}" autocomplete="email"> - <small class="pure-form-message" id="@{fieldEmail}.help">Please enter your email address.</small> - @renderFormErrors(fieldEmail, formErrors) - </div> - <div class="pure-control-group"> - <label for="@{fieldPassword}">Password</label> - <input class="pure-input-1-2" id="@{fieldPassword}" name="@{fieldPassword}" placeholder="Please choose a secure password!" maxlength="128" required="" type="password" value="" autocomplete="new-password"> - <small class="pure-form-message" id="@{fieldPassword}.help">Your password must be at least 12 characters long.</small> - @renderFormErrors(fieldPassword, formErrors) - </div> - @csrfToken(csrf) - <div class="pure-controls"> - <button type="submit" class="pure-button">@Messages("form.signup.button.submit")</button> - </div> - </fieldset> - </form> + </div> + <div class="signup-form"> + <form action="@action" method="POST" accept-charset="UTF-8" class="pure-form pure-form-aligned" autocomplete="on"> + <fieldset id="signup-data"> + <div class="pure-control-group"> + <label for="@{fieldName}">@Messages("form.signup.username")</label> + <input class="pure-input-1-2" id="@{fieldName}" name="@{fieldName}" placeholder="@Messages("form.signup.username.placeholder")" maxlength="31" required="" type="text" value="@{formData.get(fieldName)}" autocomplete="username" autofocus> + <small class="pure-form-message" id="@{fieldName}.help">@Messages("form.signup.username.help")</small> + @renderFormErrors(fieldName, formErrors) + </div> + <div class="pure-control-group"> + <label for="@{fieldEmail}">Email address</label> + <input class="pure-input-1-2" id="@{fieldEmail}" name="@{fieldEmail}" placeholder="some@@somewhere.org" maxlength="128" required="" type="email" value="@{formData.get(fieldEmail)}" autocomplete="email"> + <small class="pure-form-message" id="@{fieldEmail}.help">Please enter your email address.</small> + @renderFormErrors(fieldEmail, formErrors) + </div> + <div class="pure-control-group"> + <label for="@{fieldPassword}">Password</label> + <input class="pure-input-1-2" id="@{fieldPassword}" name="@{fieldPassword}" placeholder="Please choose a secure password!" maxlength="128" required="" type="password" value="" autocomplete="new-password"> + <small class="pure-form-message" id="@{fieldPassword}.help">Your password must be at least 12 characters long.</small> + @renderFormErrors(fieldPassword, formErrors) + </div> + @csrfToken(csrf) + <div class="pure-controls"> + <button type="submit" class="pure-button">@Messages("form.signup.button.submit")</button> + </div> + </fieldset> + </form> + </div> </div> </div> </div> diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/welcome.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/welcome.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/welcome.scala.html 2025-02-02 12:11:36.331101932 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/welcome.scala.html 2025-02-02 12:11:36.331101932 +0000 @@ -1,18 +1,18 @@ -@(lang: LanguageCode = LanguageCode("en"), pathPrefix: Option[Uri] = None)(csrf: Option[CsrfToken] = None, title: Option[String] = None) -@main(lang, pathPrefix)()(csrf, title) { +@(baseUri: Uri, lang: LanguageCode = LanguageCode("en"))(csrf: Option[CsrfToken] = None, title: Option[String] = None) +@main(baseUri, lang)()(csrf, title) { @defining(lang.toLocale) { implicit locale => <div class="content"> - <h2 class="content-head is-center" style="color: #34495e; text-align: center;">@Messages("landingpage.welcome.title")</h2> + <h1 class="content-head is-center" style="color: #34495e; text-align: center;">@Messages("landingpage.welcome.title")</h2> </div> <div class="content"> <div class="ribbon l-box-lrg pure-g" style="background: #2d3e50; color: #aaa; padding: 2em; border-bottom: 1px solid rgba(0,0,0,0.1);"> <div class="l-box-lrg is-center pure-u-1 pure-u-md-1-2 pure-u-lg-2-5"> - <img width="300" alt="A neon sign saying: Do something great!" class="pure-img-responsive" src="@pathPrefix/assets/img/clark-tibbs-oqStl2L5oxI-unsplash.jpg"> + <img width="300" alt="A neon sign saying: Do something great!" class="pure-img-responsive" src="@{baseUri.addPath("assets/img/clark-tibbs-oqStl2L5oxI-unsplash.jpg")}"> </div> <div class="pure-u-1 pure-u-md-1-2 pure-u-lg-3-5"> <h2 class="content-head content-head-ribbon" style="color: white;">@Messages("landingpage.welcome.ribbon.title")</h2> <p>@Messages("landingpage.welcome.ribbon.text")</p> - <p><a class="pure-button pure-button-primary" href="/login">@Messages("global.login")</a></p> + <p><a class="pure-button pure-button-primary" href="@{baseUri.addPath("login")}">@Messages("global.login")</a></p> </div> </div> </div>