From 4966bfc9b333689eaaecff6cc03c54fda522d42c Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 22 Jun 2022 12:18:35 +0300 Subject: [PATCH] Refactor page placement. --- src/main/kotlin/html5up/forty/common.kt | 4 +- src/main/kotlin/html5up/forty/landing.kt | 4 +- src/main/kotlin/html5up/forty/page.kt | 4 +- src/main/kotlin/ru/mipt/spc/master.kt | 16 +-- src/main/kotlin/ru/mipt/spc/spcCollection.kt | 11 ++- src/main/kotlin/ru/mipt/spc/spcHome.kt | 80 ++++++++------- src/main/kotlin/ru/mipt/spc/spcMisc.kt | 10 +- .../space/kscience/snark/SiteBuilder.kt | 49 ++++++---- .../snark/{SiteContext.kt => SiteData.kt} | 44 ++++----- .../kotlin/space/kscience/snark/SiteLayout.kt | 97 +++++++++++++------ src/main/kotlin/space/kscience/snark/site.kt | 26 ----- 11 files changed, 189 insertions(+), 156 deletions(-) rename src/main/kotlin/space/kscience/snark/{SiteContext.kt => SiteData.kt} (57%) delete mode 100644 src/main/kotlin/space/kscience/snark/site.kt diff --git a/src/main/kotlin/html5up/forty/common.kt b/src/main/kotlin/html5up/forty/common.kt index df8acde..e0378ad 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.SiteContext +import space.kscience.snark.SiteData import space.kscience.snark.resolveRef @@ -201,7 +201,7 @@ internal fun FlowContent.fortyFooter() { } } -context(SiteContext) internal fun BODY.fortyScripts() { +context(SiteData) 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 b18919e..1974c49 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.SiteContext +import space.kscience.snark.SiteData -context(SiteContext) internal fun HTML.landing(){ +context(SiteData) 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 c933dda..6dcc315 100644 --- a/src/main/kotlin/html5up/forty/page.kt +++ b/src/main/kotlin/html5up/forty/page.kt @@ -1,10 +1,10 @@ package html5up.forty import kotlinx.html.* -import space.kscience.snark.SiteContext +import space.kscience.snark.SiteData import space.kscience.snark.resolveRef -context(SiteContext) internal fun HTML.fortyPage(){ +context(SiteData) internal fun HTML.fortyPage(){ head { title { } diff --git a/src/main/kotlin/ru/mipt/spc/master.kt b/src/main/kotlin/ru/mipt/spc/master.kt index 3347957..d2cf0f6 100644 --- a/src/main/kotlin/ru/mipt/spc/master.kt +++ b/src/main/kotlin/ru/mipt/spc/master.kt @@ -80,7 +80,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(SiteContext) private fun FlowContent.programSection() { +context(SiteData) private fun FlowContent.programSection() { val programBlock = resolveHtml(PROGRAM_PATH)!! val recommendedBlock = resolveHtml(RECOMMENDED_COURSES_PATH)!! div("inner") { @@ -97,7 +97,7 @@ context(SiteContext) private fun FlowContent.programSection() { } } -context(SiteContext) private fun FlowContent.partners() { +context(SiteData) 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") { @@ -127,7 +127,7 @@ context(SiteContext) private fun FlowContent.partners() { // val photo: String? by meta.string() //} -context(SiteContext) private fun FlowContent.team() { +context(SiteData) private fun FlowContent.team() { val team = findByType("magprog_team").values.sortedBy { it.order } div("inner") { @@ -182,7 +182,7 @@ context(SiteContext) private fun FlowContent.team() { // } } -context(SiteContext) private fun FlowContent.mentors() { +context(SiteData) private fun FlowContent.mentors() { val mentors = findByType("magprog_mentor").entries.sortedBy { it.value.id } div("inner") { @@ -219,12 +219,12 @@ context(SiteContext) private fun FlowContent.mentors() { } } -context(SiteContext) internal fun FlowContent.contacts() { +context(SiteData) internal fun FlowContent.contacts() { } -context(SiteContext) internal fun HTML.magProgHead(title: String) { +context(SiteData) internal fun HTML.magProgHead(title: String) { head { this.title = title meta { @@ -251,7 +251,7 @@ context(SiteContext) internal fun HTML.magProgHead(title: String) { } } -context(SiteContext) internal fun BODY.magProgFooter() { +context(SiteData) internal fun BODY.magProgFooter() { footer("wrapper style1-alt") { id = "footer" div("inner") { @@ -296,7 +296,7 @@ internal fun Application.spcMaster(context: Context, dataPath: Path, prefix: Str val snark = context.fetch(SnarkPlugin) - val magProgSiteContext: SiteContext = snark.read(dataPath.resolve("content"), prefix) + val magProgSiteContext: SiteData = snark.readDirectory(dataPath.resolve("content"), prefix) routing { route(prefix) { diff --git a/src/main/kotlin/ru/mipt/spc/spcCollection.kt b/src/main/kotlin/ru/mipt/spc/spcCollection.kt index 352bc34..5cadd52 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(SiteContext) private fun FlowContent.spcSpotlightContent( +context(SiteData) private fun FlowContent.spcSpotlightContent( landing: HtmlData, content: Map, ) { @@ -88,11 +88,12 @@ context(SiteContext) private fun FlowContent.spcSpotlightContent( } -context(SiteContext) internal fun SiteBuilder.spcSpotlight( - name: String, +context(SiteData) internal fun SiteBuilder.spcSpotlight( + address: String, contentFilter: (Name, Meta) -> Boolean, ) { - val body = resolveHtml(name.parseAsName()) ?: error("Could not find body for $name") + val name = address.parseAsName() + val body = resolveHtml(name) ?: error("Could not find body for $name") val content = resolveAllHtml(contentFilter) val meta = body.meta @@ -109,7 +110,7 @@ context(SiteContext) internal fun SiteBuilder.spcSpotlight( } content.forEach { (name, contentBody) -> - page(name.tokens.joinToString("/")){ + page(name){ spcPageContent(contentBody.meta) { htmlData(contentBody) } diff --git a/src/main/kotlin/ru/mipt/spc/spcHome.kt b/src/main/kotlin/ru/mipt/spc/spcHome.kt index f5f0e51..da4ca09 100644 --- a/src/main/kotlin/ru/mipt/spc/spcHome.kt +++ b/src/main/kotlin/ru/mipt/spc/spcHome.kt @@ -9,18 +9,20 @@ import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.error import space.kscience.dataforge.context.fetch import space.kscience.dataforge.context.logger -import space.kscience.dataforge.data.filterByType -import space.kscience.dataforge.data.forEach +import space.kscience.dataforge.data.Data import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.get import space.kscience.dataforge.meta.string -import space.kscience.dataforge.names.* +import space.kscience.dataforge.names.Name +import space.kscience.dataforge.names.asName +import space.kscience.dataforge.names.startsWith import space.kscience.dataforge.values.string import space.kscience.snark.* import java.nio.file.Path +import kotlin.reflect.typeOf -context(SiteContext) internal fun HTML.spcPageContent( +context(SiteData) internal fun HTML.spcPageContent( meta: Meta, title: String = meta["title"].string ?: SPC_TITLE, fragment: FlowContent.() -> Unit, @@ -58,18 +60,18 @@ context(SiteContext) internal fun HTML.spcPageContent( } -context(SiteContext) internal fun SiteBuilder.spcPage(subRoute: String, meta: Meta, fragment: FlowContent.() -> Unit) { +internal fun SiteBuilder.spcPage(subRoute: Name, meta: Meta, fragment: FlowContent.() -> Unit) { page(subRoute) { spcPageContent(meta, fragment = fragment) } } -context(SiteContext) internal fun SiteBuilder.spcPage( - subRoute: String, - dataPath: Name = subRoute.replace("/", ".").parseAsName(), +internal fun SiteBuilder.spcPage( + subRoute: Name, + dataPath: Name = subRoute, more: FlowContent.() -> Unit = {}, ) { - val data = resolveHtml(dataPath) + val data = data.resolveHtml(dataPath) if (data != null) { spcPage(subRoute, data.meta) { htmlData(data) @@ -80,34 +82,46 @@ context(SiteContext) internal fun SiteBuilder.spcPage( } } -/** - * Route a directory - */ -context(SiteContext) internal fun SiteBuilder.spcDirectory( - subRoute: String, - dataPath: Name = subRoute.replace("/", ".").parseAsName(), -) { - data.filterByType { name, _ -> name.startsWith(dataPath) }.forEach { html -> - val pageName = if (html.name.lastOrNull()?.body == SiteContext.INDEX_PAGE_NAME) { - html.name.cutLast() - } else { - html.name - } - - spcPage(pageName.tokens.joinToString(separator = "/"), html.meta) { - htmlData(html) +@Suppress("UNCHECKED_CAST") +internal val FortyDataRenderer: SiteBuilder.(Data<*>) -> Unit = { data -> + if(data.type == typeOf()) { + data as Data + page { + spcPageContent(data.meta) { + htmlData(data) + } } } } -context(SiteContext) internal fun SiteBuilder.spcPage( +///** +// * Route a directory +// */ +//internal fun SiteBuilder.spcDirectory( +// subRoute: String, +// dataPath: Name = subRoute.replace("/", ".").parseAsName(), +//) { +// data.filterByType { name, _ -> name.startsWith(dataPath) }.forEach { html -> +// val pageName = if (html.name.lastOrNull()?.body == SiteData.INDEX_PAGE_NAME) { +// html.name.cutLast() +// } else { +// html.name +// } +// +// spcPage(pageName.tokens.joinToString(separator = "/"), html.meta) { +// htmlData(html) +// } +// } +//} + +internal fun SiteBuilder.spcPage( name: Name, more: FlowContent.() -> Unit = {}, ) { - spcPage(name.tokens.joinToString("/"), name, more) + spcPage(name, name, more) } -context(SiteContext, HTML) private fun HTML.spcHome() { +context(SiteData, HTML) private fun HTML.spcHome() { spcHead() body("is-preload") { wrapper { @@ -298,18 +312,18 @@ internal fun Application.spcHome(context: Context, rootPath: Path, prefix: Strin val snark = context.fetch(SnarkPlugin) - val homePageContext = snark.read(rootPath.resolve("content"), prefix) + val homePageContext = snark.readDirectory(rootPath.resolve("content"), prefix) routing { route(prefix) { snarkSite(homePageContext) { - staticDirectory("assets", rootPath.resolve("assets")) - staticDirectory("images", rootPath.resolve("images")) + assetDirectory("assets", rootPath.resolve("assets")) + assetDirectory("images", rootPath.resolve("images")) page { spcHome() } - spcDirectory("consulting") - spcDirectory("ru/consulting") + pages("consulting", dataRenderer = FortyDataRenderer) + //pages("ru.consulting".parseAsName(), dataRenderer = FortyDataRenderer) spcSpotlight("team") { _, m -> m["type"].string == "team" } spcSpotlight("research") { name, m -> name.startsWith("projects".asName()) && m["type"].string == "project" } diff --git a/src/main/kotlin/ru/mipt/spc/spcMisc.kt b/src/main/kotlin/ru/mipt/spc/spcMisc.kt index b6725f3..cd29478 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.SiteContext +import space.kscience.snark.SiteData import space.kscience.snark.homeRef import space.kscience.snark.resolveRef internal const val SPC_TITLE = "Scientific Programming Centre" -context(SiteContext) internal fun HTML.spcHead(title: String = SPC_TITLE) { +context(SiteData) internal fun HTML.spcHead(title: String = SPC_TITLE) { head { title { +title @@ -27,7 +27,7 @@ context(SiteContext) internal fun HTML.spcHead(title: String = SPC_TITLE) { } } -context(SiteContext) internal fun FlowContent.spcHomeMenu() { +context(SiteData) internal fun FlowContent.spcHomeMenu() { nav { id = "menu" ul("links") { @@ -79,7 +79,7 @@ context(SiteContext) internal fun FlowContent.spcHomeMenu() { } } -context(SiteContext) internal fun FlowContent.spcFooter() { +context(SiteData) internal fun FlowContent.spcFooter() { footer { id = "footer" div("inner") { @@ -129,7 +129,7 @@ context(SiteContext) internal fun FlowContent.spcFooter() { } } -context(SiteContext) internal fun FlowContent.wrapper(contentBody: FlowContent.() -> Unit) { +context(SiteData) internal fun FlowContent.wrapper(contentBody: FlowContent.() -> Unit) { div { id = "wrapper" // Header diff --git a/src/main/kotlin/space/kscience/snark/SiteBuilder.kt b/src/main/kotlin/space/kscience/snark/SiteBuilder.kt index 3733bdf..d76c32a 100644 --- a/src/main/kotlin/space/kscience/snark/SiteBuilder.kt +++ b/src/main/kotlin/space/kscience/snark/SiteBuilder.kt @@ -9,71 +9,78 @@ import io.ktor.server.routing.get import kotlinx.html.HTML import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.ContextAware +import space.kscience.dataforge.names.Name +import space.kscience.dataforge.names.parseAsName import java.nio.file.Path +internal fun Name.toWebPath() = tokens.joinToString(separator = "/") /** * An abstraction, which is used to render sites to the different rendering engines */ interface SiteBuilder : ContextAware { - val siteContext: SiteContext + val data: SiteData - override val context: Context get() = siteContext.context + override val context: Context get() = data.context - fun staticFile(remotePath: String, file: Path) + fun assetFile(remotePath: String, file: Path) - fun staticDirectory(remotePath: String, directory: Path) + fun assetDirectory(remotePath: String, directory: Path) - fun staticResourceFile(remotePath: String, resourcesPath: String) + fun assetResourceFile(remotePath: String, resourcesPath: String) - fun staticResourceDirectory(resourcesPath: String) + fun assetResourceDirectory(resourcesPath: String) - fun page(route: String = "", content: context(SiteContext, HTML) () -> Unit) + fun page(route: Name = Name.EMPTY, content: context(SiteData, HTML) () -> Unit) /** * Create a route */ - fun route(subRoute: String): SiteBuilder + fun route(subRoute: Name): SiteBuilder } -public inline fun SiteBuilder.route(route: String, block: SiteBuilder.() -> Unit) { +public inline fun SiteBuilder.route(route: Name, block: SiteBuilder.() -> Unit) { route(route).apply(block) } -class KtorSiteRoute(override val siteContext: SiteContext, private val ktorRoute: Route) : SiteBuilder { - override fun staticFile(remotePath: String, file: Path) { +public inline fun SiteBuilder.route(route: String, block: SiteBuilder.() -> Unit) { + route(route.parseAsName()).apply(block) +} + +class KtorSiteRoute(override val data: SiteData, private val ktorRoute: Route) : SiteBuilder { + override fun assetFile(remotePath: String, file: Path) { ktorRoute.file(remotePath, file.toFile()) } - override fun staticDirectory(remotePath: String, directory: Path) { + override fun assetDirectory(remotePath: String, directory: Path) { ktorRoute.static(remotePath) { files(directory.toFile()) } } - override fun page(route: String, content: context(SiteContext, HTML)() -> Unit) { - ktorRoute.get(route) { + override fun page(route: Name, content: context(SiteData, HTML)() -> Unit) { + ktorRoute.get(route.toWebPath()) { call.respondHtml { - content(siteContext.copyWithRequestHost(call.request), this) + content(data.copyWithRequestHost(call.request), this) } } } - override fun route(subRoute: String): SiteBuilder = - KtorSiteRoute(siteContext, ktorRoute.createRouteFromPath(subRoute)) + override fun route(subRoute: Name): SiteBuilder = + KtorSiteRoute(data, ktorRoute.createRouteFromPath(subRoute.toWebPath())) - override fun staticResourceFile(remotePath: String, resourcesPath: String) { + override fun assetResourceFile(remotePath: String, resourcesPath: String) { ktorRoute.resource(resourcesPath, resourcesPath) } - override fun staticResourceDirectory(resourcesPath: String) { + override fun assetResourceDirectory(resourcesPath: String) { ktorRoute.resources(resourcesPath) } } inline fun Route.snarkSite( - siteContext: SiteContext, - block: context(SiteContext, SiteBuilder)() -> Unit, + siteContext: SiteData, + block: context(SiteData, SiteBuilder)() -> Unit, ) { block(siteContext, KtorSiteRoute(siteContext, this@snarkSite)) } \ No newline at end of file diff --git a/src/main/kotlin/space/kscience/snark/SiteContext.kt b/src/main/kotlin/space/kscience/snark/SiteData.kt similarity index 57% rename from src/main/kotlin/space/kscience/snark/SiteContext.kt rename to src/main/kotlin/space/kscience/snark/SiteData.kt index b125140..e4b5f72 100644 --- a/src/main/kotlin/space/kscience/snark/SiteContext.kt +++ b/src/main/kotlin/space/kscience/snark/SiteData.kt @@ -15,15 +15,15 @@ import space.kscience.dataforge.meta.string import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.plus import space.kscience.dataforge.names.startsWith -import space.kscience.snark.SiteContext.Companion.INDEX_PAGE_NAME +import space.kscience.snark.SiteData.Companion.INDEX_PAGE_NAME import java.nio.file.Path -data class SiteContext( +data class SiteData( val snark: SnarkPlugin, - val path: String, - val meta: Meta, val data: DataTree<*>, -) : ContextAware { + val urlPath: String, + override val meta: Meta = data.meta +) : ContextAware, DataTree by data { override val context: Context get() = snark.context @@ -37,15 +37,15 @@ data class SiteContext( /** * Resolve a resource full path by its name */ -fun SiteContext.resolveRef(name: String): String = "${path.removeSuffix("/")}/$name" +fun SiteData.resolveRef(name: String): String = "${urlPath.removeSuffix("/")}/$name" -fun SiteContext.resolveRef(name: Name): String = "${path.removeSuffix("/")}/${name.tokens.joinToString("/")}" +fun SiteData.resolveRef(name: Name): String = "${urlPath.removeSuffix("/")}/${name.tokens.joinToString("/")}" /** * Resolve a Html builder by its full name */ -fun SiteContext.resolveHtml(name: Name): HtmlData? { - val resolved = (data.getByType(name) ?: data.getByType(name + INDEX_PAGE_NAME)) +fun DataTree<*>.resolveHtml(name: Name): HtmlData? { + val resolved = (getByType(name) ?: getByType(name + INDEX_PAGE_NAME)) return resolved?.takeIf { it.published //TODO add language confirmation @@ -55,45 +55,45 @@ fun SiteContext.resolveHtml(name: Name): HtmlData? { /** * Find all Html blocks using given name/meta filter */ -fun SiteContext.resolveAllHtml(predicate: (name: Name, meta: Meta) -> Boolean): Map = - data.filterByType { name, meta -> +fun DataTree<*>.resolveAllHtml(predicate: (name: Name, meta: Meta) -> Boolean): Map = + filterByType { name, meta -> predicate(name, meta) && meta["published"].string != "false" //TODO add language confirmation }.asSequence().associate { it.name to it.data } -val SiteContext.homeRef get() = resolveRef("").removeSuffix("/") +val SiteData.homeRef get() = resolveRef("").removeSuffix("/") -fun SiteContext.findByType(contentType: String, baseName: Name = Name.EMPTY) = resolveAllHtml { name, meta -> +fun SiteData.findByType(contentType: String, baseName: Name = Name.EMPTY) = resolveAllHtml { name, meta -> name.startsWith(baseName) && meta["content_type"].string == contentType } internal val Data<*>.published: Boolean get() = meta["published"].string != "false" -fun SnarkPlugin.siteContext(rootUrl: String, data: DataTree<*>): SiteContext = - SiteContext(this, rootUrl, data.meta, data) +fun SnarkPlugin.readData(data: DataTree<*>, rootUrl: String = "/"): SiteData = + SiteData(this, data, rootUrl) -fun SnarkPlugin.read(path: Path, rootUrl: String = "/"): SiteContext { +fun SnarkPlugin.readDirectory(path: Path, rootUrl: String = "/"): SiteData { val parsedData: DataTree = readDirectory(path) - return siteContext(rootUrl, parsedData) + return readData(parsedData, rootUrl) } @PublishedApi -internal fun SiteContext.copyWithRequestHost(request: ApplicationRequest): SiteContext { +internal fun SiteData.copyWithRequestHost(request: ApplicationRequest): SiteData { val uri = URLBuilder( protocol = URLProtocol.createOrDefault(request.origin.scheme), host = request.host(), port = request.port(), - pathSegments = path.split("/"), + pathSegments = urlPath.split("/"), ) - return copy(path = uri.buildString()) + return copy(urlPath = uri.buildString()) } /** - * Substitute uri in [SiteContext] with uri in the call to properly resolve relative refs. Only host properties are substituted. + * Substitute uri in [SiteData] with uri in the call to properly resolve relative refs. Only host properties are substituted. */ -context(SiteContext) inline fun withRequest(request: ApplicationRequest, block: context(SiteContext) () -> Unit) { +context(SiteData) inline fun withRequest(request: ApplicationRequest, block: context(SiteData) () -> Unit) { block(copyWithRequestHost(request)) } \ No newline at end of file diff --git a/src/main/kotlin/space/kscience/snark/SiteLayout.kt b/src/main/kotlin/space/kscience/snark/SiteLayout.kt index b638c4a..cf82290 100644 --- a/src/main/kotlin/space/kscience/snark/SiteLayout.kt +++ b/src/main/kotlin/space/kscience/snark/SiteLayout.kt @@ -1,22 +1,25 @@ package space.kscience.snark import kotlinx.coroutines.runBlocking -import space.kscience.dataforge.data.DataTree +import space.kscience.dataforge.data.Data import space.kscience.dataforge.data.DataTreeItem import space.kscience.dataforge.data.await +import space.kscience.dataforge.data.getItem import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.get import space.kscience.dataforge.meta.getIndexed import space.kscience.dataforge.meta.string import space.kscience.dataforge.names.Name +import space.kscience.dataforge.names.NameToken import space.kscience.dataforge.names.asName -import space.kscience.dataforge.names.plus -import space.kscience.snark.SiteLayout.Companion.DESIGNATION_KEY +import space.kscience.dataforge.names.parseAsName +import space.kscience.snark.SiteLayout.Companion.ASSETS_KEY +import space.kscience.snark.SiteLayout.Companion.INDEX_PAGE_TOKEN import space.kscience.snark.SiteLayout.Companion.LAYOUT_KEY import java.nio.file.Path import kotlin.reflect.typeOf -internal fun SiteBuilder.staticFrom(rootMeta: Meta) { +internal fun SiteBuilder.assetsFrom(rootMeta: Meta) { rootMeta.getIndexed("resource".asName()).forEach { (_, meta) -> val path by meta.string() @@ -25,75 +28,109 @@ internal fun SiteBuilder.staticFrom(rootMeta: Meta) { path?.let { resourcePath -> //If remote path provided, use a single resource remotePath?.let { - staticResourceFile(it, resourcePath) + assetResourceFile(it, resourcePath) return@forEach } - //otherwise use package resources - staticResourceDirectory(resourcePath) + assetResourceDirectory(resourcePath) } } rootMeta.getIndexed("file".asName()).forEach { (_, meta) -> val remotePath by meta.string { error("File remote path is not provided") } val path by meta.string { error("File path is not provided") } - staticFile(remotePath, Path.of(path)) + assetFile(remotePath, Path.of(path)) } rootMeta.getIndexed("directory".asName()).forEach { (_, meta) -> val path by meta.string { error("Directory path is not provided") } - staticDirectory("", Path.of(path)) + assetDirectory("", Path.of(path)) } } /** - * Represent pages in a [DataTree] + * Recursively renders the data items in [data]. If [LAYOUT_KEY] is defined in an item, use it to load + * layout from the context, otherwise render children nodes as name segments and individual data items using [dataRenderer]. */ -fun SiteBuilder.data(data: DataTreeItem<*>, prefix: Name = Name.EMPTY) { +fun SiteBuilder.pages( + data: DataTreeItem<*>, + dataRenderer: SiteBuilder.(Data<*>) -> Unit = SiteLayout.defaultDataRenderer, +) { val layoutMeta = data.meta[LAYOUT_KEY] if (layoutMeta != null) { //use layout if it is defined - siteContext.snark.layout(layoutMeta).render(data) + this.data.snark.layout(layoutMeta).render(data) } else { when (data) { is DataTreeItem.Node -> { data.tree.items.forEach { (token, item) -> - data(item, prefix + token) - } - } - is DataTreeItem.Leaf -> { - val item = data.data - if (item.type == typeOf() && item.meta[DESIGNATION_KEY].string == "page") { - route(prefix.tokens.joinToString(separator = "/")) { - page { - @Suppress("UNCHECKED_CAST") - val pageFragment: HtmlFragment = runBlocking { item.await() as HtmlFragment } - pageFragment.invoke(consumer) - } - staticFrom(item.meta) + //Don't apply index token + if (token == INDEX_PAGE_TOKEN) { + pages(item, dataRenderer) + } + route(token.toString()) { + pages(item, dataRenderer) } } } + is DataTreeItem.Leaf -> { + dataRenderer.invoke(this, data.data) + } + } + data.meta[ASSETS_KEY]?.let { + assetsFrom(it) } - } //TODO watch for changes } +/** + * Render all pages in a node with given name + */ +fun SiteBuilder.pages( + dataPath: Name, + remotePath: Name = dataPath, + dataRenderer: SiteBuilder.(Data<*>) -> Unit = SiteLayout.defaultDataRenderer, +) { + val item = data.getItem(dataPath) ?: error("No data found by name $dataPath") + route(remotePath) { + pages(item, dataRenderer) + } +} + +fun SiteBuilder.pages( + dataPath: String, + remotePath: Name = dataPath.parseAsName(), + dataRenderer: SiteBuilder.(Data<*>) -> Unit = SiteLayout.defaultDataRenderer, +) { + pages(dataPath.parseAsName(), remotePath, dataRenderer = dataRenderer) +} + fun interface SiteLayout { - context(SiteBuilder) fun render(data: DataTreeItem<*>) + context(SiteBuilder) fun render(item: DataTreeItem<*>) companion object { - internal const val DESIGNATION_KEY = "designation" const val LAYOUT_KEY = "layout" + const val ASSETS_KEY = "assets" + val INDEX_PAGE_TOKEN = NameToken("index") + + val defaultDataRenderer: SiteBuilder.(Data<*>) -> Unit = { data -> + if (data.type == typeOf()) { + page { + @Suppress("UNCHECKED_CAST") + val pageFragment: HtmlFragment = runBlocking { data.await() as HtmlFragment } + pageFragment.invoke(consumer) + } + } + } } } object DefaultSiteLayout : SiteLayout { - context(SiteBuilder) override fun render(data: DataTreeItem<*>) { - data(data) + context(SiteBuilder) override fun render(item: DataTreeItem<*>) { + pages(item) } } \ No newline at end of file diff --git a/src/main/kotlin/space/kscience/snark/site.kt b/src/main/kotlin/space/kscience/snark/site.kt deleted file mode 100644 index 72b585d..0000000 --- a/src/main/kotlin/space/kscience/snark/site.kt +++ /dev/null @@ -1,26 +0,0 @@ -package ru.mipt.spc - -//class SiteBuilderAction : AbstractAction(typeOf()) { -// -// private val pageBuilders = HashMap) -> HtmlData>() -// -// fun page(name: Name, meta: Meta = Meta.EMPTY, builder: context(PageContext) TagConsumer<*>.() -> Unit) { -// val prefix = name.tokens.joinToString(separator = "/", prefix = "/") -// pageBuilders[name] = { dataset -> -// val fragment: HtmlFragment = { -// builder.invoke(PageContext(prefix, dataset), this) -// } -// Data(fragment, meta.copy { -// "name" put name.toString() -// }) -// } -// } -// -// -// override fun DataSetBuilder.generate(data: DataSet, meta: Meta) { -// pageBuilders.forEach { (name, builder) -> -// data(name, builder(data)) -// } -// } -// -//} \ No newline at end of file