Add overview
This commit is contained in:
parent
e88db9c75c
commit
c28317aefd
|
@ -22,7 +22,8 @@ version "0.1.0"
|
|||
|
||||
repositories {
|
||||
jcenter()
|
||||
maven { url "http://dl.bintray.com/kotlin/ktor" }
|
||||
maven { url "https://dl.bintray.com/kotlin/ktor" }
|
||||
maven { url "https://dl.bintray.com/jetbrains/markdown" }
|
||||
maven { url "https://kotlin.bintray.com/kotlinx" }
|
||||
maven { url "https://kotlin.bintray.com/kotlin-js-wrappers" }
|
||||
mavenCentral()
|
||||
|
@ -87,6 +88,9 @@ kotlin {
|
|||
|
||||
implementation "de.westermann:KObserve-jvm:$observable_version"
|
||||
|
||||
//api 'org.jetbrains:markdown:0.1.28'
|
||||
//implementation 'com.atlassian.commonmark:commonmark:0.12.1'
|
||||
implementation 'com.vladsch.flexmark:flexmark-all:0.42.10'
|
||||
api 'io.github.microutils:kotlin-logging:1.6.23'
|
||||
api 'ch.qos.logback:logback-classic:1.2.3'
|
||||
api 'org.fusesource.jansi:jansi:1.8'
|
||||
|
|
|
@ -1 +1 @@
|
|||
kotlin.code.style=official
|
||||
kotlin.code.style=official
|
||||
|
|
60
src/commonMain/kotlin/de/kif/common/CacheRepository.kt
Normal file
60
src/commonMain/kotlin/de/kif/common/CacheRepository.kt
Normal file
|
@ -0,0 +1,60 @@
|
|||
package de.kif.common
|
||||
|
||||
import de.kif.common.model.Model
|
||||
import de.westermann.kobserve.event.EventHandler
|
||||
|
||||
|
||||
class CacheRepository<T : Model>(val repository: Repository<T>) : Repository<T> {
|
||||
|
||||
override val onCreate = EventHandler<Long>()
|
||||
override val onUpdate = EventHandler<Long>()
|
||||
override val onDelete = EventHandler<Long>()
|
||||
|
||||
var cache: Map<Long, T> = emptyMap()
|
||||
var cacheComplete: Boolean = false
|
||||
|
||||
override suspend fun get(id: Long): T? {
|
||||
return if (id in cache) {
|
||||
cache[id]
|
||||
} else {
|
||||
val element = repository.get(id)
|
||||
|
||||
if (element != null) {
|
||||
cache = cache + (id to element)
|
||||
}
|
||||
|
||||
element
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun create(model: T): Long {
|
||||
return repository.create(model)
|
||||
}
|
||||
|
||||
override suspend fun update(model: T) {
|
||||
val id = model.id
|
||||
if (id != null) {
|
||||
cache = cache - id
|
||||
}
|
||||
repository.update(model)
|
||||
}
|
||||
|
||||
override suspend fun delete(id: Long) {
|
||||
cache = cache - id
|
||||
repository.delete(id)
|
||||
}
|
||||
|
||||
override suspend fun all(): List<T> {
|
||||
if (cacheComplete) {
|
||||
return cache.values.toList()
|
||||
} else {
|
||||
val all = repository.all()
|
||||
|
||||
cache = all.associateBy { it.id!! }
|
||||
cacheComplete = true
|
||||
|
||||
return all
|
||||
}
|
||||
}
|
||||
|
||||
}
|
86
src/commonMain/kotlin/de/kif/common/ConstraintChecking.kt
Normal file
86
src/commonMain/kotlin/de/kif/common/ConstraintChecking.kt
Normal file
|
@ -0,0 +1,86 @@
|
|||
package de.kif.common
|
||||
|
||||
import de.kif.common.model.ConstraintType
|
||||
import de.kif.common.model.Schedule
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ConstraintError(
|
||||
val reason: String = ""
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ConstraintMap(
|
||||
val map: List<Pair<Long, List<ConstraintError>>>
|
||||
)
|
||||
|
||||
fun checkConstraints(
|
||||
check: List<Schedule>,
|
||||
against: List<Schedule>
|
||||
): ConstraintMap {
|
||||
val map = mutableMapOf<Long, List<ConstraintError>>()
|
||||
|
||||
for (schedule in check) {
|
||||
if (schedule.id == null) continue
|
||||
val errors = mutableListOf<ConstraintError>()
|
||||
|
||||
if (schedule.workGroup.projector && !schedule.room.projector) {
|
||||
errors += ConstraintError("Work group requires projector, but room does not have one!")
|
||||
}
|
||||
if (schedule.workGroup.internet && !schedule.room.internet) {
|
||||
errors += ConstraintError("Work group requires internet, but room does not have one!")
|
||||
}
|
||||
if (schedule.workGroup.whiteboard && !schedule.room.whiteboard) {
|
||||
errors += ConstraintError("Work group requires whiteboard, but room does not have one!")
|
||||
}
|
||||
if (schedule.workGroup.blackboard && !schedule.room.blackboard) {
|
||||
errors += ConstraintError("Work group requires blackboard, but room does not have one!")
|
||||
}
|
||||
if (schedule.workGroup.accessible && !schedule.room.accessible) {
|
||||
errors += ConstraintError("Work group requires accessible, but room does not have one!")
|
||||
}
|
||||
|
||||
for (constraint in schedule.workGroup.constraints) {
|
||||
when (constraint.type) {
|
||||
ConstraintType.OnlyOnDay -> {
|
||||
if (constraint.number.toInt() != schedule.day) {
|
||||
errors += ConstraintError("Work group requires day ${constraint.number}, but is on ${schedule.day}!")
|
||||
}
|
||||
}
|
||||
ConstraintType.OnlyAfterTime -> {
|
||||
if (constraint.number.toInt() > schedule.time) {
|
||||
errors += ConstraintError("Work group requires time after ${constraint.number}, but is on ${schedule.time}!")
|
||||
}
|
||||
}
|
||||
ConstraintType.NotAtSameTime -> {
|
||||
val start = schedule.getAbsoluteStartTime()
|
||||
val end = schedule.getAbsoluteEndTime()
|
||||
for (s in against) {
|
||||
if (
|
||||
s.workGroup.id == constraint.number &&
|
||||
start <= s.getAbsoluteEndTime() &&
|
||||
s.getAbsoluteStartTime() <= end
|
||||
) {
|
||||
errors += ConstraintError("Work group requires not same time with ${s.workGroup.name}!")
|
||||
}
|
||||
}
|
||||
}
|
||||
ConstraintType.OnlyAfterWorkGroup -> {
|
||||
val start = schedule.getAbsoluteStartTime()
|
||||
for (s in against) {
|
||||
if (
|
||||
s.workGroup.id == constraint.number &&
|
||||
s.getAbsoluteEndTime() > start
|
||||
) {
|
||||
errors += ConstraintError("Work group requires after ${s.workGroup.name}!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
map[schedule.id] = errors
|
||||
}
|
||||
|
||||
return ConstraintMap(map.map { it.toPair() })
|
||||
}
|
|
@ -31,5 +31,5 @@ enum class MessageType {
|
|||
}
|
||||
|
||||
enum class RepositoryType {
|
||||
ROOM, SCHEDULE, TRACK, USER, WORK_GROUP
|
||||
ROOM, SCHEDULE, TRACK, USER, WORK_GROUP, POST
|
||||
}
|
||||
|
|
13
src/commonMain/kotlin/de/kif/common/model/Constraint.kt
Normal file
13
src/commonMain/kotlin/de/kif/common/model/Constraint.kt
Normal file
|
@ -0,0 +1,13 @@
|
|||
package de.kif.common.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Constraint(
|
||||
val type: ConstraintType,
|
||||
val number: Long
|
||||
)
|
||||
|
||||
enum class ConstraintType {
|
||||
OnlyOnDay, OnlyAfterTime, NotAtSameTime, OnlyAfterWorkGroup
|
||||
}
|
|
@ -3,5 +3,6 @@ package de.kif.common.model
|
|||
import de.kif.common.SearchElement
|
||||
|
||||
interface Model {
|
||||
val id : Long?
|
||||
fun createSearch(): SearchElement
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
package de.kif.common.model
|
||||
|
||||
enum class Permission {
|
||||
USER, SCHEDULE, WORK_GROUP, ROOM, PERSON, ADMIN
|
||||
USER, SCHEDULE, WORK_GROUP, ROOM, POST, ADMIN
|
||||
}
|
||||
|
|
29
src/commonMain/kotlin/de/kif/common/model/Post.kt
Normal file
29
src/commonMain/kotlin/de/kif/common/model/Post.kt
Normal file
|
@ -0,0 +1,29 @@
|
|||
package de.kif.common.model
|
||||
|
||||
import de.kif.common.SearchElement
|
||||
import kotlin.random.Random
|
||||
|
||||
data class Post(
|
||||
override val id: Long? = null,
|
||||
val name: String,
|
||||
val content: String,
|
||||
val url: String
|
||||
) : Model {
|
||||
|
||||
override fun createSearch() = SearchElement(
|
||||
mapOf(
|
||||
"name" to name
|
||||
)
|
||||
)
|
||||
|
||||
companion object {
|
||||
private const val chars = "abcdefghijklmnopqrstuvwxyz"
|
||||
private const val length = 32
|
||||
fun generateUrl() = (0 until length).asSequence()
|
||||
.map { Random.nextInt(chars.length) }
|
||||
.map { chars[it] }
|
||||
.map {
|
||||
if (Random.nextBoolean()) it else it.toUpperCase()
|
||||
}.joinToString("")
|
||||
}
|
||||
}
|
|
@ -5,10 +5,14 @@ import kotlinx.serialization.Serializable
|
|||
|
||||
@Serializable
|
||||
data class Room(
|
||||
val id: Long? = null,
|
||||
override val id: Long? = null,
|
||||
val name: String,
|
||||
val places: Int,
|
||||
val projector: Boolean
|
||||
val projector: Boolean,
|
||||
val internet: Boolean,
|
||||
val whiteboard: Boolean,
|
||||
val blackboard: Boolean,
|
||||
val accessible: Boolean
|
||||
) : Model {
|
||||
|
||||
override fun createSearch() = SearchElement(
|
||||
|
|
|
@ -5,7 +5,7 @@ import kotlinx.serialization.Serializable
|
|||
|
||||
@Serializable
|
||||
data class Schedule(
|
||||
val id: Long?,
|
||||
override val id: Long?,
|
||||
val workGroup: WorkGroup,
|
||||
val room: Room,
|
||||
val day: Int,
|
||||
|
@ -22,4 +22,7 @@ data class Schedule(
|
|||
"day" to day.toDouble()
|
||||
)
|
||||
)
|
||||
|
||||
fun getAbsoluteStartTime(): Int = day * 60 * 24 + time
|
||||
fun getAbsoluteEndTime(): Int = getAbsoluteStartTime() + workGroup.length
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import kotlinx.serialization.Serializable
|
|||
|
||||
@Serializable
|
||||
data class Track(
|
||||
val id: Long?,
|
||||
override val id: Long?,
|
||||
val name: String,
|
||||
val color: Color
|
||||
) : Model {
|
||||
|
|
|
@ -5,7 +5,7 @@ import kotlinx.serialization.Serializable
|
|||
|
||||
@Serializable
|
||||
data class User(
|
||||
val id: Long?,
|
||||
override val id: Long?,
|
||||
val username: String,
|
||||
val password: String,
|
||||
val permissions: Set<Permission>
|
||||
|
|
|
@ -5,14 +5,19 @@ import kotlinx.serialization.Serializable
|
|||
|
||||
@Serializable
|
||||
data class WorkGroup(
|
||||
val id: Long?,
|
||||
override val id: Long?,
|
||||
val name: String,
|
||||
val interested: Int,
|
||||
val track: Track?,
|
||||
val projector: Boolean,
|
||||
val resolution: Boolean,
|
||||
val internet: Boolean,
|
||||
val whiteboard: Boolean,
|
||||
val blackboard: Boolean,
|
||||
val accessible: Boolean,
|
||||
val length: Int,
|
||||
val language: Language
|
||||
val language: Language,
|
||||
val constraints: List<Constraint>
|
||||
) : Model {
|
||||
|
||||
override fun createSearch() = SearchElement(
|
||||
|
|
|
@ -2,6 +2,7 @@ package de.kif.frontend
|
|||
|
||||
import de.kif.frontend.views.calendar.initCalendar
|
||||
import de.kif.frontend.views.initTableLayout
|
||||
import de.kif.frontend.views.initWorkGroupConstraints
|
||||
import de.westermann.kwebview.components.init
|
||||
import kotlin.browser.document
|
||||
|
||||
|
@ -14,4 +15,7 @@ fun main() = init {
|
|||
if (document.getElementsByClassName("table-layout").length > 0) {
|
||||
initTableLayout()
|
||||
}
|
||||
if (document.getElementsByClassName("work-group-constraints").length > 0) {
|
||||
initWorkGroupConstraints()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package de.kif.frontend.repository
|
||||
|
||||
import de.kif.common.ConstraintMap
|
||||
import de.kif.common.Message
|
||||
import de.kif.common.Repository
|
||||
import de.kif.common.RepositoryType
|
||||
|
@ -49,4 +50,14 @@ object ScheduleRepository : Repository<Schedule> {
|
|||
|
||||
override fun onDelete(id: Long) = onDelete.emit(id)
|
||||
}
|
||||
|
||||
suspend fun checkConstraints(): ConstraintMap {
|
||||
val json = repositoryGet("/api/constraints")
|
||||
return parser.parse(json, ConstraintMap.serializer())
|
||||
}
|
||||
|
||||
suspend fun checkConstraintsFor(schedule: Schedule): ConstraintMap {
|
||||
val json = repositoryGet("/api/constraint/${schedule.id}")
|
||||
return parser.parse(json, ConstraintMap.serializer())
|
||||
}
|
||||
}
|
||||
|
|
118
src/jsMain/kotlin/de/kif/frontend/views/WorkGroupConstraints.kt
Normal file
118
src/jsMain/kotlin/de/kif/frontend/views/WorkGroupConstraints.kt
Normal file
|
@ -0,0 +1,118 @@
|
|||
package de.kif.frontend.views
|
||||
|
||||
import de.kif.frontend.launch
|
||||
import de.kif.frontend.repository.WorkGroupRepository
|
||||
import de.westermann.kobserve.event.EventListener
|
||||
import de.westermann.kwebview.View
|
||||
import de.westermann.kwebview.async
|
||||
import de.westermann.kwebview.components.*
|
||||
import de.westermann.kwebview.createHtmlView
|
||||
import org.w3c.dom.*
|
||||
import kotlin.browser.document
|
||||
|
||||
fun initWorkGroupConstraints() {
|
||||
var index = 10000
|
||||
|
||||
val constraints =
|
||||
ListView.wrap<View>(document.getElementsByClassName("work-group-constraints")[0] as HTMLElement)
|
||||
val addButton =
|
||||
View.wrap(document.getElementsByClassName("work-group-constraints-add")[0] as HTMLElement)
|
||||
val addList =
|
||||
ListView.wrap<View>(document.getElementsByClassName("work-group-constraints-add-list")[0] as HTMLElement)
|
||||
|
||||
console.log(constraints.html)
|
||||
console.log(addButton.html)
|
||||
console.log(addList.html)
|
||||
|
||||
addButton.onClick {
|
||||
addList.classList += "active"
|
||||
|
||||
var listener: EventListener<*>? = null
|
||||
|
||||
async {
|
||||
listener = Body.onClick.reference {
|
||||
addList.classList -= "active"
|
||||
listener?.detach()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addList.textView("Add only on day") {
|
||||
onClick {
|
||||
constraints.html.appendChild(View.wrap(createHtmlView<HTMLDivElement>()) {
|
||||
classList += "input-group"
|
||||
html.appendChild(TextView("On day").apply { classList += "form-btn" }.html)
|
||||
html.appendChild(InputView(InputType.NUMBER).apply {
|
||||
classList += "form-control"
|
||||
html.name = "constraint-only-on-day-${index++}"
|
||||
min = -1337.0
|
||||
max = 1337.0
|
||||
}.html)
|
||||
}.html)
|
||||
}
|
||||
}
|
||||
addList.textView("Add only after time") {
|
||||
onClick {
|
||||
constraints.html.appendChild(View.wrap(createHtmlView<HTMLDivElement>()) {
|
||||
classList += "input-group"
|
||||
html.appendChild(TextView("After time").apply { classList += "form-btn" }.html)
|
||||
html.appendChild(InputView(InputType.NUMBER).apply {
|
||||
classList += "form-control"
|
||||
html.name = "constraint-only-after-time-${index++}"
|
||||
min = -1337.0
|
||||
max = 133700.0
|
||||
}.html)
|
||||
}.html)
|
||||
}
|
||||
}
|
||||
addList.textView("Add not at same time") {
|
||||
onClick {
|
||||
constraints.html.appendChild(View.wrap(createHtmlView<HTMLDivElement>()) {
|
||||
classList += "input-group"
|
||||
html.appendChild(TextView("Not with").apply { classList += "form-btn" }.html)
|
||||
|
||||
val select = createHtmlView<HTMLSelectElement>()
|
||||
select.classList.add("form-control")
|
||||
select.name = "constraint-not-at-same-time-${index++}"
|
||||
|
||||
launch {
|
||||
val all = WorkGroupRepository.all()
|
||||
|
||||
for (wg in all) {
|
||||
val option = createHtmlView<HTMLOptionElement>()
|
||||
option.value = wg.id.toString()
|
||||
option.textContent = wg.name
|
||||
select.appendChild(option)
|
||||
}
|
||||
}
|
||||
|
||||
html.appendChild(select)
|
||||
}.html)
|
||||
}
|
||||
}
|
||||
addList.textView("Add only after work group") {
|
||||
onClick {
|
||||
constraints.html.appendChild(View.wrap(createHtmlView<HTMLDivElement>()) {
|
||||
classList += "input-group"
|
||||
html.appendChild(TextView("After AK").apply { classList += "form-btn" }.html)
|
||||
|
||||
val select = createHtmlView<HTMLSelectElement>()
|
||||
select.classList.add("form-control")
|
||||
select.name = "constraint-only-after-work-group-${index++}"
|
||||
|
||||
launch {
|
||||
val all = WorkGroupRepository.all()
|
||||
|
||||
for (wg in all) {
|
||||
val option = createHtmlView<HTMLOptionElement>()
|
||||
option.value = wg.id.toString()
|
||||
option.textContent = wg.name
|
||||
select.appendChild(option)
|
||||
}
|
||||
}
|
||||
|
||||
html.appendChild(select)
|
||||
}.html)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ import de.kif.frontend.iterator
|
|||
import de.kif.frontend.launch
|
||||
import de.kif.frontend.repository.ScheduleRepository
|
||||
import de.westermann.kwebview.View
|
||||
import de.westermann.kwebview.createHtmlView
|
||||
import org.w3c.dom.*
|
||||
import kotlin.browser.document
|
||||
|
||||
|
@ -30,7 +31,7 @@ class Calendar(calendar: HTMLElement) : View(calendar) {
|
|||
}
|
||||
|
||||
fun scrollHorizontalTo(pixel: Double) {
|
||||
calendarTable.scrollTo (ScrollToOptions(pixel, 0.0, ScrollBehavior.SMOOTH))
|
||||
calendarTable.scrollTo(ScrollToOptions(pixel, 0.0, ScrollBehavior.SMOOTH))
|
||||
}
|
||||
|
||||
init {
|
||||
|
@ -76,6 +77,27 @@ class Calendar(calendar: HTMLElement) : View(calendar) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
val cont = document.getElementsByClassName("header-right")[0] as HTMLElement
|
||||
|
||||
val view = View.wrap(createHtmlView<HTMLAnchorElement>())
|
||||
cont.appendChild(view.html)
|
||||
view.html.textContent = "Check"
|
||||
view.onClick {
|
||||
launch {
|
||||
val errors = ScheduleRepository.checkConstraints()
|
||||
|
||||
println(errors)
|
||||
|
||||
for ((s, l) in errors.map) {
|
||||
for (entry in calendarEntries) {
|
||||
if (entry.scheduleId == s) {
|
||||
entry.error = l.isNotEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ class CalendarEntry(private val calendar: Calendar, view: HTMLElement) : View(vi
|
|||
private lateinit var workGroup: WorkGroup
|
||||
|
||||
var pending by classList.property("pending")
|
||||
var error by classList.property("error")
|
||||
private var nextScroll = 0.0
|
||||
|
||||
var editable: Boolean = false
|
||||
|
|
|
@ -144,6 +144,6 @@ abstract class View(view: HTMLElement = createHtmlView()) {
|
|||
}
|
||||
|
||||
companion object {
|
||||
fun wrap(htmlElement: HTMLElement) = object : View(htmlElement) {}
|
||||
fun wrap(htmlElement: HTMLElement, init: View.() -> Unit = {}) = object : View(htmlElement) {}.also(init)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -296,6 +296,7 @@ a {
|
|||
|
||||
span {
|
||||
padding: 0 0.5rem;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba($text-primary-color, 0.06);
|
||||
}
|
||||
|
@ -321,6 +322,12 @@ a {
|
|||
}
|
||||
}
|
||||
|
||||
textarea.form-control {
|
||||
height: 16rem;
|
||||
line-height: 1.1rem;
|
||||
padding: 0.6rem 1rem;
|
||||
}
|
||||
|
||||
select:-moz-focusring {
|
||||
color: transparent;
|
||||
text-shadow: 0 0 0 $text-primary-color;
|
||||
|
@ -671,6 +678,10 @@ form {
|
|||
opacity: 0.6;
|
||||
}
|
||||
|
||||
&.error {
|
||||
outline: solid 0.4rem $error-color;
|
||||
}
|
||||
|
||||
@include no-select()
|
||||
}
|
||||
|
||||
|
@ -908,4 +919,115 @@ form {
|
|||
width: 4rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.work-group-constraints {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.work-group-constraints-add {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.work-group-constraints-add-list {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
display: none;
|
||||
|
||||
background: $background-primary-color;
|
||||
border: solid 1px rgba($text-primary-color, 0.1);
|
||||
|
||||
span {
|
||||
padding: 0 0.5rem;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba($text-primary-color, 0.06);
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.overview {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.overview-main {
|
||||
flex-grow: 4;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.overview-side {
|
||||
min-width: 20%;
|
||||
}
|
||||
|
||||
.overview-shortcuts {
|
||||
a {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.overview-twitter {
|
||||
height: 20rem;
|
||||
background-color: #b3e6f9;
|
||||
}
|
||||
|
||||
.post {
|
||||
position: relative;
|
||||
padding-top: 2rem;
|
||||
}
|
||||
|
||||
.post-name {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
font-size: 1.2rem;
|
||||
color: $primary-color;
|
||||
line-height: 2rem;
|
||||
|
||||
&:empty::before {
|
||||
display: block;
|
||||
content: 'No title';
|
||||
opacity: 0.5;
|
||||
font-size: 1.2rem;
|
||||
line-height: 2rem;
|
||||
color: $text-primary-color;
|
||||
}
|
||||
}
|
||||
.post-edit {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
line-height: 2rem;
|
||||
}
|
||||
|
||||
.post-content {
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
h1 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
h2 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
h3 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
h4 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
h5 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
h6 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
|
@ -12,7 +12,9 @@ import io.ktor.http.content.static
|
|||
import io.ktor.jackson.jackson
|
||||
import io.ktor.routing.routing
|
||||
import io.ktor.websocket.WebSockets
|
||||
import kotlinx.serialization.ImplicitReflectionSerializer
|
||||
|
||||
@ImplicitReflectionSerializer
|
||||
fun Application.main() {
|
||||
install(DefaultHeaders)
|
||||
install(CallLogging)
|
||||
|
@ -35,7 +37,7 @@ fun Application.main() {
|
|||
}
|
||||
|
||||
// UI routes
|
||||
dashboard()
|
||||
overview()
|
||||
calendar()
|
||||
login()
|
||||
account()
|
||||
|
@ -53,6 +55,7 @@ fun Application.main() {
|
|||
trackApi()
|
||||
userApi()
|
||||
workGroupApi()
|
||||
constraintsApi()
|
||||
|
||||
// Web socket push notifications
|
||||
pushService()
|
||||
|
|
|
@ -8,8 +8,10 @@ import io.ktor.application.Application
|
|||
import io.ktor.server.engine.embeddedServer
|
||||
import io.ktor.server.netty.Netty
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.serialization.ImplicitReflectionSerializer
|
||||
|
||||
object Main {
|
||||
@ImplicitReflectionSerializer
|
||||
@Suppress("UnusedMainParameter")
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
|
|
|
@ -18,7 +18,8 @@ object Connection {
|
|||
SchemaUtils.create(
|
||||
DbTrack, DbWorkGroup,
|
||||
DbRoom, DbSchedule,
|
||||
DbUser, DbUserPermission
|
||||
DbUser, DbUserPermission,
|
||||
DbPost
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,15 +12,22 @@ object DbTrack : Table() {
|
|||
|
||||
object DbWorkGroup : Table() {
|
||||
val id = long("id").autoIncrement().primaryKey()
|
||||
val name = varchar("first_name", 64)
|
||||
val name = varchar("name", 64)
|
||||
|
||||
val interested = integer("interested")
|
||||
val trackId = long("track_id").nullable()
|
||||
val projector = bool("projector")
|
||||
val resolution = bool("resolution")
|
||||
|
||||
val internet = bool("internet")
|
||||
val whiteboard = bool("whiteboard")
|
||||
val blackboard = bool("blackboard")
|
||||
val accessible = bool("accessible")
|
||||
|
||||
val language = enumeration("language", Language::class)
|
||||
|
||||
val length = integer("length")
|
||||
val constraints = text("constraints")
|
||||
}
|
||||
|
||||
object DbRoom : Table() {
|
||||
|
@ -29,6 +36,11 @@ object DbRoom : Table() {
|
|||
|
||||
val places = integer("places")
|
||||
val projector = bool("projector")
|
||||
|
||||
val internet = bool("internet")
|
||||
val whiteboard = bool("whiteboard")
|
||||
val blackboard = bool("blackboard")
|
||||
val accessible = bool("accessible")
|
||||
}
|
||||
|
||||
object DbSchedule : Table() {
|
||||
|
@ -49,3 +61,11 @@ object DbUserPermission : Table() {
|
|||
val userId = long("id").primaryKey(0)
|
||||
val permission = enumeration("permission", Permission::class).primaryKey(1)
|
||||
}
|
||||
|
||||
object DbPost : Table() {
|
||||
val id = long("id").autoIncrement().primaryKey()
|
||||
val name = varchar("name", 64)
|
||||
|
||||
val content = text("content")
|
||||
val url = varchar("url", 64).uniqueIndex()
|
||||
}
|
||||
|
|
107
src/jvmMain/kotlin/de/kif/backend/repository/PostRepository.kt
Normal file
107
src/jvmMain/kotlin/de/kif/backend/repository/PostRepository.kt
Normal file
|
@ -0,0 +1,107 @@
|
|||
package de.kif.backend.repository
|
||||
|
||||
import de.kif.backend.database.DbPost
|
||||
import de.kif.backend.database.dbQuery
|
||||
import de.kif.backend.util.PushService
|
||||
import de.kif.common.MessageType
|
||||
import de.kif.common.Repository
|
||||
import de.kif.common.RepositoryType
|
||||
import de.kif.common.model.Post
|
||||
import de.westermann.kobserve.event.EventHandler
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.jetbrains.exposed.sql.*
|
||||
|
||||
object PostRepository : Repository<Post> {
|
||||
|
||||
override val onCreate = EventHandler<Long>()
|
||||
override val onUpdate = EventHandler<Long>()
|
||||
override val onDelete = EventHandler<Long>()
|
||||
|
||||
private fun rowToModel(row: ResultRow): Post {
|
||||
val id = row[DbPost.id]
|
||||
val name = row[DbPost.name]
|
||||
val content = row[DbPost.content]
|
||||
val url = row[DbPost.url]
|
||||
|
||||
return Post(id, name, content, url)
|
||||
}
|
||||
|
||||
override suspend fun get(id: Long): Post? {
|
||||
return dbQuery {
|
||||
rowToModel(DbPost.select { DbPost.id eq id }.firstOrNull() ?: return@dbQuery null)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun create(model: Post): Long {
|
||||
return dbQuery {
|
||||
val id = DbPost.insert {
|
||||
it[name] = model.name
|
||||
it[content] = model.content
|
||||
it[url] = model.url
|
||||
}[DbPost.id] ?: throw IllegalStateException("Cannot create model!")
|
||||
|
||||
onCreate.emit(id)
|
||||
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun update(model: Post) {
|
||||
if (model.id == null) throw IllegalStateException("Cannot update model which was not created!")
|
||||
dbQuery {
|
||||
DbPost.update({ DbPost.id eq model.id }) {
|
||||
it[name] = model.name
|
||||
it[content] = model.content
|
||||
it[url] = model.url
|
||||
}
|
||||
|
||||
onUpdate.emit(model.id)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun delete(id: Long) {
|
||||
onDelete.emit(id)
|
||||
|
||||
dbQuery {
|
||||
DbPost.deleteWhere { DbPost.id eq id }
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun all(): List<Post> {
|
||||
return dbQuery {
|
||||
val result = DbPost.selectAll()
|
||||
|
||||
result.map(this::rowToModel)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getByUrl(url: String): Post? {
|
||||
return dbQuery {
|
||||
val result = DbPost.select { DbPost.url eq url }
|
||||
|
||||
runBlocking {
|
||||
result.firstOrNull()?.let {
|
||||
rowToModel(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun registerPushService() {
|
||||
onCreate {
|
||||
runBlocking {
|
||||
PushService.notify(MessageType.CREATE, RepositoryType.POST, it)
|
||||
}
|
||||
}
|
||||
onUpdate {
|
||||
runBlocking {
|
||||
PushService.notify(MessageType.UPDATE, RepositoryType.POST, it)
|
||||
}
|
||||
}
|
||||
onDelete {
|
||||
runBlocking {
|
||||
PushService.notify(MessageType.DELETE, RepositoryType.POST, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,8 +20,12 @@ object RoomRepository : Repository<Room> {
|
|||
val name = row[DbRoom.name]
|
||||
val places = row[DbRoom.places]
|
||||
val projector = row[DbRoom.projector]
|
||||
val internet = row[DbRoom.internet]
|
||||
val whiteboard = row[DbRoom.whiteboard]
|
||||
val blackboard = row[DbRoom.blackboard]
|
||||
val accessible = row[DbRoom.accessible]
|
||||
|
||||
return Room(id, name, places, projector)
|
||||
return Room(id, name, places, projector, internet, whiteboard, blackboard, accessible)
|
||||
}
|
||||
|
||||
override suspend fun get(id: Long): Room? {
|
||||
|
@ -36,6 +40,10 @@ object RoomRepository : Repository<Room> {
|
|||
it[name] = model.name
|
||||
it[places] = model.places
|
||||
it[projector] = model.projector
|
||||
it[internet] = model.internet
|
||||
it[whiteboard] = model.whiteboard
|
||||
it[blackboard] = model.blackboard
|
||||
it[accessible] = model.accessible
|
||||
}[DbRoom.id] ?: throw IllegalStateException("Cannot create model!")
|
||||
|
||||
onCreate.emit(id)
|
||||
|
@ -51,6 +59,10 @@ object RoomRepository : Repository<Room> {
|
|||
it[name] = model.name
|
||||
it[places] = model.places
|
||||
it[projector] = model.projector
|
||||
it[internet] = model.internet
|
||||
it[whiteboard] = model.whiteboard
|
||||
it[blackboard] = model.blackboard
|
||||
it[accessible] = model.accessible
|
||||
}
|
||||
|
||||
onUpdate.emit(model.id)
|
||||
|
|
|
@ -3,12 +3,15 @@ package de.kif.backend.repository
|
|||
import de.kif.backend.database.DbWorkGroup
|
||||
import de.kif.backend.database.dbQuery
|
||||
import de.kif.backend.util.PushService
|
||||
import de.kif.common.Message
|
||||
import de.kif.common.MessageType
|
||||
import de.kif.common.Repository
|
||||
import de.kif.common.RepositoryType
|
||||
import de.kif.common.model.Constraint
|
||||
import de.kif.common.model.WorkGroup
|
||||
import de.westermann.kobserve.event.EventHandler
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.serialization.list
|
||||
import org.jetbrains.exposed.sql.*
|
||||
|
||||
object WorkGroupRepository : Repository<WorkGroup> {
|
||||
|
@ -24,12 +27,31 @@ object WorkGroupRepository : Repository<WorkGroup> {
|
|||
val trackId = row[DbWorkGroup.trackId]
|
||||
val projector = row[DbWorkGroup.projector]
|
||||
val resolution = row[DbWorkGroup.resolution]
|
||||
val internet = row[DbWorkGroup.internet]
|
||||
val whiteboard = row[DbWorkGroup.whiteboard]
|
||||
val blackboard = row[DbWorkGroup.blackboard]
|
||||
val accessible = row[DbWorkGroup.accessible]
|
||||
val length = row[DbWorkGroup.length]
|
||||
val language = row[DbWorkGroup.language]
|
||||
val constraints = Message.json.parse(Constraint.serializer().list, row[DbWorkGroup.constraints])
|
||||
|
||||
val track = trackId?.let { TrackRepository.get(it) }
|
||||
|
||||
return WorkGroup(id, name, interested, track, projector, resolution, length, language)
|
||||
return WorkGroup(
|
||||
id,
|
||||
name,
|
||||
interested,
|
||||
track,
|
||||
projector,
|
||||
resolution,
|
||||
internet,
|
||||
whiteboard,
|
||||
blackboard,
|
||||
accessible,
|
||||
length,
|
||||
language,
|
||||
constraints
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun get(id: Long): WorkGroup? {
|
||||
|
@ -50,8 +72,13 @@ object WorkGroupRepository : Repository<WorkGroup> {
|
|||
it[trackId] = model.track?.id
|
||||
it[projector] = model.projector
|
||||
it[resolution] = model.resolution
|
||||
it[internet] = model.internet
|
||||
it[whiteboard] = model.whiteboard
|
||||
it[blackboard] = model.blackboard
|
||||
it[accessible] = model.accessible
|
||||
it[length] = model.length
|
||||
it[language] = model.language
|
||||
it[constraints] = Message.json.stringify(Constraint.serializer().list, model.constraints)
|
||||
}[DbWorkGroup.id] ?: throw IllegalStateException("Cannot create model!")
|
||||
|
||||
onCreate.emit(id)
|
||||
|
@ -69,8 +96,13 @@ object WorkGroupRepository : Repository<WorkGroup> {
|
|||
it[trackId] = model.track?.id
|
||||
it[projector] = model.projector
|
||||
it[resolution] = model.resolution
|
||||
it[internet] = model.internet
|
||||
it[whiteboard] = model.whiteboard
|
||||
it[blackboard] = model.blackboard
|
||||
it[accessible] = model.accessible
|
||||
it[length] = model.length
|
||||
it[language] = model.language
|
||||
it[constraints] = Message.json.stringify(Constraint.serializer().list, model.constraints)
|
||||
}
|
||||
|
||||
onUpdate.emit(model.id)
|
||||
|
|
|
@ -136,7 +136,7 @@ private fun DIV.renderTimeToRoom(
|
|||
val minutes = (time % 60).toString().padStart(2, '0')
|
||||
val hours = (time / 60).toString().padStart(2, '0')
|
||||
title = "$hours:$minutes"
|
||||
attributes["data-time"] = time.toString()
|
||||
attributes["data-time"] = start.toString()
|
||||
attributes["data-room"] = room.id.toString()
|
||||
attributes["data-day"] = day.toString()
|
||||
|
||||
|
@ -203,7 +203,7 @@ private fun DIV.renderRoomToTime(
|
|||
|
||||
for (room in rooms) {
|
||||
div("calendar-cell") {
|
||||
attributes["data-time"] = time.toString()
|
||||
attributes["data-time"] = start.toString()
|
||||
attributes["data-room"] = room.id.toString()
|
||||
attributes["data-day"] = day.toString()
|
||||
title = timeString
|
||||
|
@ -251,6 +251,7 @@ fun Route.calendar() {
|
|||
|
||||
get("/calendar/{day}") {
|
||||
val user = isAuthenticated(Permission.SCHEDULE)
|
||||
val editable = user != null
|
||||
|
||||
val day = call.parameters["day"]?.toIntOrNull() ?: return@get
|
||||
|
||||
|
@ -280,11 +281,14 @@ fun Route.calendar() {
|
|||
min = h1
|
||||
}
|
||||
|
||||
if (editable) {
|
||||
min = min(min, 0)
|
||||
max = max(max, 24 * 60)
|
||||
}
|
||||
|
||||
min = (min / 60 - 1) * 60
|
||||
max = (max / 60 + 2) * 60
|
||||
|
||||
min = min(min, 0)
|
||||
|
||||
call.respondHtmlTemplate(MainTemplate()) {
|
||||
menuTemplate {
|
||||
this.user = user
|
||||
|
@ -314,7 +318,6 @@ fun Route.calendar() {
|
|||
}
|
||||
|
||||
div("calendar") {
|
||||
val editable = user != null
|
||||
attributes["data-day"] = day.toString()
|
||||
attributes["data-editable"] = editable.toString()
|
||||
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
package de.kif.backend.route
|
||||
|
||||
import de.kif.backend.PortalSession
|
||||
import de.kif.backend.view.MainTemplate
|
||||
import de.kif.backend.view.MenuTemplate
|
||||
import io.ktor.application.call
|
||||
import io.ktor.html.respondHtmlTemplate
|
||||
import io.ktor.routing.Route
|
||||
import io.ktor.routing.get
|
||||
import io.ktor.sessions.get
|
||||
import io.ktor.sessions.sessions
|
||||
import kotlinx.html.h1
|
||||
|
||||
fun Route.dashboard() {
|
||||
get("") {
|
||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
||||
call.respondHtmlTemplate(MainTemplate()) {
|
||||
menuTemplate {
|
||||
this.user = user
|
||||
active = MenuTemplate.Tab.DASHBOARD
|
||||
}
|
||||
content {
|
||||
h1 { +"Dashboard" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
281
src/jvmMain/kotlin/de/kif/backend/route/Overview.kt
Normal file
281
src/jvmMain/kotlin/de/kif/backend/route/Overview.kt
Normal file
|
@ -0,0 +1,281 @@
|
|||
package de.kif.backend.route
|
||||
|
||||
import de.kif.backend.authenticateOrRedirect
|
||||
import de.kif.backend.isAuthenticated
|
||||
import de.kif.backend.repository.PostRepository
|
||||
import de.kif.backend.util.markdownToHtml
|
||||
import de.kif.backend.view.MainTemplate
|
||||
import de.kif.backend.view.MenuTemplate
|
||||
import de.kif.common.model.Permission
|
||||
import de.kif.common.model.Post
|
||||
import io.ktor.application.call
|
||||
import io.ktor.html.respondHtmlTemplate
|
||||
import io.ktor.request.receiveParameters
|
||||
import io.ktor.response.respondRedirect
|
||||
import io.ktor.routing.Route
|
||||
import io.ktor.routing.get
|
||||
import io.ktor.routing.post
|
||||
import io.ktor.util.toMap
|
||||
import kotlinx.html.*
|
||||
|
||||
fun Route.overview() {
|
||||
get("") {
|
||||
val user = isAuthenticated(Permission.POST)
|
||||
val editable = user != null
|
||||
|
||||
val postList = PostRepository.all().asReversed()
|
||||
|
||||
call.respondHtmlTemplate(MainTemplate()) {
|
||||
menuTemplate {
|
||||
this.user = user
|
||||
active = MenuTemplate.Tab.BOARD
|
||||
}
|
||||
content {
|
||||
div("overview") {
|
||||
div("overview-main") {
|
||||
if (editable) {
|
||||
div("overview-new") {
|
||||
a("post/new", classes = "form-btn") {
|
||||
+"New"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (post in postList) {
|
||||
div("overview-post 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
div("overview-side") {
|
||||
div("overview-shortcuts") {
|
||||
a {
|
||||
+"Wiki"
|
||||
}
|
||||
a {
|
||||
+"Wiki"
|
||||
}
|
||||
a {
|
||||
+"Wiki"
|
||||
}
|
||||
a {
|
||||
+"Wiki"
|
||||
}
|
||||
a {
|
||||
+"Wiki"
|
||||
}
|
||||
a {
|
||||
+"Wiki"
|
||||
}
|
||||
}
|
||||
div("overview-twitter") {
|
||||
+"The Twitter Wall"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
get("/post/{id}") {
|
||||
authenticateOrRedirect(Permission.POST) { user ->
|
||||
val postId = call.parameters["id"]?.toLongOrNull() ?: return@get
|
||||
val editPost = PostRepository.get(postId) ?: return@get
|
||||
call.respondHtmlTemplate(MainTemplate()) {
|
||||
menuTemplate {
|
||||
this.user = user
|
||||
active = MenuTemplate.Tab.BOARD
|
||||
}
|
||||
content {
|
||||
h1 { +"Edit post" }
|
||||
form(method = FormMethod.post) {
|
||||
div("form-group") {
|
||||
label {
|
||||
htmlFor = "name"
|
||||
+"Name"
|
||||
}
|
||||
input(
|
||||
name = "name",
|
||||
classes = "form-control"
|
||||
) {
|
||||
id = "name"
|
||||
placeholder = "Name"
|
||||
value = editPost.name
|
||||
}
|
||||
}
|
||||
div("form-group") {
|
||||
label {
|
||||
htmlFor = "url"
|
||||
+"Url"
|
||||
}
|
||||
input(
|
||||
name = "url",
|
||||
classes = "form-control"
|
||||
) {
|
||||
id = "places"
|
||||
placeholder = "Places"
|
||||
value = editPost.url
|
||||
}
|
||||
}
|
||||
|
||||
div("form-group") {
|
||||
label {
|
||||
htmlFor = "content"
|
||||
+"Content"
|
||||
}
|
||||
textArea(rows = "10", classes = "form-control") {
|
||||
name = "content"
|
||||
id = "projector"
|
||||
|
||||
+editPost.content
|
||||
}
|
||||
}
|
||||
|
||||
div("form-group") {
|
||||
a("/") {
|
||||
button(classes = "form-btn") {
|
||||
+"Cancel"
|
||||
}
|
||||
}
|
||||
button(type = ButtonType.submit, classes = "form-btn btn-primary") {
|
||||
+"Save"
|
||||
}
|
||||
}
|
||||
}
|
||||
a("/post/${editPost.id}/delete") {
|
||||
button(classes = "form-btn btn-danger") {
|
||||
+"Delete"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post("/post/{id}") {
|
||||
authenticateOrRedirect(Permission.POST) { user ->
|
||||
val postId = call.parameters["id"]?.toLongOrNull() ?: return@post
|
||||
val params = call.receiveParameters().toMap().mapValues { (_, list) ->
|
||||
list.firstOrNull()
|
||||
}
|
||||
var post = PostRepository.get(postId) ?: return@post
|
||||
|
||||
params["name"]?.let { post = post.copy(name = it) }
|
||||
params["url"]?.let { post = post.copy(url = it) }
|
||||
params["content"]?.let { post = post.copy(content = it) }
|
||||
|
||||
PostRepository.update(post)
|
||||
|
||||
call.respondRedirect("/")
|
||||
}
|
||||
}
|
||||
|
||||
get("/post/new") {
|
||||
authenticateOrRedirect(Permission.POST) { user ->
|
||||
call.respondHtmlTemplate(MainTemplate()) {
|
||||
menuTemplate {
|
||||
this.user = user
|
||||
active = MenuTemplate.Tab.BOARD
|
||||
}
|
||||
content {
|
||||
h1 { +"Create post" }
|
||||
form(method = FormMethod.post) {
|
||||
div("form-group") {
|
||||
label {
|
||||
htmlFor = "name"
|
||||
+"Name"
|
||||
}
|
||||
input(
|
||||
name = "name",
|
||||
classes = "form-control"
|
||||
) {
|
||||
id = "name"
|
||||
placeholder = "Name"
|
||||
value = ""
|
||||
}
|
||||
}
|
||||
div("form-group") {
|
||||
label {
|
||||
htmlFor = "url"
|
||||
+"Url"
|
||||
}
|
||||
input(
|
||||
name = "url",
|
||||
classes = "form-control"
|
||||
) {
|
||||
id = "places"
|
||||
placeholder = "Places"
|
||||
value = Post.generateUrl()
|
||||
}
|
||||
}
|
||||
|
||||
div("form-group") {
|
||||
label {
|
||||
htmlFor = "content"
|
||||
+"Content"
|
||||
}
|
||||
textArea(rows = "10", classes = "form-control") {
|
||||
name = "content"
|
||||
id = "projector"
|
||||
|
||||
+""
|
||||
}
|
||||
}
|
||||
|
||||
div("form-group") {
|
||||
a("/") {
|
||||
button(classes = "form-btn") {
|
||||
+"Cancel"
|
||||
}
|
||||
}
|
||||
button(type = ButtonType.submit, classes = "form-btn btn-primary") {
|
||||
+"Create"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post("/post/new") {
|
||||
authenticateOrRedirect(Permission.POST) { user ->
|
||||
val params = call.receiveParameters().toMap().mapValues { (_, list) ->
|
||||
list.firstOrNull()
|
||||
}
|
||||
|
||||
val name = params["name"] ?: return@post
|
||||
val content = params["content"] ?: return@post
|
||||
val url = params["url"] ?: return@post
|
||||
|
||||
val post = Post(null, name, content, url)
|
||||
|
||||
PostRepository.create(post)
|
||||
|
||||
call.respondRedirect("/")
|
||||
}
|
||||
}
|
||||
|
||||
get("/post/{id}/delete") {
|
||||
authenticateOrRedirect(Permission.POST) { user ->
|
||||
val postId = call.parameters["id"]?.toLongOrNull() ?: return@get
|
||||
|
||||
PostRepository.delete(postId)
|
||||
|
||||
call.respondRedirect("/")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -36,7 +36,6 @@ fun Route.room() {
|
|||
active = MenuTemplate.Tab.ROOM
|
||||
}
|
||||
content {
|
||||
h1 { +"Rooms" }
|
||||
insert(TableTemplate()) {
|
||||
searchValue = search
|
||||
|
||||
|
@ -169,6 +168,74 @@ fun Route.room() {
|
|||
}
|
||||
}
|
||||
|
||||
div("form-switch-group") {
|
||||
div("form-group form-switch") {
|
||||
input(
|
||||
name = "internet",
|
||||
classes = "form-control",
|
||||
type = InputType.checkBox
|
||||
) {
|
||||
id = "internet"
|
||||
checked = editRoom.internet
|
||||
}
|
||||
label {
|
||||
htmlFor = "internet"
|
||||
+"Internet"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div("form-switch-group") {
|
||||
div("form-group form-switch") {
|
||||
input(
|
||||
name = "whiteboard",
|
||||
classes = "form-control",
|
||||
type = InputType.checkBox
|
||||
) {
|
||||
id = "whiteboard"
|
||||
checked = editRoom.whiteboard
|
||||
}
|
||||
label {
|
||||
htmlFor = "whiteboard"
|
||||
+"Whiteboard"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div("form-switch-group") {
|
||||
div("form-group form-switch") {
|
||||
input(
|
||||
name = "blackboard",
|
||||
classes = "form-control",
|
||||
type = InputType.checkBox
|
||||
) {
|
||||
id = "blackboard"
|
||||
checked = editRoom.blackboard
|
||||
}
|
||||
label {
|
||||
htmlFor = "blackboard"
|
||||
+"Blackboard"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div("form-switch-group") {
|
||||
div("form-group form-switch") {
|
||||
input(
|
||||
name = "accessible",
|
||||
classes = "form-control",
|
||||
type = InputType.checkBox
|
||||
) {
|
||||
id = "accessible"
|
||||
checked = editRoom.accessible
|
||||
}
|
||||
label {
|
||||
htmlFor = "accessible"
|
||||
+"Accessible"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div("form-group") {
|
||||
a("/room") {
|
||||
button(classes = "form-btn") {
|
||||
|
@ -201,6 +268,10 @@ fun Route.room() {
|
|||
params["name"]?.let { room = room.copy(name = it) }
|
||||
params["places"]?.let { room = room.copy(places = it.toIntOrNull() ?: 0) }
|
||||
params["projector"]?.let { room = room.copy(projector = it == "on") }
|
||||
params["internet"]?.let { room = room.copy(internet = it == "on") }
|
||||
params["whiteboard"]?.let { room = room.copy(whiteboard = it == "on") }
|
||||
params["blackboard"]?.let { room = room.copy(blackboard = it == "on") }
|
||||
params["accessible"]?.let { room = room.copy(accessible = it == "on") }
|
||||
|
||||
RoomRepository.update(room)
|
||||
|
||||
|
@ -268,6 +339,74 @@ fun Route.room() {
|
|||
}
|
||||
}
|
||||
|
||||
div("form-switch-group") {
|
||||
div("form-group form-switch") {
|
||||
input(
|
||||
name = "internet",
|
||||
classes = "form-control",
|
||||
type = InputType.checkBox
|
||||
) {
|
||||
id = "internet"
|
||||
checked = false
|
||||
}
|
||||
label {
|
||||
htmlFor = "internet"
|
||||
+"Internet"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div("form-switch-group") {
|
||||
div("form-group form-switch") {
|
||||
input(
|
||||
name = "whiteboard",
|
||||
classes = "form-control",
|
||||
type = InputType.checkBox
|
||||
) {
|
||||
id = "whiteboard"
|
||||
checked = false
|
||||
}
|
||||
label {
|
||||
htmlFor = "whiteboard"
|
||||
+"Whiteboard"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div("form-switch-group") {
|
||||
div("form-group form-switch") {
|
||||
input(
|
||||
name = "blackboard",
|
||||
classes = "form-control",
|
||||
type = InputType.checkBox
|
||||
) {
|
||||
id = "blackboard"
|
||||
checked = false
|
||||
}
|
||||
label {
|
||||
htmlFor = "blackboard"
|
||||
+"Blackboard"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div("form-switch-group") {
|
||||
div("form-group form-switch") {
|
||||
input(
|
||||
name = "accessible",
|
||||
classes = "form-control",
|
||||
type = InputType.checkBox
|
||||
) {
|
||||
id = "accessible"
|
||||
checked = false
|
||||
}
|
||||
label {
|
||||
htmlFor = "accessible"
|
||||
+"Accessible"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div("form-group") {
|
||||
a("/room") {
|
||||
button(classes = "form-btn") {
|
||||
|
@ -293,8 +432,12 @@ fun Route.room() {
|
|||
val name = params["name"] ?: return@post
|
||||
val places = (params["places"] ?: return@post).toIntOrNull() ?: 0
|
||||
val projector = params["projector"] == "on"
|
||||
val internet = params["internet"] == "on"
|
||||
val whiteboard = params["whiteboard"] == "on"
|
||||
val blackboard = params["blackboard"] == "on"
|
||||
val accessible = params["accessible"] == "on"
|
||||
|
||||
val room = Room(null, name, places, projector)
|
||||
val room = Room(null, name, places, projector, internet, whiteboard, blackboard, accessible)
|
||||
|
||||
RoomRepository.create(room)
|
||||
|
||||
|
|
|
@ -94,7 +94,6 @@ fun Route.track() {
|
|||
active = MenuTemplate.Tab.WORK_GROUP
|
||||
}
|
||||
content {
|
||||
h1 { +"Tracks" }
|
||||
insert(TableTemplate()) {
|
||||
searchValue = search
|
||||
|
||||
|
|
|
@ -37,7 +37,6 @@ fun Route.user() {
|
|||
active = MenuTemplate.Tab.USER
|
||||
}
|
||||
content {
|
||||
h1 { +"Users" }
|
||||
insert(TableTemplate()) {
|
||||
searchValue = search
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package de.kif.backend.route
|
||||
|
||||
|
||||
import de.kif.backend.authenticateOrRedirect
|
||||
import de.kif.backend.repository.TrackRepository
|
||||
import de.kif.backend.repository.WorkGroupRepository
|
||||
|
@ -8,9 +7,7 @@ import de.kif.backend.view.MainTemplate
|
|||
import de.kif.backend.view.MenuTemplate
|
||||
import de.kif.backend.view.TableTemplate
|
||||
import de.kif.common.Search
|
||||
import de.kif.common.model.Language
|
||||
import de.kif.common.model.Permission
|
||||
import de.kif.common.model.WorkGroup
|
||||
import de.kif.common.model.*
|
||||
import io.ktor.application.call
|
||||
import io.ktor.html.insert
|
||||
import io.ktor.html.respondHtmlTemplate
|
||||
|
@ -36,7 +33,6 @@ fun Route.workGroup() {
|
|||
active = MenuTemplate.Tab.WORK_GROUP
|
||||
}
|
||||
content {
|
||||
h1 { +"Work groups" }
|
||||
insert(TableTemplate()) {
|
||||
searchValue = search
|
||||
|
||||
|
@ -157,6 +153,17 @@ fun Route.workGroup() {
|
|||
val workGroupId = call.parameters["id"]?.toLongOrNull() ?: return@get
|
||||
val editWorkGroup = WorkGroupRepository.get(workGroupId) ?: return@get
|
||||
val tracks = TrackRepository.all()
|
||||
|
||||
val workGroups = editWorkGroup.constraints.mapNotNull {
|
||||
when (it.type) {
|
||||
ConstraintType.NotAtSameTime -> it.number
|
||||
ConstraintType.OnlyAfterWorkGroup -> it.number
|
||||
else -> null
|
||||
}
|
||||
}.distinct().associateWith {
|
||||
WorkGroupRepository.get(it)!!
|
||||
}
|
||||
|
||||
call.respondHtmlTemplate(MainTemplate()) {
|
||||
menuTemplate {
|
||||
this.user = user
|
||||
|
@ -201,7 +208,6 @@ fun Route.workGroup() {
|
|||
htmlFor = "track"
|
||||
+"Track"
|
||||
}
|
||||
//div("input-group") {
|
||||
select(
|
||||
classes = "form-control"
|
||||
) {
|
||||
|
@ -220,12 +226,6 @@ fun Route.workGroup() {
|
|||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
a("/tracks", classes = "form-btn") {
|
||||
i("material-icons") { +"edit" }
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
div("form-group") {
|
||||
label {
|
||||
|
@ -294,6 +294,149 @@ fun Route.workGroup() {
|
|||
+"Resolution"
|
||||
}
|
||||
}
|
||||
|
||||
div("form-group form-switch") {
|
||||
input(
|
||||
name = "internet",
|
||||
classes = "form-control",
|
||||
type = InputType.checkBox
|
||||
) {
|
||||
id = "internet"
|
||||
checked = editWorkGroup.internet
|
||||
}
|
||||
label {
|
||||
htmlFor = "internet"
|
||||
+"Internet"
|
||||
}
|
||||
}
|
||||
|
||||
div("form-group form-switch") {
|
||||
input(
|
||||
name = "whiteboard",
|
||||
classes = "form-control",
|
||||
type = InputType.checkBox
|
||||
) {
|
||||
id = "whiteboard"
|
||||
checked = editWorkGroup.whiteboard
|
||||
}
|
||||
label {
|
||||
htmlFor = "whiteboard"
|
||||
+"Whiteboard"
|
||||
}
|
||||
}
|
||||
|
||||
div("form-group form-switch") {
|
||||
input(
|
||||
name = "blackboard",
|
||||
classes = "form-control",
|
||||
type = InputType.checkBox
|
||||
) {
|
||||
id = "blackboard"
|
||||
checked = editWorkGroup.blackboard
|
||||
}
|
||||
label {
|
||||
htmlFor = "blackboard"
|
||||
+"Blackboard"
|
||||
}
|
||||
}
|
||||
|
||||
div("form-group form-switch") {
|
||||
input(
|
||||
name = "accessible",
|
||||
classes = "form-control",
|
||||
type = InputType.checkBox
|
||||
) {
|
||||
id = "accessible"
|
||||
checked = editWorkGroup.accessible
|
||||
}
|
||||
label {
|
||||
htmlFor = "accessible"
|
||||
+"Accessible"
|
||||
}
|
||||
}
|
||||
|
||||
div("work-group-constraints")
|
||||
}
|
||||
|
||||
div("form-group work-group-constraints") {
|
||||
label {
|
||||
+"Constraints"
|
||||
}
|
||||
span("form-btn work-group-constraints-add") {
|
||||
i("material-icons") { +"add" }
|
||||
}
|
||||
div("work-group-constraints-add-list") {
|
||||
|
||||
}
|
||||
for ((index, constraint) in editWorkGroup.constraints.withIndex()) {
|
||||
div("input-group") {
|
||||
when (constraint.type) {
|
||||
ConstraintType.OnlyOnDay -> {
|
||||
span("form-btn") {
|
||||
+"On day"
|
||||
}
|
||||
input(
|
||||
name = "constraint-only-on-day-$index",
|
||||
classes = "form-control",
|
||||
type = InputType.number
|
||||
) {
|
||||
value = constraint.number.toString()
|
||||
|
||||
min = "-1337"
|
||||
max = "1337"
|
||||
}
|
||||
}
|
||||
ConstraintType.OnlyAfterTime -> {
|
||||
span("form-btn") {
|
||||
+"After time"
|
||||
}
|
||||
input(
|
||||
name = "constraint-only-after-time-$index",
|
||||
classes = "form-control",
|
||||
type = InputType.number
|
||||
) {
|
||||
value = constraint.number.toString()
|
||||
|
||||
min = "-1337"
|
||||
max = "133700"
|
||||
}
|
||||
}
|
||||
ConstraintType.NotAtSameTime -> {
|
||||
span("form-btn") {
|
||||
+"Not with"
|
||||
}
|
||||
select(
|
||||
classes = "form-control"
|
||||
) {
|
||||
name = "constraint-not-at-same-time-$index"
|
||||
|
||||
option {
|
||||
selected = true
|
||||
value = constraint.number.toString()
|
||||
+(workGroups[constraint.number]?.name ?: "")
|
||||
}
|
||||
}
|
||||
}
|
||||
ConstraintType.OnlyAfterWorkGroup -> {
|
||||
span("form-btn") {
|
||||
+"After AK"
|
||||
}
|
||||
select(
|
||||
classes = "form-control"
|
||||
) {
|
||||
name = "constraint-only-after-work-group-$index"
|
||||
|
||||
option {
|
||||
selected = true
|
||||
value = constraint.number.toString()
|
||||
+(workGroups[constraint.number]?.name ?: "")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div("form-group") {
|
||||
|
@ -333,12 +476,37 @@ fun Route.workGroup() {
|
|||
}
|
||||
params["projector"]?.let { editWorkGroup = editWorkGroup.copy(projector = it == "on") }
|
||||
params["resolution"]?.let { editWorkGroup = editWorkGroup.copy(resolution = it == "on") }
|
||||
params["internet"]?.let { editWorkGroup = editWorkGroup.copy(internet = it == "on") }
|
||||
params["whiteboard"]?.let { editWorkGroup = editWorkGroup.copy(whiteboard = it == "on") }
|
||||
params["blackboard"]?.let { editWorkGroup = editWorkGroup.copy(blackboard = it == "on") }
|
||||
params["accessible"]?.let { editWorkGroup = editWorkGroup.copy(accessible = it == "on") }
|
||||
|
||||
params["length"]?.toIntOrNull()?.let { editWorkGroup = editWorkGroup.copy(length = it) }
|
||||
params["language"]?.let {
|
||||
editWorkGroup =
|
||||
editWorkGroup.copy(language = Language.values().find { l -> l.code == it } ?: Language.GERMAN)
|
||||
}
|
||||
|
||||
val constraints = params.mapNotNull { (key, value) ->
|
||||
when {
|
||||
key.startsWith("constraint-only-on-day") -> {
|
||||
value?.toLongOrNull()?.let { Constraint(ConstraintType.OnlyOnDay, it) }
|
||||
}
|
||||
key.startsWith("constraint-only-after-time") -> {
|
||||
value?.toLongOrNull()?.let { Constraint(ConstraintType.OnlyAfterTime, it) }
|
||||
}
|
||||
key.startsWith("constraint-not-at-same-time") -> {
|
||||
value?.toLongOrNull()?.let { Constraint(ConstraintType.NotAtSameTime, it) }
|
||||
}
|
||||
key.startsWith("constraint-only-after-work-group") -> {
|
||||
value?.toLongOrNull()?.let { Constraint(ConstraintType.OnlyAfterWorkGroup, it) }
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
editWorkGroup = editWorkGroup.copy(constraints = constraints)
|
||||
|
||||
WorkGroupRepository.update(editWorkGroup)
|
||||
|
||||
call.respondRedirect("/workgroups")
|
||||
|
@ -477,6 +645,78 @@ fun Route.workGroup() {
|
|||
+"Resolution"
|
||||
}
|
||||
}
|
||||
|
||||
div("form-group form-switch") {
|
||||
input(
|
||||
name = "internet",
|
||||
classes = "form-control",
|
||||
type = InputType.checkBox
|
||||
) {
|
||||
id = "internet"
|
||||
checked = false
|
||||
}
|
||||
label {
|
||||
htmlFor = "internet"
|
||||
+"Internet"
|
||||
}
|
||||
}
|
||||
|
||||
div("form-group form-switch") {
|
||||
input(
|
||||
name = "whiteboard",
|
||||
classes = "form-control",
|
||||
type = InputType.checkBox
|
||||
) {
|
||||
id = "whiteboard"
|
||||
checked = false
|
||||
}
|
||||
label {
|
||||
htmlFor = "whiteboard"
|
||||
+"Whiteboard"
|
||||
}
|
||||
}
|
||||
|
||||
div("form-group form-switch") {
|
||||
input(
|
||||
name = "blackboard",
|
||||
classes = "form-control",
|
||||
type = InputType.checkBox
|
||||
) {
|
||||
id = "blackboard"
|
||||
checked = false
|
||||
}
|
||||
label {
|
||||
htmlFor = "blackboard"
|
||||
+"Blackboard"
|
||||
}
|
||||
}
|
||||
|
||||
div("form-group form-switch") {
|
||||
input(
|
||||
name = "accessible",
|
||||
classes = "form-control",
|
||||
type = InputType.checkBox
|
||||
) {
|
||||
id = "accessible"
|
||||
checked = false
|
||||
}
|
||||
label {
|
||||
htmlFor = "accessible"
|
||||
+"Accessible"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div("form-group work-group-constraints") {
|
||||
label {
|
||||
+"Constraints"
|
||||
}
|
||||
span("form-btn work-group-constraints-add") {
|
||||
i("material-icons") { +"add" }
|
||||
}
|
||||
div("work-group-constraints-add-list") {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
div("form-group") {
|
||||
|
@ -511,6 +751,29 @@ fun Route.workGroup() {
|
|||
Language.values().find { l -> l.code == it } ?: Language.GERMAN
|
||||
}
|
||||
|
||||
val internet = params["internet"] == "on"
|
||||
val whiteboard = params["whiteboard"] == "on"
|
||||
val blackboard = params["blackboard"] == "on"
|
||||
val accessible = params["accessible"] == "on"
|
||||
|
||||
val constraints = params.mapNotNull { (key, value) ->
|
||||
when {
|
||||
key.startsWith("constraint-only-on-day") -> {
|
||||
value?.toLongOrNull()?.let { Constraint(ConstraintType.OnlyOnDay, it) }
|
||||
}
|
||||
key.startsWith("constraint-only-after-time") -> {
|
||||
value?.toLongOrNull()?.let { Constraint(ConstraintType.OnlyAfterTime, it) }
|
||||
}
|
||||
key.startsWith("constraint-not-at-same-time") -> {
|
||||
value?.toLongOrNull()?.let { Constraint(ConstraintType.NotAtSameTime, it) }
|
||||
}
|
||||
key.startsWith("constraint-only-after-work-group") -> {
|
||||
value?.toLongOrNull()?.let { Constraint(ConstraintType.OnlyAfterWorkGroup, it) }
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
val workGroup = WorkGroup(
|
||||
null,
|
||||
name = name,
|
||||
|
@ -518,8 +781,13 @@ fun Route.workGroup() {
|
|||
track = track,
|
||||
projector = projector,
|
||||
resolution = resolution,
|
||||
internet = internet,
|
||||
whiteboard = whiteboard,
|
||||
blackboard = blackboard,
|
||||
accessible = accessible,
|
||||
length = length,
|
||||
language = language
|
||||
language = language,
|
||||
constraints = constraints
|
||||
)
|
||||
|
||||
WorkGroupRepository.create(workGroup)
|
||||
|
|
47
src/jvmMain/kotlin/de/kif/backend/route/api/Constraints.kt
Normal file
47
src/jvmMain/kotlin/de/kif/backend/route/api/Constraints.kt
Normal file
|
@ -0,0 +1,47 @@
|
|||
package de.kif.backend.route.api
|
||||
|
||||
import de.kif.backend.authenticate
|
||||
import de.kif.backend.repository.ScheduleRepository
|
||||
import de.kif.common.checkConstraints
|
||||
import de.kif.common.model.Permission
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.routing.Route
|
||||
import io.ktor.routing.get
|
||||
|
||||
fun Route.constraintsApi() {
|
||||
get("/api/constraints") {
|
||||
try {
|
||||
authenticate(Permission.SCHEDULE) {
|
||||
val schedules = ScheduleRepository.all()
|
||||
|
||||
val errors = checkConstraints(schedules, schedules)
|
||||
|
||||
call.success(errors)
|
||||
} onFailure {
|
||||
call.error(HttpStatusCode.Unauthorized)
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
call.error(HttpStatusCode.InternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
get("/api/constraint/{id}") {
|
||||
try {
|
||||
authenticate(Permission.SCHEDULE) {
|
||||
val id = call.parameters["id"]?.toLongOrNull()
|
||||
val schedules = ScheduleRepository.all()
|
||||
|
||||
val check = schedules.filter { it.workGroup.id == id }
|
||||
|
||||
val errors = checkConstraints(check, schedules)
|
||||
|
||||
call.success(errors)
|
||||
} onFailure {
|
||||
call.error(HttpStatusCode.Unauthorized)
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
call.error(HttpStatusCode.InternalServerError)
|
||||
}
|
||||
}
|
||||
}
|
60
src/jvmMain/kotlin/de/kif/backend/util/ParseMarkdown.kt
Normal file
60
src/jvmMain/kotlin/de/kif/backend/util/ParseMarkdown.kt
Normal file
|
@ -0,0 +1,60 @@
|
|||
package de.kif.backend.util
|
||||
|
||||
import com.vladsch.flexmark.ext.autolink.AutolinkExtension
|
||||
import com.vladsch.flexmark.ext.emoji.EmojiExtension
|
||||
import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughExtension
|
||||
import com.vladsch.flexmark.ext.gfm.tasklist.TaskListExtension
|
||||
import com.vladsch.flexmark.ext.tables.TablesExtension
|
||||
import com.vladsch.flexmark.util.ast.Node;
|
||||
import com.vladsch.flexmark.html.HtmlRenderer;
|
||||
import com.vladsch.flexmark.parser.Parser;
|
||||
import com.vladsch.flexmark.util.options.MutableDataSet;
|
||||
import java.util.*
|
||||
|
||||
/*
|
||||
import org.intellij.markdown.flavours.commonmark.CommonMarkFlavourDescriptor
|
||||
import org.intellij.markdown.html.HtmlGenerator
|
||||
import org.intellij.markdown.parser.MarkdownParser
|
||||
|
||||
fun markdownToHtml(content: String): String {
|
||||
val flavour = CommonMarkFlavourDescriptor()
|
||||
val parsedTree = MarkdownParser(flavour).buildMarkdownTreeFromString(content)
|
||||
val html = HtmlGenerator(content, parsedTree, flavour).generateHtml()
|
||||
return html
|
||||
}
|
||||
*/
|
||||
/*
|
||||
import org.commonmark.parser.Parser
|
||||
import org.commonmark.renderer.html.HtmlRenderer
|
||||
|
||||
fun markdownToHtml(content: String): String {
|
||||
val parser = Parser.builder().build()
|
||||
val document = parser.parse(content)
|
||||
val renderer = HtmlRenderer.builder().build()
|
||||
return renderer.render(document)
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
fun markdownToHtml(content: String): String {
|
||||
val options = MutableDataSet()
|
||||
|
||||
options.set(Parser.EXTENSIONS, Arrays.asList(
|
||||
TablesExtension.create(),
|
||||
StrikethroughExtension.create(),
|
||||
TaskListExtension.create(),
|
||||
EmojiExtension.create(),
|
||||
AutolinkExtension.create()
|
||||
));
|
||||
|
||||
//options.set(HtmlRenderer.SOFT_BREAK, "<br />\n");
|
||||
|
||||
val parser = Parser.builder(options).build()
|
||||
val renderer = HtmlRenderer.builder(options).build()
|
||||
|
||||
// You can re-use parser and renderer instances
|
||||
val document = parser.parse(content)
|
||||
val html = renderer.render(document)
|
||||
|
||||
return html
|
||||
}
|
|
@ -50,4 +50,5 @@ fun Route.pushService() {
|
|||
TrackRepository.registerPushService()
|
||||
UserRepository.registerPushService()
|
||||
WorkGroupRepository.registerPushService()
|
||||
PostRepository.registerPushService()
|
||||
}
|
|
@ -7,14 +7,14 @@ import kotlinx.html.*
|
|||
|
||||
class MenuTemplate() : Template<FlowContent> {
|
||||
|
||||
var active: Tab = Tab.DASHBOARD
|
||||
var active: Tab = Tab.BOARD
|
||||
var user: User? = null
|
||||
|
||||
override fun FlowContent.apply() {
|
||||
nav("menu") {
|
||||
div("container") {
|
||||
div("menu-left") {
|
||||
a("/", classes = if (active == Tab.DASHBOARD) "active" else null) {
|
||||
a("/", classes = if (active == Tab.BOARD) "active" else null) {
|
||||
+"Dashboard"
|
||||
}
|
||||
a("/calendar", classes = if (active == Tab.CALENDAR) "active" else null) {
|
||||
|
@ -42,7 +42,7 @@ class MenuTemplate() : Template<FlowContent> {
|
|||
+"Rooms"
|
||||
}
|
||||
}
|
||||
if (user.checkPermission(Permission.PERSON)) {
|
||||
if (user.checkPermission(Permission.USER)) {
|
||||
a("/users", classes = if (active == Tab.USER) "active" else null) {
|
||||
+"Users"
|
||||
}
|
||||
|
@ -58,6 +58,6 @@ class MenuTemplate() : Template<FlowContent> {
|
|||
}
|
||||
|
||||
enum class Tab {
|
||||
DASHBOARD, CALENDAR, ACCOUNT, WORK_GROUP, ROOM, PERSON, USER
|
||||
BOARD, CALENDAR, ACCOUNT, WORK_GROUP, ROOM, PERSON, USER
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue