Add post auto update
This commit is contained in:
parent
2be6c7c819
commit
74c297263e
13 changed files with 354 additions and 40 deletions
|
@ -1,8 +1,10 @@
|
|||
package de.kif.common.model
|
||||
|
||||
import de.kif.common.SearchElement
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlin.random.Random
|
||||
|
||||
@Serializable
|
||||
data class Post(
|
||||
override val id: Long? = null,
|
||||
val name: String,
|
||||
|
|
|
@ -61,7 +61,8 @@ class WebSocketClient() {
|
|||
ScheduleRepository.handler,
|
||||
TrackRepository.handler,
|
||||
UserRepository.handler,
|
||||
WorkGroupRepository.handler
|
||||
WorkGroupRepository.handler,
|
||||
PostRepository.handler
|
||||
)
|
||||
|
||||
init {
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
package de.kif.frontend
|
||||
|
||||
import de.kif.frontend.views.calendar.initCalendar
|
||||
import de.kif.frontend.views.initTableLayout
|
||||
import de.kif.frontend.views.table.initTableLayout
|
||||
import de.kif.frontend.views.initWorkGroupConstraints
|
||||
import de.kif.frontend.views.overview.initOverviewMain
|
||||
import de.kif.frontend.views.overview.initPosts
|
||||
import de.westermann.kwebview.components.init
|
||||
import kotlin.browser.document
|
||||
|
||||
|
@ -18,4 +20,10 @@ fun main() = init {
|
|||
if (document.getElementsByClassName("work-group-constraints").length > 0) {
|
||||
initWorkGroupConstraints()
|
||||
}
|
||||
if (document.getElementsByClassName("overview-main").length > 0) {
|
||||
initOverviewMain()
|
||||
}
|
||||
if (document.getElementsByClassName("post").length > 0) {
|
||||
initPosts()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,6 +82,30 @@ suspend fun repositoryPost(
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun repositoryRawGet(
|
||||
url: String
|
||||
): String {
|
||||
console.log("GET: $url")
|
||||
val promise = Promise<String> { resolve, reject ->
|
||||
val xhttp = XMLHttpRequest()
|
||||
|
||||
xhttp.onreadystatechange = {
|
||||
if (xhttp.readyState == 4.toShort()) {
|
||||
if (xhttp.status == 200.toShort() || xhttp.status == 304.toShort()) {
|
||||
resolve(xhttp.responseText)
|
||||
} else {
|
||||
reject(NoSuchElementException("${xhttp.status}: ${xhttp.statusText}"))
|
||||
}
|
||||
}
|
||||
}
|
||||
xhttp.open("GET", url, true)
|
||||
|
||||
xhttp.send()
|
||||
}
|
||||
|
||||
return promise.await()
|
||||
}
|
||||
|
||||
suspend fun <T> Promise<T>.await(): T = suspendCoroutine { cont ->
|
||||
then({ cont.resume(it) }, { cont.resumeWithException(it) })
|
||||
}
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
package de.kif.frontend.repository
|
||||
|
||||
import de.kif.common.Message
|
||||
import de.kif.common.Repository
|
||||
import de.kif.common.RepositoryType
|
||||
import de.kif.common.model.Post
|
||||
import de.kif.frontend.MessageHandler
|
||||
import de.westermann.kobserve.event.EventHandler
|
||||
import kotlinx.serialization.DynamicObjectParser
|
||||
import kotlinx.serialization.list
|
||||
|
||||
object PostRepository : Repository<Post> {
|
||||
|
||||
override val onCreate = EventHandler<Long>()
|
||||
override val onUpdate = EventHandler<Long>()
|
||||
override val onDelete = EventHandler<Long>()
|
||||
|
||||
private val parser = DynamicObjectParser()
|
||||
|
||||
override suspend fun get(id: Long): Post? {
|
||||
val json = repositoryGet("/api/post/$id") ?: return null
|
||||
return parser.parse(json, Post.serializer())
|
||||
}
|
||||
|
||||
override suspend fun create(model: Post): Long {
|
||||
return repositoryPost("/api/posts", Message.json.stringify(Post.serializer(), model))
|
||||
?: throw IllegalStateException("Cannot create model!")
|
||||
}
|
||||
|
||||
override suspend fun update(model: Post) {
|
||||
if (model.id == null) throw IllegalStateException("Cannot update model which was not created!")
|
||||
repositoryPost("/api/post/${model.id}", Message.json.stringify(Post.serializer(), model))
|
||||
}
|
||||
|
||||
override suspend fun delete(id: Long) {
|
||||
repositoryPost("/api/post/$id/delete")
|
||||
}
|
||||
|
||||
override suspend fun all(): List<Post> {
|
||||
val json = repositoryGet("/api/posts") ?: return emptyList()
|
||||
return parser.parse(json, Post.serializer().list)
|
||||
}
|
||||
|
||||
suspend fun htmlByUrl(url: String): String {
|
||||
return repositoryRawGet("/api/p/$url")
|
||||
}
|
||||
|
||||
val handler = object : MessageHandler(RepositoryType.POST) {
|
||||
|
||||
override fun onCreate(id: Long) = onCreate.emit(id)
|
||||
|
||||
override fun onUpdate(id: Long) = onUpdate.emit(id)
|
||||
|
||||
override fun onDelete(id: Long) = onDelete.emit(id)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package de.kif.frontend.views.overview
|
||||
|
||||
import de.kif.frontend.iterator
|
||||
import de.kif.frontend.repository.PostRepository
|
||||
import org.w3c.dom.HTMLElement
|
||||
import org.w3c.dom.get
|
||||
import kotlin.browser.document
|
||||
|
||||
fun initOverviewMain() {
|
||||
val main = document.getElementsByClassName("overview-main")[0] as HTMLElement
|
||||
|
||||
PostRepository.onCreate {
|
||||
val post = PostView.create(it)
|
||||
post.classList += "overview-post"
|
||||
|
||||
val first = main.firstElementChild as? HTMLElement
|
||||
|
||||
if (first == null) {
|
||||
main.appendChild(post.html)
|
||||
return@onCreate
|
||||
}
|
||||
if (first.classList.contains("post")) {
|
||||
main.insertBefore(post.html, first)
|
||||
return@onCreate
|
||||
}
|
||||
|
||||
val next = first.nextElementSibling as? HTMLElement
|
||||
if (next == null) {
|
||||
main.appendChild(post.html)
|
||||
} else {
|
||||
main.insertBefore(post.html, next)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun initPosts() {
|
||||
val postList = document.getElementsByClassName("post")
|
||||
for (post in postList) {
|
||||
PostView(post)
|
||||
}
|
||||
}
|
69
src/jsMain/kotlin/de/kif/frontend/views/overview/PostView.kt
Normal file
69
src/jsMain/kotlin/de/kif/frontend/views/overview/PostView.kt
Normal file
|
@ -0,0 +1,69 @@
|
|||
package de.kif.frontend.views.overview
|
||||
|
||||
import de.kif.common.model.Post
|
||||
import de.kif.frontend.launch
|
||||
import de.kif.frontend.repository.PostRepository
|
||||
import de.westermann.kwebview.View
|
||||
import de.westermann.kwebview.components.Link
|
||||
import de.westermann.kwebview.createHtmlView
|
||||
import org.w3c.dom.HTMLAnchorElement
|
||||
import org.w3c.dom.HTMLElement
|
||||
import org.w3c.dom.get
|
||||
import org.w3c.dom.set
|
||||
import kotlin.browser.document
|
||||
|
||||
class PostView(
|
||||
view: HTMLElement
|
||||
) : View(view) {
|
||||
|
||||
private var postId = dataset["id"]?.toLongOrNull() ?: -1
|
||||
|
||||
private val nameView: Link
|
||||
private val contentView: View
|
||||
|
||||
private fun reload() {
|
||||
launch {
|
||||
val p = PostRepository.get(postId) ?: return@launch
|
||||
|
||||
nameView.text = p.name
|
||||
nameView.target = "/p/${p.url}"
|
||||
|
||||
contentView.html.innerHTML = PostRepository.htmlByUrl(p.url)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
val nameHtml = view.getElementsByClassName("post-name")[0]
|
||||
nameView = nameHtml?.let { Link.wrap(it as HTMLAnchorElement) } ?: Link().also {
|
||||
html.appendChild(it.html)
|
||||
it.classList += "post-name"
|
||||
}
|
||||
|
||||
// val editHtml = view.getElementsByClassName("post-edit")[0]
|
||||
// editView = editHtml?.let { Link.wrap(it as HTMLAnchorElement) } ?: Link()
|
||||
|
||||
val contentHtml = view.getElementsByClassName("post-content")[0]
|
||||
contentView = contentHtml?.let { wrap(it as HTMLElement) } ?: wrap(createHtmlView()).also {
|
||||
html.appendChild(it.html)
|
||||
it.classList += "post-content"
|
||||
}
|
||||
|
||||
PostRepository.onUpdate {
|
||||
if (it == postId) {
|
||||
reload()
|
||||
}
|
||||
}
|
||||
PostRepository.onDelete {
|
||||
html.remove()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun create(postId: Long): PostView {
|
||||
val div = document.createElement("div") as HTMLElement
|
||||
div.classList.add("post")
|
||||
div.dataset["id"] = postId.toString()
|
||||
return PostView(div).also(PostView::reload)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +1,6 @@
|
|||
package de.kif.frontend.views
|
||||
package de.kif.frontend.views.table
|
||||
|
||||
import de.kif.frontend.iterator
|
||||
import de.kif.frontend.views.table.RoomTableLine
|
||||
import de.kif.frontend.views.table.TableLine
|
||||
import de.kif.frontend.views.table.WorkGroupTableLine
|
||||
import de.westermann.kwebview.components.InputView
|
||||
import org.w3c.dom.HTMLFormElement
|
||||
import org.w3c.dom.HTMLInputElement
|
|
@ -11,7 +11,11 @@ import org.w3c.dom.HTMLAnchorElement
|
|||
*
|
||||
* @author lars
|
||||
*/
|
||||
class Link(target: String) : ViewCollection<View>(createHtmlView<HTMLAnchorElement>("a")) {
|
||||
class Link(view: HTMLAnchorElement = createHtmlView()) : View(view) {
|
||||
|
||||
constructor(target: String, view: HTMLAnchorElement = createHtmlView()): this(view) {
|
||||
this.target = target
|
||||
}
|
||||
|
||||
override val html = super.html as HTMLAnchorElement
|
||||
|
||||
|
@ -27,8 +31,8 @@ class Link(target: String) : ViewCollection<View>(createHtmlView<HTMLAnchorEleme
|
|||
html.href = value
|
||||
}
|
||||
|
||||
init {
|
||||
this.target = target
|
||||
companion object {
|
||||
fun wrap(view: HTMLAnchorElement) = Link(view)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1036,6 +1036,7 @@ form {
|
|||
.overview-post {
|
||||
max-height: 20rem;
|
||||
overflow: hidden;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
|
|
|
@ -56,6 +56,7 @@ fun Application.main() {
|
|||
userApi()
|
||||
workGroupApi()
|
||||
constraintsApi()
|
||||
postApi()
|
||||
|
||||
// Web socket push notifications
|
||||
pushService()
|
||||
|
|
|
@ -20,6 +20,29 @@ import io.ktor.routing.post
|
|||
import io.ktor.util.toMap
|
||||
import kotlinx.html.*
|
||||
|
||||
fun DIV.createPost(post: Post, editable: Boolean = false, additionalClasses: String = "") {
|
||||
var classes = "post"
|
||||
if (additionalClasses.isNotBlank()) {
|
||||
classes += " $additionalClasses"
|
||||
}
|
||||
div(classes) {
|
||||
attributes["data-id"] = post.id.toString()
|
||||
a("/p/${post.url}", classes = "post-name") {
|
||||
+post.name
|
||||
}
|
||||
if (editable) {
|
||||
a("/post/${post.id}", classes = "post-edit") {
|
||||
i("material-icons") { +"edit" }
|
||||
}
|
||||
}
|
||||
div("post-content") {
|
||||
unsafe {
|
||||
raw(markdownToHtml(post.content))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Route.overview() {
|
||||
get("") {
|
||||
val user = isAuthenticated(Permission.POST)
|
||||
|
@ -44,21 +67,7 @@ fun Route.overview() {
|
|||
}
|
||||
|
||||
for (post in postList) {
|
||||
div("overview-post post") {
|
||||
a("/p/${post.url}", classes="post-name") {
|
||||
+post.name
|
||||
}
|
||||
if (editable) {
|
||||
a("/post/${post.id}", classes = "post-edit") {
|
||||
i("material-icons") { +"edit" }
|
||||
}
|
||||
}
|
||||
div("post-content") {
|
||||
unsafe {
|
||||
raw(markdownToHtml(post.content))
|
||||
}
|
||||
}
|
||||
}
|
||||
createPost(post, editable, "overview-post")
|
||||
}
|
||||
}
|
||||
div("overview-side") {
|
||||
|
@ -109,22 +118,8 @@ fun Route.overview() {
|
|||
}
|
||||
content {
|
||||
div("overview") {
|
||||
div("post") {
|
||||
span("post-name") {
|
||||
+post.name
|
||||
}
|
||||
if (editable) {
|
||||
a("/post/${post.id}", classes = "post-edit") {
|
||||
i("material-icons") { +"edit" }
|
||||
}
|
||||
}
|
||||
div("post-content") {
|
||||
unsafe {
|
||||
raw(markdownToHtml(post.content))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
createPost(post, editable)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
115
src/jvmMain/kotlin/de/kif/backend/route/api/Post.kt
Normal file
115
src/jvmMain/kotlin/de/kif/backend/route/api/Post.kt
Normal file
|
@ -0,0 +1,115 @@
|
|||
package de.kif.backend.route.api
|
||||
|
||||
import de.kif.backend.authenticate
|
||||
import de.kif.backend.repository.PostRepository
|
||||
import de.kif.backend.util.markdownToHtml
|
||||
import de.kif.common.model.Permission
|
||||
import de.kif.common.model.Post
|
||||
import io.ktor.application.call
|
||||
import io.ktor.html.respondHtml
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.request.receive
|
||||
import io.ktor.response.respond
|
||||
import io.ktor.routing.Route
|
||||
import io.ktor.routing.get
|
||||
import io.ktor.routing.post
|
||||
import kotlinx.html.unsafe
|
||||
|
||||
fun Route.postApi() {
|
||||
get("/api/posts") {
|
||||
try {
|
||||
val posts = PostRepository.all()
|
||||
call.success(posts)
|
||||
} catch (_: Exception) {
|
||||
call.error(HttpStatusCode.InternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
post("/api/posts") {
|
||||
try {
|
||||
authenticate(Permission.POST) {
|
||||
val post = call.receive<Post>()
|
||||
|
||||
val id = PostRepository.create(post)
|
||||
|
||||
call.success(mapOf("id" to id))
|
||||
} onFailure {
|
||||
call.error(HttpStatusCode.Unauthorized)
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
call.error(HttpStatusCode.InternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
get("/api/post/{id}") {
|
||||
try {
|
||||
val id = call.parameters["id"]?.toLongOrNull()
|
||||
val post = id?.let { PostRepository.get(it) }
|
||||
|
||||
if (post != null) {
|
||||
call.success(post)
|
||||
} else {
|
||||
call.error(HttpStatusCode.NotFound)
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
call.error(HttpStatusCode.InternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
post("/api/post/{id}") {
|
||||
try {
|
||||
authenticate {
|
||||
val id = call.parameters["id"]?.toLongOrNull()
|
||||
val post = call.receive<Post>().copy(id = id)
|
||||
|
||||
if (post.id != null) {
|
||||
PostRepository.update(post)
|
||||
call.success()
|
||||
} else {
|
||||
call.error(HttpStatusCode.NotFound)
|
||||
}
|
||||
} onFailure {
|
||||
call.error(HttpStatusCode.Unauthorized)
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
call.error(HttpStatusCode.InternalServerError)
|
||||
}
|
||||
}
|
||||
post("/api/post/{id}/delete") {
|
||||
try {
|
||||
authenticate {
|
||||
val id = call.parameters["id"]?.toLongOrNull()
|
||||
|
||||
if (id != null) {
|
||||
PostRepository.delete(id)
|
||||
call.success()
|
||||
} else {
|
||||
call.error(HttpStatusCode.NotFound)
|
||||
}
|
||||
} onFailure {
|
||||
call.error(HttpStatusCode.Unauthorized)
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
call.error(HttpStatusCode.InternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
get("/api/p/{url}") {
|
||||
try {
|
||||
val id = call.parameters["url"]
|
||||
val post = id?.let { PostRepository.getByUrl(it) }
|
||||
|
||||
if (post != null) {
|
||||
call.respondHtml(HttpStatusCode.OK) {
|
||||
unsafe {
|
||||
raw(markdownToHtml(post.content))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
call.error(HttpStatusCode.NotFound)
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
call.error(HttpStatusCode.InternalServerError)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue