diff --git a/build.gradle.kts b/build.gradle.kts index f1272e5..b9d5b2d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -29,6 +29,7 @@ dependencies { implementation("io.ktor:ktor-server-netty:$ktorVersion") implementation("io.ktor:ktor-server-http-redirect:$ktorVersion") + implementation("ch.qos.logback:logback-classic:1.2.11") testImplementation("io.ktor:ktor-server-tests:$ktorVersion") } @@ -43,7 +44,7 @@ apiValidation{ tasks.withType { kotlinOptions { - freeCompilerArgs = freeCompilerArgs + "-Xcontext-receivers" + freeCompilerArgs = freeCompilerArgs + "-Xcontext-receivers" } } diff --git a/data/common/android-chrome-192x192.png b/data/common/android-chrome-192x192.png new file mode 100644 index 0000000..6b59b36 Binary files /dev/null and b/data/common/android-chrome-192x192.png differ diff --git a/data/common/android-chrome-512x512.png b/data/common/android-chrome-512x512.png new file mode 100644 index 0000000..0eece2d Binary files /dev/null and b/data/common/android-chrome-512x512.png differ diff --git a/data/common/apple-touch-icon.png b/data/common/apple-touch-icon.png new file mode 100644 index 0000000..5c4b1ed Binary files /dev/null and b/data/common/apple-touch-icon.png differ diff --git a/data/common/favicon-16x16.png b/data/common/favicon-16x16.png new file mode 100644 index 0000000..eb5e0e5 Binary files /dev/null and b/data/common/favicon-16x16.png differ diff --git a/data/common/favicon-32x32.png b/data/common/favicon-32x32.png new file mode 100644 index 0000000..7296b2e Binary files /dev/null and b/data/common/favicon-32x32.png differ diff --git a/data/common/favicon.ico b/data/common/favicon.ico new file mode 100644 index 0000000..cef4411 Binary files /dev/null and b/data/common/favicon.ico differ diff --git a/data/common/site.webmanifest b/data/common/site.webmanifest new file mode 100644 index 0000000..45dc8a2 --- /dev/null +++ b/data/common/site.webmanifest @@ -0,0 +1 @@ +{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} \ No newline at end of file diff --git a/src/main/kotlin/html5up/forty/common.kt b/src/main/kotlin/html5up/forty/common.kt index 16c7be1..93e8ad0 100644 --- a/src/main/kotlin/html5up/forty/common.kt +++ b/src/main/kotlin/html5up/forty/common.kt @@ -1,7 +1,7 @@ package html5up.forty import kotlinx.html.* -import space.kscience.snark.html.Page +import space.kscience.snark.html.WebPage internal fun FlowContent.fortyMenu() { @@ -200,7 +200,7 @@ internal fun FlowContent.fortyFooter() { } } -context(Page) internal fun BODY.fortyScripts() { +context(WebPage) internal fun BODY.fortyScripts() { script { src = resolveRef("assets/js/jquery.min.js") } diff --git a/src/main/kotlin/html5up/forty/landing.kt b/src/main/kotlin/html5up/forty/landing.kt index 82e18ef..530242b 100644 --- a/src/main/kotlin/html5up/forty/landing.kt +++ b/src/main/kotlin/html5up/forty/landing.kt @@ -1,9 +1,9 @@ package html5up.forty import kotlinx.html.* -import space.kscience.snark.html.Page +import space.kscience.snark.html.WebPage -context(Page) internal fun HTML.landing(){ +context(WebPage) internal fun HTML.landing(){ head { title { } diff --git a/src/main/kotlin/html5up/forty/page.kt b/src/main/kotlin/html5up/forty/page.kt index 7bb7724..81d7128 100644 --- a/src/main/kotlin/html5up/forty/page.kt +++ b/src/main/kotlin/html5up/forty/page.kt @@ -1,9 +1,9 @@ package html5up.forty import kotlinx.html.* -import space.kscience.snark.html.Page +import space.kscience.snark.html.WebPage -context(Page) internal fun HTML.fortyPage(){ +context(WebPage) internal fun HTML.fortyPage(){ head { title { } diff --git a/src/main/kotlin/ru/mipt/spc/Application.kt b/src/main/kotlin/ru/mipt/spc/Application.kt index d4d1fed..dfd6c88 100644 --- a/src/main/kotlin/ru/mipt/spc/Application.kt +++ b/src/main/kotlin/ru/mipt/spc/Application.kt @@ -5,10 +5,8 @@ import io.ktor.server.application.log import kotlinx.css.CssBuilder import kotlinx.html.CommonAttributeGroupFacade import kotlinx.html.style -import space.kscience.dataforge.context.Context -import space.kscience.dataforge.context.fetch -import space.kscience.snark.html.SnarkPlugin -import space.kscience.snark.ktor.snarkSite +import space.kscience.snark.SnarkEnvironment +import space.kscience.snark.ktor.site import java.net.URI import java.nio.file.FileSystems import java.nio.file.Files @@ -49,11 +47,6 @@ const val BUILD_DATE_FILE = "/buildDate" fun Application.spcModule() { // install(HttpsRedirect) - val context = Context("spc-site") { - plugin(SnarkPlugin) - } - val snark = context.fetch(SnarkPlugin) - val dataPath = Path.of("data") // Clear data directory if it is outdated @@ -88,20 +81,20 @@ fun Application.spcModule() { dataPath.resolve(DEPLOY_DATE_FILE).writeText(date) } - snarkSite(snark) { + SnarkEnvironment.default.site { val homeDataPath = resolveData( this@spcModule.javaClass.getResource("/home")!!.toURI(), dataPath / "home" ) - spcHome(rootPath = homeDataPath) + spcHome(dataPath = homeDataPath) val mastersDataPath = resolveData( this@spcModule.javaClass.getResource("/magprog")!!.toURI(), dataPath / "magprog" ) - spcMaster(dataPath = mastersDataPath) + spcMasters(dataPath = mastersDataPath) } } diff --git a/src/main/kotlin/ru/mipt/spc/spcCollection.kt b/src/main/kotlin/ru/mipt/spc/spcCollection.kt index efcd0e4..aaf88b0 100644 --- a/src/main/kotlin/ru/mipt/spc/spcCollection.kt +++ b/src/main/kotlin/ru/mipt/spc/spcCollection.kt @@ -15,7 +15,7 @@ import kotlin.collections.component1 import kotlin.collections.component2 import kotlin.collections.set -context(Page) private fun FlowContent.spcSpotlightContent( +context(WebPage) private fun FlowContent.spcSpotlightContent( landing: HtmlData, content: Map, ) { diff --git a/src/main/kotlin/ru/mipt/spc/spcHome.kt b/src/main/kotlin/ru/mipt/spc/spcHome.kt index 850d247..b9c1120 100644 --- a/src/main/kotlin/ru/mipt/spc/spcHome.kt +++ b/src/main/kotlin/ru/mipt/spc/spcHome.kt @@ -15,7 +15,7 @@ import java.nio.file.Path import kotlin.reflect.typeOf -context(Page) internal fun HTML.spcPageContent( +context(WebPage) internal fun HTML.spcPageContent( meta: Meta, title: String = meta["title"].string ?: SPC_TITLE, fragment: FlowContent.() -> Unit, @@ -65,7 +65,7 @@ internal val FortyDataRenderer: DataRenderer = { name, data -> } -context(Page) private fun HTML.spcHome() { +context(WebPage) private fun HTML.spcHome() { spcHead() body("is-preload") { wrapper { @@ -252,13 +252,14 @@ context(Page) private fun HTML.spcHome() { } -internal fun SiteBuilder.spcHome(rootPath: Path, prefix: Name = Name.EMPTY) { +internal fun SiteBuilder.spcHome(dataPath: Path, prefix: Name = Name.EMPTY) { - val homePageData = snark.readDirectory(rootPath.resolve("content")) + val homePageData = snark.readDirectory(dataPath.resolve("content")) route(prefix, homePageData, setAsRoot = true) { - assetDirectory("assets", rootPath.resolve("assets")) - assetDirectory("images", rootPath.resolve("images")) + file(dataPath.resolve("assets")) + file(dataPath.resolve("images")) + file(dataPath.resolve("../common"), "") page { spcHome() } diff --git a/src/main/kotlin/ru/mipt/spc/master.kt b/src/main/kotlin/ru/mipt/spc/spcMasters.kt similarity index 91% rename from src/main/kotlin/ru/mipt/spc/master.kt rename to src/main/kotlin/ru/mipt/spc/spcMasters.kt index 02a8ef3..19b702f 100644 --- a/src/main/kotlin/ru/mipt/spc/master.kt +++ b/src/main/kotlin/ru/mipt/spc/spcMasters.kt @@ -15,7 +15,7 @@ import space.kscience.dataforge.names.plus import space.kscience.dataforge.names.withIndex import space.kscience.snark.* import space.kscience.snark.html.* -import space.kscience.snark.html.Page +import space.kscience.snark.html.WebPage import java.nio.file.Path import kotlin.collections.component1 import kotlin.collections.component2 @@ -37,14 +37,14 @@ import kotlin.collections.set private val HtmlData.imagePath: String? get() = meta["image"]?.string ?: meta["image.path"].string private val HtmlData.name: String get() = meta["name"].string ?: error("Name not found") -context(Page) class MagProgSection( +context(WebPage) class MagProgSection( val id: String, val title: String, val style: String, val content: FlowContent.() -> Unit, ) -context(Page) private fun wrapSection( +context(WebPage) private fun wrapSection( id: String, title: String, sectionContent: FlowContent.() -> Unit, @@ -55,7 +55,7 @@ context(Page) private fun wrapSection( } } -context(Page) private fun wrapSection( +context(WebPage) private fun wrapSection( block: HtmlData, idOverride: String? = null, ): MagProgSection = wrapSection( @@ -73,7 +73,7 @@ private val PROGRAM_PATH: Name = CONTENT_NODE_NAME + "program" private val RECOMMENDED_COURSES_PATH: Name = CONTENT_NODE_NAME + "recommendedCourses" private val PARTNERS_PATH: Name = CONTENT_NODE_NAME + "partners" -context(Page) private fun FlowContent.programSection() { +context(WebPage) private fun FlowContent.programSection() { val programBlock = data.resolveHtml(PROGRAM_PATH)!! val recommendedBlock = data.resolveHtml(RECOMMENDED_COURSES_PATH)!! div("inner") { @@ -90,7 +90,7 @@ context(Page) private fun FlowContent.programSection() { } } -context(Page) private fun FlowContent.partners() { +context(WebPage) private fun FlowContent.partners() { //val partnersData: Meta = resolve(PARTNERS_PATH)?.meta ?: Meta.EMPTY val partnersData: Meta = runBlocking { data.getByType(PARTNERS_PATH)?.await() } ?: Meta.EMPTY div("inner") { @@ -120,7 +120,7 @@ context(Page) private fun FlowContent.partners() { // val photo: String? by meta.string() //} -context(Page) private fun FlowContent.team() { +context(WebPage) private fun FlowContent.team() { val team = data.findByContentType("magprog_team").values.sortedBy { it.order } div("inner") { @@ -175,7 +175,7 @@ context(Page) private fun FlowContent.team() { // } } -context(Page) private fun FlowContent.mentors() { +context(WebPage) private fun FlowContent.mentors() { val mentors = data.findByContentType("magprog_mentor").entries.sortedBy { it.value.id } div("inner") { @@ -213,7 +213,7 @@ context(Page) private fun FlowContent.mentors() { } } -context(Page) internal fun HTML.magProgHead(title: String) { +context(WebPage) internal fun HTML.magProgHead(title: String) { head { this.title = title meta { @@ -237,10 +237,31 @@ context(Page) internal fun HTML.magProgHead(title: String) { href = resolveRef("assets/css/noscript.css") } } + link { + rel = "apple-touch-icon" + sizes = "180x180" + href = "/apple-touch-icon.png" + } + link { + rel = "icon" + type = "image/png" + sizes = "32x32" + href = "/favicon-32x32.png" + } + link { + rel = "icon" + type = "image/png" + sizes = "16x16" + href = "/favicon-16x16.png" + } + link { + rel = "manifest" + href = "/site.webmanifest" + } } } -context(Page) internal fun BODY.magProgFooter() { +context(WebPage) internal fun BODY.magProgFooter() { footer("wrapper style1-alt") { id = "footer" div("inner") { @@ -281,13 +302,14 @@ context(Page) internal fun BODY.magProgFooter() { context(SnarkContext) private val HtmlData.mentorPageId get() = "mentor-${id}" -internal fun SiteBuilder.spcMaster(dataPath: Path, prefix: Name = "magprog".asName()) { +internal fun SiteBuilder.spcMasters(dataPath: Path, prefix: Name = "magprog".asName()) { val magProgData: DataTree = snark.readDirectory(dataPath.resolve("content")) route(prefix, magProgData, setAsRoot = true) { - assetDirectory("assets", dataPath.resolve("assets")) - assetDirectory("images", dataPath.resolve("images")) + file(dataPath.resolve("assets")) + file(dataPath.resolve("images")) + file(dataPath.resolve("../common"), "") page { val sections = listOf( diff --git a/src/main/kotlin/ru/mipt/spc/spcMisc.kt b/src/main/kotlin/ru/mipt/spc/spcMisc.kt index 042913c..36ca434 100644 --- a/src/main/kotlin/ru/mipt/spc/spcMisc.kt +++ b/src/main/kotlin/ru/mipt/spc/spcMisc.kt @@ -1,14 +1,14 @@ package ru.mipt.spc import kotlinx.html.* -import space.kscience.snark.html.Page +import space.kscience.snark.html.WebPage import space.kscience.snark.html.homeRef import space.kscience.snark.html.resolvePageRef internal const val SPC_TITLE = "Scientific Programming Centre" -context(Page) internal fun HTML.spcHead(title: String = SPC_TITLE) { +context(WebPage) internal fun HTML.spcHead(title: String = SPC_TITLE) { head { title { +title @@ -24,10 +24,31 @@ context(Page) internal fun HTML.spcHead(title: String = SPC_TITLE) { noScript { link(rel = "stylesheet", href = resolveRef("assets/css/noscript.css")) } + link { + rel = "apple-touch-icon" + sizes = "180x180" + href = "/apple-touch-icon.png" + } + link { + rel = "icon" + type = "image/png" + sizes = "32x32" + href = "/favicon-32x32.png" + } + link { + rel = "icon" + type = "image/png" + sizes = "16x16" + href = "/favicon-16x16.png" + } + link { + rel = "manifest" + href = "/site.webmanifest" + } } } -context(Page) internal fun FlowContent.spcHomeMenu() { +context(WebPage) internal fun FlowContent.spcHomeMenu() { nav { id = "menu" ul("links") { @@ -39,7 +60,7 @@ context(Page) internal fun FlowContent.spcHomeMenu() { } li { a { - href = resolvePageRef("magprog") + href = resolvePageRef("magprog.index") +"""Master""" } } @@ -51,7 +72,7 @@ context(Page) internal fun FlowContent.spcHomeMenu() { } li { a { - href = resolvePageRef("consulting") + href = resolvePageRef("consulting.index") +"""Consulting""" } } @@ -79,7 +100,7 @@ context(Page) internal fun FlowContent.spcHomeMenu() { } } -context(Page) internal fun FlowContent.spcFooter() { +context(WebPage) internal fun FlowContent.spcFooter() { footer { id = "footer" div("inner") { @@ -129,7 +150,7 @@ context(Page) internal fun FlowContent.spcFooter() { } } -context(Page) internal fun FlowContent.wrapper(contentBody: FlowContent.() -> Unit) { +context(WebPage) internal fun FlowContent.wrapper(contentBody: FlowContent.() -> Unit) { div { id = "wrapper" // Header diff --git a/src/main/kotlin/ru/mipt/spc/staticRender.kt b/src/main/kotlin/ru/mipt/spc/staticRender.kt index c6f59ec..4af783e 100644 --- a/src/main/kotlin/ru/mipt/spc/staticRender.kt +++ b/src/main/kotlin/ru/mipt/spc/staticRender.kt @@ -1,15 +1,13 @@ package ru.mipt.spc -import space.kscience.dataforge.context.Global -import space.kscience.dataforge.context.fetch -import space.kscience.snark.html.SnarkPlugin -import space.kscience.snark.html.renderStatic +import space.kscience.snark.SnarkEnvironment +import space.kscience.snark.html.static import java.nio.file.Path import kotlin.io.path.toPath fun main() { - Global.fetch(SnarkPlugin).renderStatic(Path.of("build/out")) { - spcHome(rootPath = javaClass.getResource("/home")!!.toURI().toPath()) - spcMaster(dataPath = javaClass.getResource("/magprog")!!.toURI().toPath()) + SnarkEnvironment.default.static(Path.of("build/out")) { + spcHome(dataPath = javaClass.getResource("/home")!!.toURI().toPath()) + spcMasters(dataPath = javaClass.getResource("/magprog")!!.toURI().toPath()) } } \ No newline at end of file