Trash feature done

This commit is contained in:
2020-08-17 21:18:01 +02:00
parent 15de81394c
commit 5d9ca85b22
15 changed files with 234 additions and 51 deletions
@@ -80,7 +80,9 @@ class NoteController(
val html = when (it) {
MissingMeta -> view.noteEditor(jwtPayload, error = "Missing note metadata", textarea = markdownForm)
InvalidMeta -> view.noteEditor(jwtPayload, error = "Invalid note metadata", textarea = markdownForm)
is ValidationError -> view.noteEditor(jwtPayload, validationErrors = it.validationErrors, textarea = markdownForm)
is ValidationError -> view.noteEditor(jwtPayload,
validationErrors = it.validationErrors,
textarea = markdownForm)
}
Response(BAD_REQUEST).html(html)
},
@@ -90,6 +92,26 @@ class NoteController(
)
}
fun trash(request: Request, jwtPayload: JwtPayload): Response {
val currentPage = request.query("page")?.toIntOrNull()?.let(::abs) ?: 1
val tag = request.query("tag")
val (pages, notes) = noteService.paginatedNotes(jwtPayload.userId, currentPage, tag = tag, deleted = true)
return Response(OK).html(view.trash(jwtPayload, notes, currentPage, pages))
}
fun deleted(request: Request, jwtPayload: JwtPayload): Response {
val uuid = request.uuidPath() ?: return Response(NOT_FOUND)
return if (request.form("delete") != null)
if (noteService.delete(jwtPayload.userId, uuid))
Response.redirect("/notes/trash")
else
Response(NOT_FOUND)
else if (noteService.restore(jwtPayload.userId, uuid))
Response.redirect("/notes/$uuid")
else
Response(NOT_FOUND)
}
private fun Request.uuidPath(): UUID? {
val uuidPath = path("uuid")!!
return try {
+2
View File
@@ -48,10 +48,12 @@ class Router(
"/notes" bind GET to { protected(it, noteController::list) },
"/notes/new" bind GET to { protected(it, noteController::new) },
"/notes/new" bind POST to { protected(it, noteController::new) },
"/notes/trash" bind GET to { protected(it, noteController::trash) },
"/notes/{uuid}" bind GET to { protected(it, noteController::note) },
"/notes/{uuid}" bind POST to { protected(it, noteController::note) },
"/notes/{uuid}/edit" bind GET to { protected(it, noteController::edit) },
"/notes/{uuid}/edit" bind POST to { protected(it, noteController::edit) },
"/notes/deleted/{uuid}" bind POST to { protected(it, noteController::deleted) },
)
val routes = routes(
+45 -31
View File
@@ -1,9 +1,7 @@
package be.simplenotes.app.views
import be.simplenotes.app.utils.StaticFileResolver
import be.simplenotes.app.views.components.Alert
import be.simplenotes.app.views.components.alert
import be.simplenotes.app.views.components.submitButton
import be.simplenotes.app.views.components.*
import be.simplenotes.domain.model.PersistedNote
import be.simplenotes.domain.model.PersistedNoteMetadata
import be.simplenotes.domain.security.JwtPayload
@@ -53,41 +51,57 @@ class NoteView(staticFileResolver: StaticFileResolver) : View(staticFileResolver
notes: List<PersistedNoteMetadata>,
currentPage: Int,
numberOfPages: Int,
tag: String?
) =
renderPage(title = "Notes", jwtPayload = jwtPayload) {
div("container mx-auto p-4") {
div("flex justify-between mb-4") {
h1("text-2xl underline") { +"Notes" }
tag: String?,
) = renderPage(title = "Notes", jwtPayload = jwtPayload) {
div("container mx-auto p-4") {
div("flex justify-between mb-4") {
h1("text-2xl underline") { +"Notes" }
span {
a(
href = "/notes/trash",
classes = "underline font-semibold"
) { +"Trash" }
a(
href = "/notes/new",
classes = "btn btn-green"
classes = "ml-2 btn btn-green"
) { +"New" }
}
if (notes.isNotEmpty()) {
ul {
notes.forEach { (title, tags, _, uuid) ->
li("flex justify-between") {
a(classes = "text-blue-200 text-xl hover:underline", href = "/notes/$uuid") {
+title
}
span("space-x-2") {
tags.forEach {
a(href = "?tag=$it", classes = "tag") {
+"#$it"
}
}
}
}
}
}
if (numberOfPages > 1)
pagination(currentPage, numberOfPages, tag)
} else
span { +"No notes yet" } // FIXME if too far in pagination, it it displayed
}
if (notes.isNotEmpty())
noteTable(notes)
else
span {
if (numberOfPages > 1) +"You went too far"
else +"No notes yet"
}
if (numberOfPages > 1) pagination(currentPage, numberOfPages, tag)
}
}
fun trash(
jwtPayload: JwtPayload,
notes: List<PersistedNoteMetadata>,
currentPage: Int,
numberOfPages: Int
) = renderPage(title = "Notes", jwtPayload = jwtPayload) {
div("container mx-auto p-4") {
div("flex justify-between mb-4") {
h1("text-2xl underline") { +"Deleted notes" }
}
if (notes.isNotEmpty())
deletedNoteTable(notes)
else
span {
if (numberOfPages > 1) +"You went too far"
else +"No deleted notes"
}
if (numberOfPages > 1) pagination(currentPage, numberOfPages, null)
}
}
private fun DIV.pagination(currentPage: Int, numberOfPages: Int, tag: String?) {
val links = mutableListOf<Pair<String, String>>()
+2 -2
View File
@@ -14,7 +14,7 @@ abstract class View(private val staticFileResolver: StaticFileResolver) {
title: String,
description: String? = null,
jwtPayload: JwtPayload?,
body: BODY.() -> Unit = {},
body: MAIN.() -> Unit = {},
) = buildString {
appendLine("<!DOCTYPE html>")
appendHTML().html {
@@ -29,7 +29,7 @@ abstract class View(private val staticFileResolver: StaticFileResolver) {
}
body("bg-gray-900 text-white") {
navbar(jwtPayload)
main { this@body.body() }
main { body() }
}
}
}
@@ -0,0 +1,51 @@
package be.simplenotes.app.views.components
import be.simplenotes.domain.model.PersistedNoteMetadata
import kotlinx.html.*
import kotlinx.html.ButtonType.submit
import kotlinx.html.FormMethod.post
import kotlinx.html.ThScope.col
import org.http4k.core.Method
fun FlowContent.deletedNoteTable(notes: List<PersistedNoteMetadata>) = div("overflow-x-auto") {
table {
id = "notes"
thead {
tr {
th(col, "w-1/4") { +"Title" }
th(col, "w-1/4") { +"Updated" }
th(col, "w-1/4") { +"Tags" }
th(col, "w-1/4") { +"Restore" }
}
}
tbody {
notes.forEach { (title, tags, updatedAt, uuid) ->
tr {
td { +title }
td("text-center") { +updatedAt.toString() } // TODO: x time ago
td { tags(tags) }
td("text-center") {
form(classes = "inline", method = post, action = "/notes/deleted/$uuid") {
button(classes = "btn btn-red", type = submit, name = "delete") {
+"Delete permanently"
}
button(classes = "ml-2 btn btn-green", type = submit, name = "restore") {
+"Restore"
}
}
}
}
}
}
}
}
private fun FlowContent.tags(tags: List<String>) {
ul("inline flex flex-wrap justify-center") {
tags.forEach { tag ->
li("mx-2 my-1") {
span("tag") { +"#$tag" }
}
}
}
}
@@ -4,9 +4,14 @@ import be.simplenotes.domain.security.JwtPayload
import kotlinx.html.*
fun BODY.navbar(jwtPayload: JwtPayload?) {
nav("nav bg-teal-700 shadow-md flex items-center justify-between px-4") {
a(href = "/", classes = "text-2xl text-gray-100 font-bold") { +"SimpleNotes" }
nav {
id = "navbar"
a("/") {
id = "home"
+"SimpleNotes"
}
ul("space-x-2") {
id = "navigation"
if (jwtPayload != null) {
val links = listOf(
"/notes" to "Notes",
@@ -0,0 +1,39 @@
package be.simplenotes.app.views.components
import be.simplenotes.domain.model.PersistedNoteMetadata
import kotlinx.html.*
import kotlinx.html.ThScope.col
fun FlowContent.noteTable(notes: List<PersistedNoteMetadata>) = div("overflow-x-auto") {
table {
id = "notes"
thead {
tr {
th(col, "w-1/2") { +"Title" }
th(col, "w-1/4") { +"Updated" }
th(col, "w-1/4") { +"Tags" }
}
}
tbody {
notes.forEach { (title, tags, updatedAt, uuid) ->
tr {
td {
a(classes = "text-blue-200 font-semibold underline", href = "/notes/$uuid") { +title }
}
td("text-center") { +updatedAt.toString() } // TODO: x time ago
td { tags(tags) }
}
}
}
}
}
private fun FlowContent.tags(tags: List<String>) {
ul("inline flex flex-wrap justify-center") {
tags.forEach { tag ->
li("mx-2 my-1") {
a(href = "?tag=$tag", classes = "tag") { +"#$tag" }
}
}
}
}