Sun, 08 Jan 2023 17:07:26 +0100
#15 add issue filters
1.1 --- a/setup/postgres/psql_create_tables.sql Tue Jan 03 18:25:51 2023 +0100 1.2 +++ b/setup/postgres/psql_create_tables.sql Sun Jan 08 17:07:26 2023 +0100 1.3 @@ -154,6 +154,7 @@ 1.4 'TogetherWith', 1.5 'Before', 1.6 'SubtaskOf', 1.7 + 'DefectOf', 1.8 'Blocks', 1.9 'Tests', 1.10 'Duplicates'
2.1 --- a/src/main/kotlin/de/uapcore/lightpit/dao/DataAccessObject.kt Tue Jan 03 18:25:51 2023 +0100 2.2 +++ b/src/main/kotlin/de/uapcore/lightpit/dao/DataAccessObject.kt Sun Jan 08 17:07:26 2023 +0100 2.3 @@ -70,8 +70,8 @@ 2.4 fun collectIssueSummary(project: Project): IssueSummary 2.5 fun collectIssueSummary(assignee: User): IssueSummary 2.6 2.7 - fun listIssues(project: Project): List<Issue> 2.8 - fun listIssues(project: Project, version: Version?, component: Component?): List<Issue> 2.9 + fun listIssues(project: Project, includeDone: Boolean): List<Issue> 2.10 + fun listIssues(project: Project, includeDone: Boolean, version: Version?, component: Component?): List<Issue> 2.11 fun findIssue(id: Int): Issue? 2.12 fun insertIssue(issue: Issue): Int 2.13 fun updateIssue(issue: Issue) 2.14 @@ -87,6 +87,7 @@ 2.15 fun insertIssueRelation(rel: IssueRelation) 2.16 fun deleteIssueRelation(rel: IssueRelation) 2.17 fun listIssueRelations(issue: Issue): List<IssueRelation> 2.18 + fun getIssueRelationMap(project: Project, includeDone: Boolean): IssueRelationMap 2.19 2.20 fun insertHistoryEvent(issue: Issue, newId: Int = 0) 2.21 fun insertHistoryEvent(issue: Issue, issueComment: IssueComment, newId: Int = 0)
3.1 --- a/src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt Tue Jan 03 18:25:51 2023 +0100 3.2 +++ b/src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt Sun Jan 08 17:07:26 2023 +0100 3.3 @@ -27,6 +27,7 @@ 3.4 3.5 import de.uapcore.lightpit.entities.* 3.6 import de.uapcore.lightpit.types.IssueHistoryType 3.7 +import de.uapcore.lightpit.types.RelationType 3.8 import de.uapcore.lightpit.types.WebColor 3.9 import de.uapcore.lightpit.viewmodel.ComponentSummary 3.10 import de.uapcore.lightpit.viewmodel.IssueSummary 3.11 @@ -480,11 +481,12 @@ 3.12 select issueid, 3.13 i.project, p.name as projectname, p.node as projectnode, 3.14 component, c.name as componentname, c.node as componentnode, 3.15 - status, category, subject, i.description, 3.16 + status, phase, category, subject, i.description, 3.17 userid, username, givenname, lastname, mail, 3.18 created, updated, eta, affected, resolved 3.19 from lpit_issue i 3.20 join lpit_project p on i.project = projectid 3.21 + join lpit_issue_phases using (status) 3.22 left join lpit_component c on component = c.id 3.23 left join lpit_user on userid = assignee 3.24 """.trimIndent() 3.25 @@ -534,15 +536,17 @@ 3.26 return i 3.27 } 3.28 3.29 - override fun listIssues(project: Project): List<Issue> = 3.30 - withStatement("$issueQuery where i.project = ?") { 3.31 + override fun listIssues(project: Project, includeDone: Boolean): List<Issue> = 3.32 + withStatement("$issueQuery where i.project = ? and (? or phase < 2)") { 3.33 setInt(1, project.id) 3.34 + setBoolean(2, includeDone) 3.35 queryAll { it.extractIssue() } 3.36 } 3.37 3.38 - override fun listIssues(project: Project, version: Version?, component: Component?): List<Issue> = 3.39 + override fun listIssues(project: Project, includeDone: Boolean, version: Version?, component: Component?): List<Issue> = 3.40 withStatement( 3.41 - """$issueQuery where i.project = ? and 3.42 + """$issueQuery where i.project = ? and 3.43 + (? or phase < 2) and 3.44 (not ? or ? in (resolved, affected)) and (not ? or (resolved is null and affected is null)) and 3.45 (not ? or component = ?) and (not ? or component is null) 3.46 """.trimIndent() 3.47 @@ -559,8 +563,9 @@ 3.48 } 3.49 } 3.50 setInt(1, project.id) 3.51 - applyFilter(version, 2, 4, 3) 3.52 - applyFilter(component, 5, 7, 6) 3.53 + setBoolean(2, includeDone) 3.54 + applyFilter(version, 3, 5, 4) 3.55 + applyFilter(component, 6, 8, 7) 3.56 3.57 queryAll { it.extractIssue() } 3.58 } 3.59 @@ -678,6 +683,21 @@ 3.60 queryAll { IssueRelation(issue, findIssue(it.getInt("from_issue"))!!, it.getEnum("type"), true) } 3.61 }.forEach(this::add) 3.62 } 3.63 + 3.64 + override fun getIssueRelationMap(project: Project, includeDone: Boolean): IssueRelationMap = 3.65 + withStatement( 3.66 + """ 3.67 + select r.from_issue, r.to_issue, r.type 3.68 + from lpit_issue_relation r 3.69 + join lpit_issue i on i.issueid = r.from_issue 3.70 + join lpit_issue_phases p on i.status = p.status 3.71 + where i.project = ? and (? or p.phase < 2) 3.72 + """.trimIndent() 3.73 + ) { 3.74 + setInt(1, project.id) 3.75 + setBoolean(2, includeDone) 3.76 + queryAll { Pair(it.getInt("from_issue"), Pair(it.getInt("to_issue"), it.getEnum<RelationType>("type"))) } 3.77 + }.groupBy({it.first},{it.second}) 3.78 //</editor-fold> 3.79 3.80 //<editor-fold desc="IssueComment">
4.1 --- a/src/main/kotlin/de/uapcore/lightpit/entities/IssueRelation.kt Tue Jan 03 18:25:51 2023 +0100 4.2 +++ b/src/main/kotlin/de/uapcore/lightpit/entities/IssueRelation.kt Sun Jan 08 17:07:26 2023 +0100 4.3 @@ -34,3 +34,5 @@ 4.4 val type: RelationType, 4.5 val reverse: Boolean = false 4.6 ) 4.7 + 4.8 +typealias IssueRelationMap = Map<Int, List<Pair<Int, RelationType>>>
5.1 --- a/src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt Tue Jan 03 18:25:51 2023 +0100 5.2 +++ b/src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt Sun Jan 08 17:07:26 2023 +0100 5.3 @@ -184,7 +184,13 @@ 5.4 private fun project(http: HttpRequest, dao: DataAccessObject) { 5.5 withPathInfo(http, dao)?.run { 5.6 5.7 - val issues = dao.listIssues(project, version, component) 5.8 + val filter = IssueFilter(http) 5.9 + 5.10 + val needRelationsMap = filter.onlyBlocker 5.11 + 5.12 + val relationsMap = if (needRelationsMap) dao.getIssueRelationMap(project, filter.includeDone) else emptyMap() 5.13 + 5.14 + val issues = dao.listIssues(project, filter.includeDone, version, component) 5.15 .sortedWith( 5.16 IssueSorter( 5.17 IssueSorter.Criteria(IssueSorter.Field.DONE), 5.18 @@ -192,10 +198,16 @@ 5.19 IssueSorter.Criteria(IssueSorter.Field.UPDATED, false) 5.20 ) 5.21 ) 5.22 + .filter { 5.23 + (!filter.onlyMine || (it.assignee?.username ?: "") == (http.remoteUser ?: "<Anonymous>")) && 5.24 + (!filter.onlyBlocker || (relationsMap[it.id]?.any { (_,type) -> type.blocking }?:false)) && 5.25 + (filter.status.isEmpty() || filter.status.contains(it.status)) && 5.26 + (filter.category.isEmpty() || filter.category.contains(it.category)) 5.27 + } 5.28 5.29 with(http) { 5.30 pageTitle = project.name 5.31 - view = ProjectDetails(projectInfo, issues, version, component) 5.32 + view = ProjectDetails(projectInfo, issues, filter, version, component) 5.33 feedPath = feedPath(project) 5.34 navigationMenu = activeProjectNavMenu( 5.35 dao.listProjects(), 5.36 @@ -467,7 +479,7 @@ 5.37 project, 5.38 version, 5.39 component, 5.40 - dao.listIssues(project), 5.41 + dao.listIssues(project, true), 5.42 dao.listIssueRelations(issue), 5.43 relationError 5.44 )
6.1 --- a/src/main/kotlin/de/uapcore/lightpit/types/RelationType.kt Tue Jan 03 18:25:51 2023 +0100 6.2 +++ b/src/main/kotlin/de/uapcore/lightpit/types/RelationType.kt Sun Jan 08 17:07:26 2023 +0100 6.3 @@ -26,12 +26,13 @@ 6.4 6.5 package de.uapcore.lightpit.types 6.6 6.7 -enum class RelationType(val bidi: Boolean) { 6.8 - RelatesTo(true), 6.9 - TogetherWith(true), 6.10 - Before(false), 6.11 - SubtaskOf(false), 6.12 - Blocks(false), 6.13 - Tests(false), 6.14 - Duplicates(false) 6.15 -} 6.16 \ No newline at end of file 6.17 +enum class RelationType(val bidi: Boolean, val blocking: Boolean) { 6.18 + RelatesTo(true, false), 6.19 + TogetherWith(true, false), 6.20 + Before(false, true), 6.21 + SubtaskOf(false, true), 6.22 + DefectOf(false, true), 6.23 + Blocks(false, true), 6.24 + Tests(false, true), 6.25 + Duplicates(false, false) 6.26 +}
7.1 --- a/src/main/kotlin/de/uapcore/lightpit/viewmodel/Issues.kt Tue Jan 03 18:25:51 2023 +0100 7.2 +++ b/src/main/kotlin/de/uapcore/lightpit/viewmodel/Issues.kt Sun Jan 08 17:07:26 2023 +0100 7.3 @@ -31,6 +31,7 @@ 7.4 import com.vladsch.flexmark.parser.Parser 7.5 import com.vladsch.flexmark.util.data.MutableDataSet 7.6 import com.vladsch.flexmark.util.data.SharedDataKeys 7.7 +import de.uapcore.lightpit.HttpRequest 7.8 import de.uapcore.lightpit.entities.* 7.9 import de.uapcore.lightpit.types.* 7.10 import kotlin.math.roundToInt 7.11 @@ -111,8 +112,9 @@ 7.12 val options = MutableDataSet() 7.13 .set(SharedDataKeys.EXTENSIONS, listOf(TablesExtension.create(), StrikethroughExtension.create())) 7.14 parser = Parser.builder(options).build() 7.15 - renderer = HtmlRenderer.builder(options 7.16 - .set(HtmlRenderer.ESCAPE_HTML, true) 7.17 + renderer = HtmlRenderer.builder( 7.18 + options 7.19 + .set(HtmlRenderer.ESCAPE_HTML, true) 7.20 ).build() 7.21 7.22 issue.description = formatMarkdown(issue.description ?: "") 7.23 @@ -164,3 +166,57 @@ 7.24 } 7.25 } 7.26 7.27 +class IssueFilter(http: HttpRequest) { 7.28 + 7.29 + val issueStatus = IssueStatus.values() 7.30 + val issueCategory = IssueCategory.values() 7.31 + val flagIncludeDone = "f.0" 7.32 + val flagMine = "f.1" 7.33 + val flagBlocker = "f.2" 7.34 + 7.35 + val includeDone: Boolean = evalFlag(http, flagIncludeDone) 7.36 + val onlyMine: Boolean = evalFlag(http, flagMine) 7.37 + val onlyBlocker: Boolean = evalFlag(http, flagBlocker) 7.38 + val status: List<IssueStatus> = evalEnum(http, "s") 7.39 + val category: List<IssueCategory> = evalEnum(http, "c") 7.40 + 7.41 + private fun evalFlag(http: HttpRequest, name: String): Boolean { 7.42 + val param = http.paramArray("filter") 7.43 + if (param.isNotEmpty()) { 7.44 + if (param.contains(name)) { 7.45 + http.session.setAttribute(name, true) 7.46 + } else { 7.47 + http.session.removeAttribute(name) 7.48 + } 7.49 + } 7.50 + return http.session.getAttribute(name) != null 7.51 + } 7.52 + 7.53 + private inline fun <reified T : Enum<T>> evalEnum(http: HttpRequest, prefix: String): List<T> { 7.54 + val sattr = "f.${prefix}" 7.55 + val param = http.paramArray("filter") 7.56 + if (param.isNotEmpty()) { 7.57 + val list = param.filter { it.startsWith("${prefix}.") } 7.58 + .map { it.substring(prefix.length + 1) } 7.59 + .map { 7.60 + try { 7.61 + // quick and very dirty validation 7.62 + enumValueOf<T>(it) 7.63 + } catch (_: IllegalArgumentException) { 7.64 + // skip 7.65 + } 7.66 + } 7.67 + if (list.isEmpty()) { 7.68 + http.session.removeAttribute(sattr) 7.69 + } else { 7.70 + http.session.setAttribute(sattr, list.joinToString(",")) 7.71 + } 7.72 + } 7.73 + 7.74 + return http.session.getAttribute(sattr) 7.75 + ?.toString() 7.76 + ?.split(",") 7.77 + ?.map { enumValueOf(it) } 7.78 + ?: emptyList() 7.79 + } 7.80 +}
8.1 --- a/src/main/kotlin/de/uapcore/lightpit/viewmodel/Projects.kt Tue Jan 03 18:25:51 2023 +0100 8.2 +++ b/src/main/kotlin/de/uapcore/lightpit/viewmodel/Projects.kt Sun Jan 08 17:07:26 2023 +0100 8.3 @@ -47,6 +47,7 @@ 8.4 class ProjectDetails( 8.5 val projectInfo: ProjectInfo, 8.6 val issues: List<Issue>, 8.7 + val filter: IssueFilter, 8.8 val version: Version? = null, 8.9 val component: Component? = null 8.10 ) : View() {
9.1 --- a/src/main/resources/localization/strings.properties Tue Jan 03 18:25:51 2023 +0100 9.2 +++ b/src/main/resources/localization/strings.properties Sun Jan 08 17:07:26 2023 +0100 9.3 @@ -25,6 +25,7 @@ 9.4 app.license.title=License 9.5 app.name=Lightweight Project and Issue Tracking 9.6 button.add=Add 9.7 +button.apply=Apply 9.8 button.back=Back 9.9 button.cancel=Cancel 9.10 button.comment.edit=Edit Comment 9.11 @@ -85,6 +86,11 @@ 9.12 issue.created=Created 9.13 issue.description=Description 9.14 issue.eta=ETA 9.15 +issue.filter=Filter 9.16 +issue.filter.blocking=show only blocking 9.17 +issue.filter.done=show resolved 9.18 +issue.filter.mine=only assigned to me 9.19 +issue.filter.more=more filters 9.20 issue.id=Issue ID 9.21 issue.relations=Relations 9.22 issue.relations.issue=Issue 9.23 @@ -169,4 +175,6 @@ 9.24 version.status.Released=Released 9.25 version.status.Unreleased=Unreleased 9.26 version.status=Status 9.27 -version=Version 9.28 \ No newline at end of file 9.29 +version=Version 9.30 +issue.relations.type.DefectOf=defect of 9.31 +issue.relations.type.DefectOf.rev=defect 9.32 \ No newline at end of file
10.1 --- a/src/main/resources/localization/strings_de.properties Tue Jan 03 18:25:51 2023 +0100 10.2 +++ b/src/main/resources/localization/strings_de.properties Sun Jan 08 17:07:26 2023 +0100 10.3 @@ -24,6 +24,7 @@ 10.4 app.changelog=Versionshistorie 10.5 app.license.title=Lizenz (Englisch) 10.6 app.name=Lightweight Project and Issue Tracking 10.7 +button.apply=Anwenden 10.8 button.add=Hinzuf\u00fcgen 10.9 button.back=Zur\u00fcck 10.10 button.cancel=Abbrechen 10.11 @@ -85,6 +86,11 @@ 10.12 issue.created=Erstellt 10.13 issue.description=Beschreibung 10.14 issue.eta=Zieldatum 10.15 +issue.filter=Filter 10.16 +issue.filter.blocking=zeige nur blockierende 10.17 +issue.filter.done=zeige erledigte 10.18 +issue.filter.mine=nur mir zugewiesene 10.19 +issue.filter.more=mehr Filter 10.20 issue.id=Vorgangs-ID 10.21 issue.relations=Beziehungen 10.22 issue.relations.issue=Vorgang 10.23 @@ -170,3 +176,5 @@ 10.24 version.status.Unreleased=Unver\u00f6ffentlicht 10.25 version.status=Status 10.26 version=Version 10.27 +issue.relations.type.DefectOf.rev=Fehler 10.28 +issue.relations.type.DefectOf=Fehler von
11.1 --- a/src/main/webapp/WEB-INF/changelogs/changelog-de.jspf Tue Jan 03 18:25:51 2023 +0100 11.2 +++ b/src/main/webapp/WEB-INF/changelogs/changelog-de.jspf Sun Jan 08 17:07:26 2023 +0100 11.3 @@ -32,6 +32,7 @@ 11.4 <li>Mehrfachauswahl für Versionen im Vorgang entfernt.</li> 11.5 <li>RSS Feeds für Projekte hinzugefügt.</li> 11.6 <li>Vorgangsansicht vereinfacht.</li> 11.7 + <li>Filteroptionen hinzugefügt.</li> 11.8 <li>Möglichkeit zum Deaktivieren einer Komponente hinzugefügt.</li> 11.9 <li>Datum der Veröffentlichung und des Supportendes zu Versionen hinzugefügt.</li> 11.10 <li>Gesamtanzahl der Kommentare wird nun angezeigt.</li>
12.1 --- a/src/main/webapp/WEB-INF/changelogs/changelog.jspf Tue Jan 03 18:25:51 2023 +0100 12.2 +++ b/src/main/webapp/WEB-INF/changelogs/changelog.jspf Sun Jan 08 17:07:26 2023 +0100 12.3 @@ -32,6 +32,7 @@ 12.4 <li>Remove multi selection of versions within an issue.</li> 12.5 <li>Add RSS feeds for projects.</li> 12.6 <li>Simplify issue view.</li> 12.7 + <li>Add filtering options.</li> 12.8 <li>Add possibility to deactivate a component.</li> 12.9 <li>Add release and end of life dates to versions.</li> 12.10 <li>Add the total number of comments to the caption.</li>
13.1 --- a/src/main/webapp/WEB-INF/jsp/project-details.jsp Tue Jan 03 18:25:51 2023 +0100 13.2 +++ b/src/main/webapp/WEB-INF/jsp/project-details.jsp Sun Jan 08 17:07:26 2023 +0100 13.3 @@ -40,6 +40,9 @@ 13.4 <button onclick="toggleProjectDetails()" id="toggle-details-button"><fmt:message key="button.project.details"/></button> 13.5 </div> 13.6 13.7 +<h3><fmt:message key="issue.filter" /></h3> 13.8 +<%@include file="../jspf/issue-filter.jspf"%> 13.9 + 13.10 <h2><fmt:message key="progress" /></h2> 13.11 13.12 <c:set var="summary" value="${viewmodel.projectInfo.issueSummary}" />
14.1 --- a/src/main/webapp/WEB-INF/jsp/site.jsp Tue Jan 03 18:25:51 2023 +0100 14.2 +++ b/src/main/webapp/WEB-INF/jsp/site.jsp Sun Jan 08 17:07:26 2023 +0100 14.3 @@ -31,7 +31,7 @@ 14.4 <%@taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> 14.5 14.6 <%-- Version suffix for forcing browsers to update the CSS / JS files --%> 14.7 -<c:set scope="page" var="versionSuffix" value="20230103"/> 14.8 +<c:set scope="page" var="versionSuffix" value="20230108"/> 14.9 14.10 <%-- Make the base href easily available at request scope --%> 14.11 <c:set scope="page" var="baseHref" value="${requestScope[Constants.REQ_ATTR_BASE_HREF]}"/>
15.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 15.2 +++ b/src/main/webapp/WEB-INF/jspf/issue-filter.jspf Sun Jan 08 17:07:26 2023 +0100 15.3 @@ -0,0 +1,60 @@ 15.4 +<%-- 15.5 + 15.6 +--%> 15.7 +<form method="GET"> 15.8 + <div> 15.9 + <label> 15.10 + <input name="filter" 15.11 + type="checkbox" 15.12 + value="${viewmodel.filter.flagIncludeDone}" 15.13 + <c:if test="${viewmodel.filter.includeDone}">checked</c:if> 15.14 + > 15.15 + <fmt:message key="issue.filter.done"/> 15.16 + </label> 15.17 + <label> 15.18 + <input name="filter" 15.19 + type="checkbox" 15.20 + value="${viewmodel.filter.flagMine}" 15.21 + <c:if test="${viewmodel.filter.onlyMine}">checked</c:if> 15.22 + > 15.23 + <fmt:message key="issue.filter.mine"/> 15.24 + </label> 15.25 + <label> 15.26 + <input name="filter" 15.27 + type="checkbox" 15.28 + value="${viewmodel.filter.flagBlocker}" 15.29 + <c:if test="${viewmodel.filter.onlyBlocker}">checked</c:if> 15.30 + > 15.31 + <fmt:message key="issue.filter.blocking"/> 15.32 + </label> 15.33 + <label> 15.34 + <input id="show-more-filters" type="checkbox" onclick="toggleFilterDetails()"> 15.35 + <fmt:message key="issue.filter.more"/> 15.36 + </label> 15.37 + </div> 15.38 + <div id="more-filters" style="display: flex; gap: 1em"> 15.39 + <div style="display: inline-block"> 15.40 + <label class="caption" style="display:block;" for="filter-category"><fmt:message key="issue.category"/></label> 15.41 + <select id="filter-category" name="filter" multiple size="10"> 15.42 + <c:forEach var="category" items="${viewmodel.filter.issueCategory}"> 15.43 + <option value="c.${category}" <c:if test="${viewmodel.filter.category.contains(category) }">selected</c:if> > 15.44 + <fmt:message key="issue.category.${category}"/> 15.45 + </option> 15.46 + </c:forEach> 15.47 + </select> 15.48 + </div> 15.49 + <div style="display: inline-block"> 15.50 + <label class="caption" style="display:block;" for="filter-status"><fmt:message key="issue.status"/></label> 15.51 + <select id="filter-status" name="filter" multiple size="10"> 15.52 + <c:forEach var="status" items="${viewmodel.filter.issueStatus}"> 15.53 + <option value="s.${status}" <c:if test="${viewmodel.filter.status.contains(status) }">selected</c:if>> 15.54 + <fmt:message key="issue.status.${status}"/> 15.55 + </option> 15.56 + </c:forEach> 15.57 + </select> 15.58 + </div> 15.59 + </div> 15.60 + <div class="medskip"> 15.61 + <button name="filter" type="submit"><fmt:message key="button.apply"/></button> 15.62 + </div> 15.63 +</form>
16.1 --- a/src/main/webapp/WEB-INF/jspf/issue-summary.jspf Tue Jan 03 18:25:51 2023 +0100 16.2 +++ b/src/main/webapp/WEB-INF/jspf/issue-summary.jspf Sun Jan 08 17:07:26 2023 +0100 16.3 @@ -9,7 +9,9 @@ 16.4 <div><c:out value="${summary.open}"/></div> 16.5 <div class="caption"><fmt:message key="issues.active"/>:</div> 16.6 <div><c:out value="${summary.active}"/></div> 16.7 + <c:if test="${summary.done gt 0}"> 16.8 <div class="caption"><fmt:message key="issues.done"/>:</div> 16.9 <div><c:out value="${summary.done}"/></div> 16.10 + </c:if> 16.11 </div> 16.12 </div> 16.13 \ No newline at end of file
17.1 --- a/src/main/webapp/lightpit.css Tue Jan 03 18:25:51 2023 +0100 17.2 +++ b/src/main/webapp/lightpit.css Sun Jan 08 17:07:26 2023 +0100 17.3 @@ -73,6 +73,14 @@ 17.4 padding: 0; 17.5 } 17.6 17.7 +h2 { 17.8 + margin: 0.75em 0; 17.9 +} 17.10 + 17.11 +h3 { 17.12 + margin: 0.25em 0; 17.13 +} 17.14 + 17.15 textarea, input, button, select { 17.16 font-family: inherit; 17.17 font-size: inherit;
18.1 --- a/src/main/webapp/project-details.js Tue Jan 03 18:25:51 2023 +0100 18.2 +++ b/src/main/webapp/project-details.js Sun Jan 08 17:07:26 2023 +0100 18.3 @@ -52,4 +52,20 @@ 18.4 full.style.display = 'none' 18.5 } 18.6 } 18.7 -window.addEventListener('load', function() { toggleProjectDetails() }, false) 18.8 + 18.9 +function toggleFilterDetails() { 18.10 + const filters = document.getElementById('more-filters') 18.11 + const toggle = document.getElementById('show-more-filters') 18.12 + if (toggle.checked) { 18.13 + filters.style.display = 'flex' 18.14 + } else { 18.15 + filters.style.display = 'none' 18.16 + } 18.17 +} 18.18 + 18.19 +function toggleDetails() { 18.20 + toggleProjectDetails() 18.21 + toggleFilterDetails() 18.22 +} 18.23 + 18.24 +window.addEventListener('load', function() { toggleDetails() }, false)