Add image upload
This commit is contained in:
parent
997f374fe4
commit
67f24adfdf
24 changed files with 614 additions and 92 deletions
|
@ -5,9 +5,13 @@ import de.kif.frontend.launch
|
|||
import de.kif.frontend.repository.PostRepository
|
||||
import de.westermann.kobserve.event.subscribe
|
||||
import org.w3c.dom.HTMLElement
|
||||
import org.w3c.dom.HTMLInputElement
|
||||
import org.w3c.dom.HTMLTextAreaElement
|
||||
import org.w3c.dom.events.EventListener
|
||||
import org.w3c.dom.get
|
||||
import org.w3c.files.File
|
||||
import org.w3c.files.FileReader
|
||||
import org.w3c.files.get
|
||||
import kotlin.browser.document
|
||||
import kotlin.dom.clear
|
||||
|
||||
|
@ -50,6 +54,7 @@ fun initPosts() {
|
|||
}
|
||||
|
||||
fun initPostEdit() {
|
||||
// Content preview
|
||||
val textArea = document.getElementById("content") as HTMLTextAreaElement
|
||||
val preview = document.getElementsByClassName("post-edit-right")[0] as HTMLElement
|
||||
|
||||
|
@ -67,4 +72,45 @@ fun initPostEdit() {
|
|||
launch {
|
||||
preview.innerHTML = PostRepository.render(textArea.value)
|
||||
}
|
||||
|
||||
// Image preview
|
||||
val imageView = document.getElementsByClassName("post-edit-image")[0] as HTMLElement
|
||||
val uploadButton = document.getElementById("image") as HTMLInputElement
|
||||
val deleteSwitch = document.getElementById("image-delete") as? HTMLInputElement
|
||||
|
||||
var file: File? = null
|
||||
val original = imageView.style.backgroundImage
|
||||
|
||||
fun updateImage() {
|
||||
val deleteState = deleteSwitch?.checked == true
|
||||
val f = file
|
||||
|
||||
when {
|
||||
deleteState -> {
|
||||
imageView.removeAttribute("style")
|
||||
uploadButton.value = ""
|
||||
file = null
|
||||
}
|
||||
f == null -> imageView.style.backgroundImage = original
|
||||
else -> {
|
||||
val reader = FileReader()
|
||||
reader.onload = {
|
||||
val dataUrl = it.target.asDynamic().result as String
|
||||
imageView.style.backgroundImage = "url(\"$dataUrl\")"
|
||||
Unit
|
||||
}
|
||||
reader.readAsDataURL(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uploadButton.addEventListener("change", EventListener {
|
||||
val files = uploadButton.files ?: return@EventListener
|
||||
file = files[0]
|
||||
updateImage()
|
||||
})
|
||||
|
||||
deleteSwitch?.addEventListener("change", EventListener {
|
||||
updateImage()
|
||||
})
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package de.kif.frontend.views.overview
|
||||
|
||||
import de.kif.common.formatDate
|
||||
import de.kif.frontend.launch
|
||||
import de.kif.frontend.repository.PostRepository
|
||||
import de.westermann.kobserve.event.emit
|
||||
|
@ -25,37 +26,44 @@ class PostView(
|
|||
}
|
||||
|
||||
private val nameView: Link
|
||||
private val contentView: View
|
||||
private val contentView: HTMLElement
|
||||
private val footerView: HTMLElement
|
||||
private val imageView: HTMLElement
|
||||
|
||||
private fun reload() {
|
||||
launch {
|
||||
val p = PostRepository.get(postId) ?: return@launch
|
||||
|
||||
classList["post-no-image"] = p.image == null
|
||||
|
||||
nameView.text = p.name
|
||||
nameView.target = "/p/${p.url}"
|
||||
pinned = p.pinned
|
||||
|
||||
contentView.html.innerHTML = PostRepository.htmlByUrl(p.url)
|
||||
if (p.image == null) {
|
||||
imageView.removeAttribute("style")
|
||||
} else {
|
||||
imageView.style.backgroundImage = "url(\"/images/${p.image}\")"
|
||||
}
|
||||
|
||||
contentView.innerHTML = PostRepository.htmlByUrl(p.url)
|
||||
footerView.innerText = formatDate(p.createdAt)
|
||||
|
||||
emit(PostChangeEvent(postId))
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
nameView = Link.wrap(view.getByClassOrCreate("post-name"))
|
||||
|
||||
// val editHtml = view.getElementsByClassName("post-edit")[0]
|
||||
// editView = editHtml?.let { Link.wrap(it as HTMLAnchorElement) } ?: Link()
|
||||
val postColumn = view.getByClassOrCreate<HTMLElement>("post-column")
|
||||
val postColumnLeft = postColumn.getByClassOrCreate<HTMLElement>("post-column-left")
|
||||
val postColumnRight = postColumn.getByClassOrCreate<HTMLElement>("post-column-right")
|
||||
|
||||
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"
|
||||
}
|
||||
imageView = postColumnLeft.getByClassOrCreate("post-image", "figure")
|
||||
|
||||
contentView = postColumnRight.getByClassOrCreate("post-content")
|
||||
footerView = postColumnRight.getByClassOrCreate("post-footer")
|
||||
|
||||
PostRepository.onUpdate {
|
||||
if (it == postId) {
|
||||
|
@ -69,7 +77,7 @@ class PostView(
|
|||
|
||||
companion object {
|
||||
fun create(postId: Long): PostView {
|
||||
val div = document.createElement("div") as HTMLElement
|
||||
val div = createHtmlView<HTMLElement>()
|
||||
div.classList.add("post")
|
||||
div.dataset["id"] = postId.toString()
|
||||
return PostView(div).also(PostView::reload)
|
||||
|
@ -78,3 +86,16 @@ class PostView(
|
|||
}
|
||||
|
||||
data class PostChangeEvent(val id: Long)
|
||||
|
||||
inline fun <reified T : HTMLElement> HTMLElement.getByClassOrCreate(name: String, newTagName: String? = null): T {
|
||||
val v = this.getElementsByClassName(name)[0] as? T
|
||||
|
||||
if (v != null) {
|
||||
return v
|
||||
}
|
||||
|
||||
val h = createHtmlView<T>(newTagName)
|
||||
h.classList.add(name)
|
||||
this.appendChild(h)
|
||||
return h
|
||||
}
|
|
@ -11,6 +11,7 @@ $background-primary-color: #fff;
|
|||
$background-secondary-color: #fcfcfc;
|
||||
|
||||
$text-primary-color: #333;
|
||||
$text-secondary-color: rgba($text-primary-color, 0.5);
|
||||
|
||||
$primary-color: #B11D33;
|
||||
$primary-text-color: #fff;
|
||||
|
@ -18,7 +19,9 @@ $primary-text-color: #fff;
|
|||
$error-color: #D55225;
|
||||
$error-text-color: #fff;
|
||||
|
||||
$border-color: #888;
|
||||
$input-border-color: #888;
|
||||
$table-border-color: rgba($text-primary-color, 0.1);
|
||||
$table-header-color: rgba($text-primary-color, 0.06);
|
||||
|
||||
$transitionTime: 150ms;
|
||||
|
||||
|
@ -40,6 +43,10 @@ body, html {
|
|||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
& > *:last-child {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.no-select {
|
||||
|
@ -128,7 +135,7 @@ a {
|
|||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgba($primary-text-color, 0.1);
|
||||
background-color: $table-border-color;
|
||||
|
||||
&::after {
|
||||
background: $primary-color;
|
||||
|
@ -165,8 +172,8 @@ a {
|
|||
position: absolute;
|
||||
background-color: $background-secondary-color;
|
||||
z-index: 5;
|
||||
left: 1rem;
|
||||
right: 1rem;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
|
@ -183,7 +190,11 @@ a {
|
|||
line-height: 3rem;
|
||||
|
||||
&:after {
|
||||
bottom: 0.2rem;
|
||||
bottom: 0.55rem;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -220,7 +231,7 @@ a {
|
|||
}
|
||||
|
||||
.main {
|
||||
padding: 0 1rem;
|
||||
//padding: 0 1rem;
|
||||
}
|
||||
|
||||
.table-layout-search {
|
||||
|
@ -252,14 +263,14 @@ a {
|
|||
}
|
||||
|
||||
tr {
|
||||
border-top: solid 1px rgba($text-primary-color, 0.1);
|
||||
border-top: solid 1px $table-border-color;
|
||||
|
||||
&:nth-child(odd) {
|
||||
//background-color: rgba($text-primary-color, 0.01);
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
background-color: rgba($text-primary-color, 0.06);
|
||||
background-color: $table-header-color;
|
||||
height: 2.5rem;
|
||||
line-height: 2.5rem;
|
||||
}
|
||||
|
@ -269,7 +280,7 @@ a {
|
|||
line-height: 2rem;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba($text-primary-color, 0.06);
|
||||
background-color: $table-header-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -292,20 +303,20 @@ a {
|
|||
z-index: 1;
|
||||
background: $background-primary-color;
|
||||
width: 100%;
|
||||
border: solid 1px rgba($text-primary-color, 0.1);
|
||||
border: solid 1px $table-border-color;
|
||||
|
||||
span {
|
||||
padding: 0 0.5rem;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba($text-primary-color, 0.06);
|
||||
background-color: $table-header-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-control {
|
||||
border: solid 1px $border-color;
|
||||
border: solid 1px $input-border-color;
|
||||
outline: none;
|
||||
padding: 0 1rem;
|
||||
line-height: 2.5rem;
|
||||
|
@ -412,7 +423,7 @@ select:-moz-focusring {
|
|||
}
|
||||
|
||||
.form-btn {
|
||||
border: solid 1px $border-color;
|
||||
border: solid 1px $input-border-color;
|
||||
outline: none;
|
||||
padding: 0 1rem;
|
||||
line-height: 2rem;
|
||||
|
@ -588,7 +599,7 @@ form {
|
|||
.calendar[data-editable = "true"].edit {
|
||||
.calendar-table {
|
||||
width: calc(100% - 16rem);
|
||||
border-right: solid 1px rgba($text-primary-color, 0.1);
|
||||
border-right: solid 1px $table-border-color;
|
||||
}
|
||||
|
||||
.calendar-edit-main {
|
||||
|
@ -608,8 +619,8 @@ form {
|
|||
display: none;
|
||||
z-index: 10;
|
||||
|
||||
border: solid 1px $border-color;
|
||||
box-shadow: 0 0.1rem 0.2rem rgba($primary-text-color);
|
||||
border: solid 1px $input-border-color;
|
||||
box-shadow: 0 0.1rem 0.2rem $primary-text-color;
|
||||
|
||||
a {
|
||||
padding: 0.2rem;
|
||||
|
@ -707,14 +718,14 @@ form {
|
|||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
border-left: solid 1px rgba($text-primary-color, 0.1);
|
||||
border-left: solid 1px $table-border-color;
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.calendar-row {
|
||||
border-top: solid 1px rgba($text-primary-color, 0.1);
|
||||
border-top: solid 1px $table-border-color;
|
||||
line-height: 3rem;
|
||||
height: 3rem;
|
||||
|
||||
|
@ -727,12 +738,12 @@ form {
|
|||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
border-left: solid 1px rgba($text-primary-color, 0.1);
|
||||
border-left: solid 1px $table-border-color;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgba($text-primary-color, 0.06);
|
||||
background-color: $table-header-color;
|
||||
}
|
||||
|
||||
.calendar-entry {
|
||||
|
@ -749,7 +760,7 @@ form {
|
|||
width: 6rem;
|
||||
left: 0;
|
||||
text-align: center;
|
||||
border-right: solid 1px rgba($text-primary-color, 0.1);
|
||||
border-right: solid 1px $table-border-color;
|
||||
}
|
||||
|
||||
.calendar-link {
|
||||
|
@ -780,7 +791,7 @@ form {
|
|||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
border-left: solid 1px rgba($text-primary-color, 0.1);
|
||||
border-left: solid 1px $table-border-color;
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
@ -800,12 +811,12 @@ form {
|
|||
width: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
border-left: solid 1px rgba($text-primary-color, 0.1);
|
||||
border-left: solid 1px $table-border-color;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgba($text-primary-color, 0.06);
|
||||
background-color: $table-header-color;
|
||||
}
|
||||
|
||||
.calendar-entry {
|
||||
|
@ -826,7 +837,7 @@ form {
|
|||
}
|
||||
|
||||
&:nth-child(4n + 2) .calendar-cell::before {
|
||||
border-top: solid 1px rgba($text-primary-color, 0.1);
|
||||
border-top: solid 1px $table-border-color;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -939,13 +950,13 @@ form {
|
|||
display: none;
|
||||
|
||||
background: $background-primary-color;
|
||||
border: solid 1px rgba($text-primary-color, 0.1);
|
||||
border: solid 1px $table-border-color;
|
||||
|
||||
span {
|
||||
padding: 0 0.5rem;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba($text-primary-color, 0.06);
|
||||
background-color: $table-header-color;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -956,11 +967,13 @@ form {
|
|||
|
||||
.overview {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.overview-main {
|
||||
flex-grow: 4;
|
||||
margin-right: 1rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.overview-side {
|
||||
|
@ -985,6 +998,7 @@ form {
|
|||
font-size: 1.2rem;
|
||||
color: $primary-color;
|
||||
line-height: 2rem;
|
||||
padding: 0 1rem;
|
||||
|
||||
&:empty::before {
|
||||
display: block;
|
||||
|
@ -999,11 +1013,30 @@ form {
|
|||
.post-edit {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
right: 1rem;
|
||||
line-height: 2rem;
|
||||
}
|
||||
|
||||
.post-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.post-image {
|
||||
width: 100%;
|
||||
margin: 0.4rem 0 0;
|
||||
padding-top: 75%;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.post-no-image .post-column-left {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.post-content {
|
||||
padding: 0 1rem;
|
||||
|
||||
h1, h2, h3, h4, h5, h6, p {
|
||||
margin: 0.7rem 0;
|
||||
padding: 0;
|
||||
|
@ -1039,11 +1072,11 @@ form {
|
|||
}
|
||||
|
||||
td {
|
||||
border-top: solid 1px rgba($text-primary-color, 0.1);
|
||||
border-top: solid 1px $table-border-color;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: rgba($text-primary-color, 0.06);
|
||||
background-color: $table-header-color;
|
||||
}
|
||||
|
||||
td, th {
|
||||
|
@ -1051,6 +1084,14 @@ form {
|
|||
}
|
||||
}
|
||||
|
||||
.post-footer {
|
||||
color: $text-secondary-color;
|
||||
padding: 0 1rem;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 400;
|
||||
margin-top: -0.3rem;
|
||||
}
|
||||
|
||||
.post-edit-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -1060,29 +1101,84 @@ form {
|
|||
margin-left: 0;
|
||||
}
|
||||
|
||||
.post-edit-image-box {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
margin-top: 0.4rem;
|
||||
|
||||
& > div:nth-child(1) {
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
& > div:nth-child(2) {
|
||||
width: 80%;
|
||||
padding-left: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.post-edit-image {
|
||||
width: 100%;
|
||||
padding-top: 75%;
|
||||
border: solid 1px $input-border-color;
|
||||
margin: 0;
|
||||
background-color: $background-primary-color;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.overview-post {
|
||||
margin-bottom: 2rem;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
display: block;
|
||||
bottom: -1rem;
|
||||
height: 1px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: $table-border-color;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.post-column {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.overview-post::after {
|
||||
left: 1rem;
|
||||
right: 1rem;
|
||||
}
|
||||
|
||||
.post-content, .post-footer {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.overview-post {
|
||||
.post-column {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.post-column-left {
|
||||
width: 30%;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.post-column-right {
|
||||
width: 70%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
.post-edit-container {
|
||||
flex-direction: row;
|
||||
}
|
||||
.post-edit-right {
|
||||
margin-left: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
.overview-post {
|
||||
max-height: 20rem;
|
||||
overflow: hidden;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
display: block;
|
||||
height: 1.5rem;
|
||||
top: 18.5rem;
|
||||
width: 100%;
|
||||
background: linear-gradient(0deg, $background-primary-color, transparent);
|
||||
.overview {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue