~jan0sch/smederee
Showing details for patch 7c6959c534e27d410c18cdcab64fca32b82ee2b5.
diff -rN -u old-smederee/modules/html-utils/src/main/scala/de/smederee/html/MarkdownRenderer.scala new-smederee/modules/html-utils/src/main/scala/de/smederee/html/MarkdownRenderer.scala --- old-smederee/modules/html-utils/src/main/scala/de/smederee/html/MarkdownRenderer.scala 2025-01-16 07:42:48.524310723 +0000 +++ new-smederee/modules/html-utils/src/main/scala/de/smederee/html/MarkdownRenderer.scala 2025-01-16 07:42:48.528310730 +0000 @@ -40,14 +40,14 @@ private val MarkdownExtensions = List(TablesExtension.create(), HeadingAnchorExtension.create(), TaskListItemsExtension.create()) - /** Render the given ticket content using markdown. + /** Render the given markdown content into HTML. * * @param markdownSource - * The content of a ticket description. + * Markdown source code that shall be rendered. * @return * A string containing the rendered markdown (HTML). */ - def renderTicketContent(markdownSource: String): String = { + def render(markdownSource: String): String = { val parser = Parser.builder().extensions(MarkdownExtensions.asJava).build() val markdown = parser.parse(markdownSource) val renderer = HtmlRenderer 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-01-16 07:42:48.524310723 +0000 +++ new-smederee/modules/hub/src/main/resources/assets/css/main.css 2025-01-16 07:42:48.528310730 +0000 @@ -81,6 +81,14 @@ border-bottom: 1px solid rgba(0,0,0,0.1); } +.s-box { + padding: 0.25em; +} + +.s-box-left-right { + padding: 0em 0.25em 0em 0.25em; +} + .is-center { text-align: center; } @@ -220,6 +228,12 @@ padding: 0.25em 0 0 0; } +.milestone-sidebar { + background: var(--background2); + line-height: 1.5em; + word-break: break-word; +} + .milestone-title { margin: auto; overflow: overlay; @@ -347,6 +361,7 @@ .ticket-sidebar { background: var(--background2); line-height: 1.5em; + word-break: break-word; } .todo-default { diff -rN -u old-smederee/modules/hub/src/main/resources/messages.properties new-smederee/modules/hub/src/main/resources/messages.properties --- old-smederee/modules/hub/src/main/resources/messages.properties 2025-01-16 07:42:48.524310723 +0000 +++ new-smederee/modules/hub/src/main/resources/messages.properties 2025-01-16 07:42:48.528310730 +0000 @@ -305,6 +305,8 @@ form.ticket.title.placeholder=Short concise summary what the ticket is about. form.ticket.title=Title +milestone.due-date=Due date + project.label.edit.link=Edit project.label.edit.title=Edit label ''{0}''. project.labels.add.title=Add a new label. @@ -318,6 +320,7 @@ project.menu.tickets=Tickets project.milestone.edit.link=Edit project.milestone.edit.title=Edit milestone ''{0}''. +project.milestone.title=Milestone {0} project.milestones.add.title=Add a new milestone. project.milestones.edit.title=Edit project milestones. project.milestones.list.empty=There are no milestones defined for this project. @@ -331,6 +334,10 @@ project.tickets.list.empty=There are no tickets defined for this project. project.tickets.list.title={0} tickets found. +ticket.assigned=Assigned to +ticket.reported=Reported by +ticket.milestones=Milestones +ticket.status=Status ticket.status.confirmed=Confirmed ticket.status.inprogress=In progress ticket.status.pending=Pending @@ -343,3 +350,4 @@ ticket.status.resolved.invalid=Invalid ticket.status.resolved.wontfix=Won''t fix ticket.status.submitted=Submitted +ticket.updated=Last updated at diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/tickets/LabelRoutes.scala new-smederee/modules/hub/src/main/scala/de/smederee/tickets/LabelRoutes.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/tickets/LabelRoutes.scala 2025-01-16 07:42:48.524310723 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/tickets/LabelRoutes.scala 2025-01-16 07:42:48.528310730 +0000 @@ -237,7 +237,7 @@ private val deleteLabel: AuthedRoutes[Account, F] = AuthedRoutes.of { case ar @ POST -> Root / UsernamePathParameter(projectOwnerName) / ProjectNamePathParameter( projectName - ) / "label" / LabelNamePathParameter(labelName) / "delete" as user => + ) / "labels" / LabelNamePathParameter(labelName) / "delete" as user => ar.req.decodeStrict[F, UrlForm] { urlForm => for { csrf <- Sync[F].delay(ar.req.getCsrfToken) @@ -304,7 +304,7 @@ private val editLabel: AuthedRoutes[Account, F] = AuthedRoutes.of { case ar @ POST -> Root / UsernamePathParameter(projectOwnerName) / ProjectNamePathParameter( projectName - ) / "label" / LabelNamePathParameter(labelName) as user => + ) / "labels" / LabelNamePathParameter(labelName) as user => ar.req.decodeStrict[F, UrlForm] { urlForm => val response = for { @@ -330,7 +330,7 @@ ) ) ) - actionUri <- Sync[F].delay(projectBaseUri.addSegment("label").addSegment(label.name.toString)) + actionUri <- Sync[F].delay(projectBaseUri.addSegment("labels").addSegment(label.name.toString)) formData <- Sync[F].delay(urlForm.values) // labelIdMatches <- Sync[F].delay( // formData @@ -415,7 +415,7 @@ private val showEditLabelForm: AuthedRoutes[Account, F] = AuthedRoutes.of { case ar @ GET -> Root / UsernamePathParameter(projectOwnerName) / ProjectNamePathParameter( projectName - ) / "label" / LabelNamePathParameter(labelName) / "edit" as user => + ) / "labels" / LabelNamePathParameter(labelName) / "edit" as user => for { csrf <- Sync[F].delay(ar.req.getCsrfToken) projectAndId <- loadProject(user.some)(projectOwnerName, projectName) @@ -435,7 +435,7 @@ ) ) ) - actionUri <- Sync[F].delay(projectBaseUri.addSegment("label").addSegment(label.name.toString)) + actionUri <- Sync[F].delay(projectBaseUri.addSegment("labels").addSegment(label.name.toString)) formData <- Sync[F].delay(LabelForm.fromLabel(label)) resp <- Ok( views.html diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/tickets/MilestoneRoutes.scala new-smederee/modules/hub/src/main/scala/de/smederee/tickets/MilestoneRoutes.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/tickets/MilestoneRoutes.scala 2025-01-16 07:42:48.524310723 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/tickets/MilestoneRoutes.scala 2025-01-16 07:42:48.528310730 +0000 @@ -33,6 +33,7 @@ import org.http4s.dsl.Http4sDsl import org.http4s.headers.Location import org.http4s.twirl.TwirlInstances._ +import org.slf4j.LoggerFactory /** Routes for managing milestones (basically CRUD functionality). * @@ -50,12 +51,71 @@ milestoneRepo: MilestoneRepository[F], projectRepo: ProjectRepository[F] ) extends Http4sDsl[F] { + private val log = LoggerFactory.getLogger(getClass) given CsrfProtectionConfiguration = configuration.csrfProtection private val linkToHubService = configuration.hub.baseUri private val linkConfig = configuration.externalUrl + /** Logic for rendering a detail page for a single milestone. + * + * @param csrf + * An optional CSRF-Token that shall be used. + * @param user + * An optional user account for whom the list of tickets shall be rendered. + * @param projectOwnerName + * The username of the account who owns the project. + * @param projectName + * The name of the project. + * @param milestoneTitle + * The title of the milestone that shall be rendered. + * @return + * An HTTP response containing the rendered HTML. + */ + private def doShowMilestone(csrf: Option[CsrfToken])(user: Option[Account])(projectOwnerName: Username)( + projectName: ProjectName + )(milestoneTitle: MilestoneTitle): F[Response[F]] = + for { + language <- Sync[F].delay(user.flatMap(_.language).getOrElse(LanguageCode("en"))) + projectAndId <- loadProject(user)(projectOwnerName, projectName) + milestone <- projectAndId match { + case Some((_, projectId)) => milestoneRepo.findMilestone(projectId)(milestoneTitle) + case _ => Sync[F].delay(None) + } + resp <- (projectAndId, milestone) match { + case (Some(project, _), Some(milestone)) => + for { + projectBaseUri <- Sync[F].delay( + linkConfig.createFullUri( + Uri(path = + Uri.Path( + Vector(Uri.Path.Segment(s"~$projectOwnerName"), Uri.Path.Segment(projectName.toString)) + ) + ) + ) + ) + actionUri <- Sync[F].delay(projectBaseUri.addSegment("milestones").addSegment(milestone.title.toString)) + renderedDescription <- Sync[F].delay(milestone.description.map(_.toString).map(MarkdownRenderer.render)) + resp <- Ok( + views.html.showMilestone(lang = language)( + actionUri, + csrf, + linkToHubService, + milestone, + renderedDescription, + projectBaseUri, + Nil, + s"Milestone ${milestone.title}".some, + user, + project + ) + ) + } yield resp + case _ => NotFound() + } + } yield resp + /** Logic for rendering a list of all milestones for a project and optionally management functionality. * * @param csrf @@ -228,7 +288,7 @@ private val deleteMilestone: AuthedRoutes[Account, F] = AuthedRoutes.of { case ar @ POST -> Root / UsernamePathParameter(projectOwnerName) / ProjectNamePathParameter( projectName - ) / "milestone" / MilestoneTitlePathParameter(milestoneTitle) / "delete" as user => + ) / "milestones" / MilestoneTitlePathParameter(milestoneTitle) / "delete" as user => ar.req.decodeStrict[F, UrlForm] { urlForm => for { csrf <- Sync[F].delay(ar.req.getCsrfToken) @@ -294,7 +354,7 @@ private val editMilestone: AuthedRoutes[Account, F] = AuthedRoutes.of { case ar @ POST -> Root / UsernamePathParameter(projectOwnerName) / ProjectNamePathParameter( projectName - ) / "milestone" / MilestoneTitlePathParameter(milestoneTitle) as user => + ) / "milestones" / MilestoneTitlePathParameter(milestoneTitle) as user => ar.req.decodeStrict[F, UrlForm] { urlForm => for { csrf <- Sync[F].delay(ar.req.getCsrfToken) @@ -320,7 +380,7 @@ ) ) actionUri <- Sync[F].delay( - projectBaseUri.addSegment("milestone").addSegment(milestone.title.toString) + projectBaseUri.addSegment("milestones").addSegment(milestone.title.toString) ) formData <- Sync[F].delay(urlForm.values) milestoneIdMatches <- Sync[F].delay( @@ -399,7 +459,7 @@ private val showEditMilestoneForm: AuthedRoutes[Account, F] = AuthedRoutes.of { case ar @ GET -> Root / UsernamePathParameter(projectOwnerName) / ProjectNamePathParameter( projectName - ) / "milestone" / MilestoneTitlePathParameter(milestoneTitle) / "edit" as user => + ) / "milestones" / MilestoneTitlePathParameter(milestoneTitle) / "edit" as user => for { csrf <- Sync[F].delay(ar.req.getCsrfToken) language <- Sync[F].delay(user.language.getOrElse(LanguageCode("en"))) @@ -420,7 +480,7 @@ ) ) ) - actionUri <- Sync[F].delay(projectBaseUri.addSegment("milestone").addSegment(milestone.title.toString)) + actionUri <- Sync[F].delay(projectBaseUri.addSegment("milestones").addSegment(milestone.title.toString)) formData <- Sync[F].delay(MilestoneForm.fromMilestone(milestone)) resp <- Ok( views.html.editMilestone(lang = language)( @@ -452,6 +512,34 @@ } yield resp } + private val showMilestone: AuthedRoutes[Account, F] = AuthedRoutes.of { + case ar @ GET -> Root / UsernamePathParameter(projectOwnerName) / ProjectNamePathParameter( + projectName + ) / "milestones" / MilestoneTitlePathParameter(milestoneTitle) as user => + val response = for { + csrf <- Sync[F].delay(ar.req.getCsrfToken) + resp <- doShowMilestone(csrf)(user.some)(projectOwnerName)(projectName)(milestoneTitle) + } yield resp + response.recoverWith { error => + log.error("Internal Server Error", error) + for { + csrf <- Sync[F].delay(ar.req.getCsrfToken) + language <- Sync[F].delay(user.language.getOrElse(LanguageCode("en"))) + resp <- InternalServerError(views.html.errors.internalServerError(lang = language)(csrf, user.some)) + } yield resp + } + } + + private val showMilestoneForGuests: HttpRoutes[F] = HttpRoutes.of { + case req @ GET -> Root / UsernamePathParameter(projectOwnerName) / ProjectNamePathParameter( + projectName + ) / "milestones" / MilestoneTitlePathParameter(milestoneTitle) => + for { + csrf <- Sync[F].delay(req.getCsrfToken) + resp <- doShowMilestone(csrf)(None)(projectOwnerName)(projectName)(milestoneTitle) + } yield resp + } + private val showMilestonesForGuests: HttpRoutes[F] = HttpRoutes.of { case req @ GET -> Root / UsernamePathParameter(projectOwnerName) / ProjectNamePathParameter( projectName @@ -463,8 +551,8 @@ } val protectedRoutes = - addMilestone <+> deleteMilestone <+> editMilestone <+> showEditMilestoneForm <+> showEditMilestonesPage + addMilestone <+> deleteMilestone <+> editMilestone <+> showEditMilestoneForm <+> showEditMilestonesPage <+> showMilestone - val routes = showMilestonesForGuests + val routes = showMilestoneForGuests <+> showMilestonesForGuests } diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/tickets/TicketRoutes.scala new-smederee/modules/hub/src/main/scala/de/smederee/tickets/TicketRoutes.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/tickets/TicketRoutes.scala 2025-01-16 07:42:48.524310723 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/tickets/TicketRoutes.scala 2025-01-16 07:42:48.528310730 +0000 @@ -142,9 +142,7 @@ ) ) ) - renderedTicketContent <- Sync[F].delay( - ticket.content.map(_.toString).map(MarkdownRenderer.renderTicketContent) - ) + renderedTicketContent <- Sync[F].delay(ticket.content.map(_.toString).map(MarkdownRenderer.render)) resp <- Ok( views.html.showTicket(lang = language)( projectBaseUri.addSegment("tickets"), diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/editLabels.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/editLabels.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/editLabels.scala.html 2025-01-16 07:42:48.524310723 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/editLabels.scala.html 2025-01-16 07:42:48.528310730 +0000 @@ -101,12 +101,12 @@ <div class="pure-u-8-24 label-description" style="height: @{lineHeight}px; line-height: @{lineHeight}px;">@label.description</div> <div class="pure-u-2-24" style="height: @{lineHeight}px; line-height: @{lineHeight}px; margin: auto;"> @if(user.exists(account => ProjectOwnerId.fromUserId(account.uid) === project.owner.uid)) { - <a class="pure-button" href="@projectBaseUri.addSegment("label").addSegment(label.name.toString).addSegment("edit")" title="@Messages("project.label.edit.title", label.name)">@Messages("project.label.edit.link")</a> + <a class="pure-button" href="@projectBaseUri.addSegment("labels").addSegment(label.name.toString).addSegment("edit")" title="@Messages("project.label.edit.title", label.name)">@Messages("project.label.edit.link")</a> } else { } </div> <div class="pure-u-8-24 label-form" style="height: @{lineHeight}px; line-height: @{lineHeight}px; padding-left: 1em;"> @if(user.exists(account => ProjectOwnerId.fromUserId(account.uid) === project.owner.uid)) { - <form action="@{linkToHubService.addPath(projectBaseUri.path.toString).addSegment("label").addSegment(label.name.toString).addSegment("delete")}" class="pure-form" method="POST" accept-charset="UTF-8"> + <form action="@{linkToHubService.addPath(projectBaseUri.path.toString).addSegment("labels").addSegment(label.name.toString).addSegment("delete")}" class="pure-form" method="POST" accept-charset="UTF-8"> <fieldset> <input type="hidden" id="@fieldId-@label.name" name="@fieldId" readonly="" value="@label.id"> <input type="hidden" id="@fieldName-@label.name" name="@fieldName" readonly="" value="@label.name"> diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/editMilestone.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/editMilestone.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/editMilestone.scala.html 2025-01-16 07:42:48.528310730 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/editMilestone.scala.html 2025-01-16 07:42:48.528310730 +0000 @@ -50,25 +50,25 @@ } </div> <div class="edit-milestones-form"> - <form action="@{linkToHubService.addPath(action.path.toString)}" class="pure-form pure-form-aligned" method="POST" accept-charset="UTF-8"> + <form action="@{linkToHubService.addPath(action.path.toString)}" class="pure-form pure-form-stacked" method="POST" accept-charset="UTF-8"> <fieldset> <input type="hidden" id="@fieldId" name="@fieldId" readonly="" value="@milestone.id"> <div class="pure-control-group"> - <milestone for="@{fieldTitle}">@Messages("form.milestone.title")</milestone> - <input id="@{fieldTitle}" name="@{fieldTitle}" maxlength="40" placeholder="@Messages("form.milestone.title.placeholder")" required="" type="text" value="@{formData(fieldTitle).headOption}"> - <span class="pure-form-message" id="@{fieldTitle}.help" style="margin-left: 13em;">@Messages("form.milestone.title.help")</span> + <label for="@{fieldTitle}">@Messages("form.milestone.title")</label> + <input class="pure-input-3-4" id="@{fieldTitle}" name="@{fieldTitle}" maxlength="40" placeholder="@Messages("form.milestone.title.placeholder")" required="" type="text" value="@{formData.get(fieldTitle).headOption}"> + <span class="pure-form-message" id="@{fieldTitle}.help">@Messages("form.milestone.title.help")</span> @renderFormErrors(fieldTitle, formErrors) </div> <div class="pure-control-group"> - <milestone for="@{fieldDescription}">@Messages("form.milestone.description")</milestone> - <input class="pure-input-3-4" id="@{fieldDescription}" name="@{fieldDescription}" maxlength="254" placeholder="@Messages("form.milestone.description.placeholder")" type="text" value="@{formData(fieldDescription).headOption}"> - <span class="pure-form-message" id="@{fieldDescription}.help" style="margin-left: 13em;">@Messages("form.milestone.description.help")</span> + <label for="@{fieldDescription}">@Messages("form.milestone.description")</label> + <textarea class="pure-input-3-4" id="@{fieldDescription}" name="@{fieldDescription}" placeholder="@Messages("form.milestone.description.placeholder")" rows="4" value="@{formData.get(fieldDescription).headOption}"></textarea> + <span class="pure-form-message" id="@{fieldDescription}.help">@Messages("form.milestone.description.help")</span> @renderFormErrors(fieldDescription, formErrors) </div> <div class="pure-control-group"> - <milestone for="@{fieldDueDate}">@Messages("form.milestone.due-date")</milestone> - <input id="@{fieldDueDate}" name="@{fieldDueDate}" type="date" value="@{formData(fieldDueDate).headOption}"> - <span class="pure-form-message" id="@{fieldDueDate}.help" style="margin-left: 13em;">@Messages("form.milestone.due-date.help")</span> + <label for="@{fieldDueDate}">@Messages("form.milestone.due-date")</label> + <input id="@{fieldDueDate}" name="@{fieldDueDate}" type="date" value="@{formData.get(fieldDueDate).headOption}"> + <span class="pure-form-message" id="@{fieldDueDate}.help">@Messages("form.milestone.due-date.help")</span> @renderFormErrors(fieldDueDate, formErrors) </div> @csrfToken(csrf) diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/editMilestones.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/editMilestones.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/editMilestones.scala.html 2025-01-16 07:42:48.528310730 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/editMilestones.scala.html 2025-01-16 07:42:48.528310730 +0000 @@ -100,15 +100,19 @@ @icon(baseUri)("flag", lineHeight.some) </div> <div class="pure-u-5-24 milestone-title" style="height: @{lineHeight}px; line-height: @{lineHeight}px;">@milestone.title @for(dueDate <- milestone.dueDate) { @formatDate(dueDate) }</div> - <div class="pure-u-8-24 milestone-description" style="height: @{lineHeight}px; line-height: @{lineHeight}px;">@milestone.description</div> + <div class="pure-u-8-24 milestone-description" style="height: @{lineHeight}px; line-height: @{lineHeight}px;"> + @for(description <- milestone.description) { + @if(description.toString.length < 180) { @description } else { @description.toString.take(176)) ...} + } + </div> <div class="pure-u-2-24" style="height: @{lineHeight}px; line-height: @{lineHeight}px; margin: auto;"> @if(user.exists(account => ProjectOwnerId.fromUserId(account.uid) === project.owner.uid)) { - <a class="pure-button" href="@projectBaseUri.addSegment("milestone").addSegment(milestone.title.toString).addSegment("edit")" title="@Messages("project.milestone.edit.title", milestone.title)">@Messages("project.milestone.edit.link")</a> + <a class="pure-button" href="@projectBaseUri.addSegment("milestones").addSegment(milestone.title.toString).addSegment("edit")" title="@Messages("project.milestone.edit.title", milestone.title)">@Messages("project.milestone.edit.link")</a> } else { } </div> <div class="pure-u-8-24 milestone-form" style="height: @{lineHeight}px; line-height: @{lineHeight}px; padding-left: 1em;"> @if(user.exists(account => ProjectOwnerId.fromUserId(account.uid) === project.owner.uid)) { - <form action="@{linkToHubService.addPath(projectBaseUri.path.toString).addSegment("milestone").addSegment(milestone.title.toString).addSegment("delete")}" class="pure-form" method="POST" accept-charset="UTF-8"> + <form action="@{linkToHubService.addPath(projectBaseUri.path.toString).addSegment("milestones").addSegment(milestone.title.toString).addSegment("delete")}" class="pure-form" method="POST" accept-charset="UTF-8"> <fieldset> <input type="hidden" id="@fieldId-@milestone.title" name="@fieldId" readonly="" value="@milestone.id"> <input type="hidden" id="@fieldTitle-@milestone.title" name="@fieldTitle" readonly="" value="@milestone.title"> diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/format/formatDate.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/format/formatDate.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/format/formatDate.scala.html 2025-01-16 07:42:48.528310730 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/format/formatDate.scala.html 2025-01-16 07:42:48.528310730 +0000 @@ -3,4 +3,4 @@ @import java.util.Locale @(date: LocalDate, style: FormatStyle = FormatStyle.MEDIUM)(implicit locale: Locale) -(@DateTimeFormatter.ofLocalizedDate(style).withLocale(locale).format(date)) +@{DateTimeFormatter.ofLocalizedDate(style).withLocale(locale).format(date)} diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/showMilestone.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/showMilestone.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/showMilestone.scala.html 1970-01-01 00:00:00.000000000 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/showMilestone.scala.html 2025-01-16 07:42:48.528310730 +0000 @@ -0,0 +1,66 @@ +@import de.smederee.hub.Account +@import de.smederee.tickets.MilestoneForm._ +@import de.smederee.tickets._ +@import de.smederee.tickets.forms._ +@import de.smederee.tickets.forms.types._ +@import de.smederee.tickets.views.html.forms.renderFormErrors +@import de.smederee.tickets.views.html.format._ +@import de.smederee.tickets.views.html.showProjectMenu + +@(baseUri: Uri = Uri(path = Uri.Path.Root), + lang: LanguageCode = LanguageCode("en") +)(action: Uri, + csrf: Option[CsrfToken] = None, + linkToHubService: Uri, + milestone: Milestone, + renderedMilestoneDescription: Option[String], + projectBaseUri: Uri, + tickets: List[Ticket], + title: Option[String] = None, + user: Option[Account], + project: Project +) +@main(linkToHubService, lang)()(csrf, title, user) { +@defining(lang.toLocale) { implicit locale => +<div class="content"> + <div class="pure-g"> + <div class="pure-u-1"> + <div class="l-box-left-right"> + <h2><a href="@{linkToHubService.addSegment(s"~${project.owner.name}")}">~@project.owner.name</a>/@project.name</h2> + @showProjectMenu(baseUri, linkToHubService)(projectBaseUri.addSegment("milestones").some, projectBaseUri, user, project) + <div class="project-summary-description"> + @Messages("project.milestone.title", milestone.title) + </div> + </div> + </div> + </div> + <div class="pure-g"> + <div class="pure-u-16-24 pure-u-md-16-24"> + <div class="l-box"> + @if(user.nonEmpty) { + <div class="milestone-buttons"> + <a class="pure-button" href="@projectBaseUri.addSegment("milestones").addSegment(milestone.title.toString).addSegment("edit")" title="@Messages("project.milestone.edit.title", milestone.title)">@Messages("project.milestone.edit.link")</a> + </div> + } else {} + <h1>@milestone.title</h1> + <div class="milestone-description">@Html(renderedMilestoneDescription)</div> + </div> + </div> + <div class="pure-u-8-24 pure-u-md-8-24"> + <div class="l-box"> + <div class="pure-g milestone-sidebar"> + <div class="pure-u-7-24"><div class="s-box-left-right">@Messages("milestone.due-date")</div></div> + <div class="pure-u-17-24">@for(date <- milestone.dueDate){@formatDate(date)}</div> + </div> + </div> + </div> + </div> + <div class="pure-g"> + <div class="pure-u-1-1 pure-u-md-1-1"> + <div class="l-box"> + </div> + </div> + </div> +</div> +} +} diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/showTicket.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/showTicket.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/showTicket.scala.html 2025-01-16 07:42:48.528310730 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/tickets/views/showTicket.scala.html 2025-01-16 07:42:48.528310730 +0000 @@ -46,18 +46,32 @@ <div class="pure-u-8-24 pure-u-md-8-24"> <div class="l-box"> <div class="pure-g ticket-sidebar"> - <div class="pure-u-1-5">Status</div><div class="pure-u-4-5">@formatTicketStatus(ticket)</div> - <div class="pure-u-1-5">Assigned</div><div class="pure-u-4-5"></div> - <div class="pure-u-1-5">Reported</div><div class="pure-u-4-5">@formatTicketSubmitter(linkToHubService)(ticket) at @formatDateTime(ticket.createdAt)</div> + <div class="pure-u-7-24"> + <div class="s-box-left-right">@Messages("ticket.status")</div> + </div> + <div class="pure-u-17-24">@formatTicketStatus(ticket)</div> + <div class="pure-u-7-24"> + <div class="s-box-left-right">@Messages("ticket.assigned")</div> + </div> + <div class="pure-u-17-24"></div> + <div class="pure-u-7-24"> + <div class="s-box-left-right">@Messages("ticket.reported")</div> + </div> + <div class="pure-u-17-24">@formatTicketSubmitter(linkToHubService)(ticket) at @formatDateTime(ticket.createdAt)</div> @if(milestones.nonEmpty) { - <div class="pure-u-1-5">Milestones</div> - <div class="pure-u-4-5"> - @for(milestone <- milestones) { - <a href="@projectBaseUri.addSegment("milestones").addSegment(milestone.title.toString)">@milestone.title</a><br/> - } - </div> + <div class="pure-u-7-24"> + <div class="s-box-left-right">@Messages("ticket.milestones")</div> + </div> + <div class="pure-u-17-24"> + @for(milestone <- milestones) { + <a href="@projectBaseUri.addSegment("milestones").addSegment(milestone.title.toString)">@milestone.title</a><br/> + } + </div> } else {} - <div class="pure-u-1-5">Updated</div><div class="pure-u-4-5">@formatDateTime(ticket.updatedAt)</div> + <div class="pure-u-7-24"> + <div class="s-box-left-right">@Messages("ticket.updated")</div> + </div> + <div class="pure-u-17-24">@formatDateTime(ticket.updatedAt)</div> </div> </div> </div>