Work on language support
This commit is contained in:
parent
62da832db1
commit
f43b23c84f
@ -1,3 +1,3 @@
|
|||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
|
|
||||||
toolsVersion=0.13.1-kotlin-1.7.20
|
toolsVersion=0.13.3-kotlin-1.7.20
|
@ -0,0 +1,83 @@
|
|||||||
|
package space.kscience.snark.html
|
||||||
|
|
||||||
|
import kotlinx.html.body
|
||||||
|
import kotlinx.html.head
|
||||||
|
import kotlinx.html.title
|
||||||
|
import space.kscience.dataforge.data.Data
|
||||||
|
import space.kscience.dataforge.data.getItem
|
||||||
|
import space.kscience.dataforge.meta.*
|
||||||
|
import space.kscience.dataforge.names.Name
|
||||||
|
import space.kscience.dataforge.names.appendLeft
|
||||||
|
import kotlin.reflect.typeOf
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render (or don't) given data piece
|
||||||
|
*/
|
||||||
|
public interface DataRenderer {
|
||||||
|
|
||||||
|
context(SiteBuilder)
|
||||||
|
public operator fun invoke(name: Name, data: Data<Any>)
|
||||||
|
|
||||||
|
public companion object {
|
||||||
|
|
||||||
|
// context (SiteBuilder)
|
||||||
|
// public fun buildPageMeta(name: Name, data: Data<Any>): Laminate {
|
||||||
|
// val languages = languages.mapKeys { it.value["key"]?.string ?: it.key }
|
||||||
|
//
|
||||||
|
// // detect current language by prefix if it is not defined explicitly
|
||||||
|
// val currentLanguage: String = data.meta["language"]?.string
|
||||||
|
// ?: languages.keys.firstOrNull() { key -> name.startsWith(key.parseAsName()) } ?: defaultLanguage
|
||||||
|
//
|
||||||
|
// //
|
||||||
|
// val languageMap = Meta {
|
||||||
|
// languages.forEach { (key, meta) ->
|
||||||
|
// val languagePrefix: String = meta.string ?: meta["name"]?.string ?: return@forEach
|
||||||
|
// val targetName = name.removeHeadOrNull("")
|
||||||
|
// val targetData = this@SiteBuilder.data[targetName.parseAsName()]
|
||||||
|
// if (targetData != null) key put targetName
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// val languageMeta = Meta {
|
||||||
|
// "language" put currentLanguage
|
||||||
|
// if (!languageMap.isEmpty()) {
|
||||||
|
// "languageMap" put languageMap
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return Laminate(data.meta, languageMeta, siteMeta)
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Automatically build a language map for a data piece with given [name] based on existence of appropriate data nodes.
|
||||||
|
*/
|
||||||
|
context(SiteBuilder)
|
||||||
|
public fun buildLanguageMeta(name: Name): Meta = Meta {
|
||||||
|
languages.forEach { (key, meta) ->
|
||||||
|
val languagePrefix = meta["prefix"].string ?: key
|
||||||
|
val nameWithLanguage: Name = if (languagePrefix.isBlank()) name else name.appendLeft(languagePrefix)
|
||||||
|
if (data.getItem(name) != null) {
|
||||||
|
key put meta.asMutableMeta().apply {
|
||||||
|
"target" put nameWithLanguage.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public val DEFAULT: DataRenderer = object : DataRenderer {
|
||||||
|
|
||||||
|
context(SiteBuilder)
|
||||||
|
override fun invoke(name: Name, data: Data<Any>) {
|
||||||
|
if (data.type == typeOf<HtmlData>()) {
|
||||||
|
page(name, data.meta) {
|
||||||
|
head {
|
||||||
|
title = data.meta["title"].string ?: "Untitled page"
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
htmlData(data as HtmlData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -26,13 +26,23 @@ public typealias HtmlData = Data<HtmlFragment>
|
|||||||
// Data(HtmlFragment(content), meta)
|
// Data(HtmlFragment(content), meta)
|
||||||
|
|
||||||
|
|
||||||
context(WebPage) public fun FlowContent.htmlData(data: HtmlData): Unit = runBlocking(Dispatchers.IO) {
|
context(WebPage)
|
||||||
|
public fun FlowContent.htmlData(data: HtmlData): Unit = runBlocking(Dispatchers.IO) {
|
||||||
with(data.await()) { consumer.renderFragment(page) }
|
with(data.await()) { consumer.renderFragment(page) }
|
||||||
}
|
}
|
||||||
|
|
||||||
context(SnarkContext) public val Data<*>.id: String get() = meta["id"]?.string ?: "block[${hashCode()}]"
|
context(SnarkContext)
|
||||||
context(SnarkContext) public val Data<*>.language: String? get() = meta["language"].string?.lowercase()
|
public val Data<*>.id: String
|
||||||
|
get() = meta["id"]?.string ?: "block[${hashCode()}]"
|
||||||
|
|
||||||
context(SnarkContext) public val Data<*>.order: Int? get() = meta["order"]?.int
|
context(SnarkContext)
|
||||||
|
public val Data<*>.language: String?
|
||||||
|
get() = meta["language"].string?.lowercase()
|
||||||
|
|
||||||
context(SnarkContext) public val Data<*>.published: Boolean get() = meta["published"].string != "false"
|
context(SnarkContext)
|
||||||
|
public val Data<*>.order: Int?
|
||||||
|
get() = meta["order"]?.int
|
||||||
|
|
||||||
|
context(SnarkContext)
|
||||||
|
public val Data<*>.published: Boolean
|
||||||
|
get() = meta["published"].string != "false"
|
||||||
|
@ -4,11 +4,18 @@ import kotlinx.html.HTML
|
|||||||
import space.kscience.dataforge.context.Context
|
import space.kscience.dataforge.context.Context
|
||||||
import space.kscience.dataforge.context.ContextAware
|
import space.kscience.dataforge.context.ContextAware
|
||||||
import space.kscience.dataforge.data.DataTree
|
import space.kscience.dataforge.data.DataTree
|
||||||
|
import space.kscience.dataforge.data.DataTreeItem
|
||||||
|
import space.kscience.dataforge.data.getItem
|
||||||
import space.kscience.dataforge.meta.Meta
|
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.Name
|
||||||
import space.kscience.dataforge.names.NameToken
|
import space.kscience.dataforge.names.NameToken
|
||||||
|
import space.kscience.dataforge.names.asName
|
||||||
import space.kscience.dataforge.names.parseAsName
|
import space.kscience.dataforge.names.parseAsName
|
||||||
import space.kscience.snark.SnarkContext
|
import space.kscience.snark.SnarkContext
|
||||||
|
import space.kscience.snark.html.SiteLayout.Companion.LAYOUT_KEY
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
|
||||||
@ -17,6 +24,11 @@ import java.nio.file.Path
|
|||||||
*/
|
*/
|
||||||
public interface SiteBuilder : ContextAware, SnarkContext {
|
public interface SiteBuilder : ContextAware, SnarkContext {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route name of this [SiteBuilder] relative to the site root
|
||||||
|
*/
|
||||||
|
public val route: Name
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data used for site construction. The type of the data is not limited
|
* Data used for site construction. The type of the data is not limited
|
||||||
*/
|
*/
|
||||||
@ -51,8 +63,10 @@ public interface SiteBuilder : ContextAware, SnarkContext {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a single page at given [route]. If route is empty, create an index page at current route.
|
* Create a single page at given [route]. If route is empty, create an index page at current route.
|
||||||
|
*
|
||||||
|
* @param pageMeta additional page meta. [WebPage] will use both it and [siteMeta]
|
||||||
*/
|
*/
|
||||||
public fun page(route: Name = Name.EMPTY, content: context(WebPage, HTML) () -> Unit)
|
public fun page(route: Name = Name.EMPTY, pageMeta: Meta = Meta.EMPTY, content: context(WebPage, HTML) () -> Unit)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a route with optional data tree override. For example one could use a subtree of the initial tree.
|
* Create a route with optional data tree override. For example one could use a subtree of the initial tree.
|
||||||
@ -61,36 +75,63 @@ public interface SiteBuilder : ContextAware, SnarkContext {
|
|||||||
public fun route(
|
public fun route(
|
||||||
routeName: Name,
|
routeName: Name,
|
||||||
dataOverride: DataTree<*>? = null,
|
dataOverride: DataTree<*>? = null,
|
||||||
metaOverride: Meta? = null,
|
routeMeta: Meta = Meta.EMPTY,
|
||||||
setAsRoot: Boolean = false,
|
|
||||||
): SiteBuilder
|
): SiteBuilder
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a route and sets it as site base url
|
||||||
|
*/
|
||||||
|
public fun site(
|
||||||
|
routeName: Name,
|
||||||
|
dataOverride: DataTree<*>? = null,
|
||||||
|
routeMeta: Meta = Meta.EMPTY,
|
||||||
|
): SiteBuilder
|
||||||
|
|
||||||
|
|
||||||
public companion object {
|
public companion object {
|
||||||
public val INDEX_PAGE_TOKEN: NameToken = NameToken("index")
|
public val INDEX_PAGE_TOKEN: NameToken = NameToken("index")
|
||||||
public val UP_PAGE_TOKEN: NameToken = NameToken("..")
|
public val UP_PAGE_TOKEN: NameToken = NameToken("..")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context(SiteBuilder) public val siteBuilder: SiteBuilder get() = this@SiteBuilder
|
context(SiteBuilder)
|
||||||
|
public val siteBuilder: SiteBuilder
|
||||||
|
get() = this@SiteBuilder
|
||||||
|
|
||||||
public inline fun SiteBuilder.route(
|
public inline fun SiteBuilder.route(
|
||||||
route: Name,
|
route: Name,
|
||||||
dataOverride: DataTree<*>? = null,
|
dataOverride: DataTree<*>? = null,
|
||||||
metaOverride: Meta? = null,
|
routeMeta: Meta = Meta.EMPTY,
|
||||||
setAsRoot: Boolean = false,
|
|
||||||
block: SiteBuilder.() -> Unit,
|
block: SiteBuilder.() -> Unit,
|
||||||
) {
|
) {
|
||||||
route(route, dataOverride, metaOverride, setAsRoot).apply(block)
|
route(route, dataOverride, routeMeta).apply(block)
|
||||||
}
|
}
|
||||||
|
|
||||||
public inline fun SiteBuilder.route(
|
public inline fun SiteBuilder.route(
|
||||||
route: String,
|
route: String,
|
||||||
dataOverride: DataTree<*>? = null,
|
dataOverride: DataTree<*>? = null,
|
||||||
metaOverride: Meta? = null,
|
routeMeta: Meta = Meta.EMPTY,
|
||||||
setAsRoot: Boolean = false,
|
|
||||||
block: SiteBuilder.() -> Unit,
|
block: SiteBuilder.() -> Unit,
|
||||||
) {
|
) {
|
||||||
route(route.parseAsName(), dataOverride, metaOverride, setAsRoot).apply(block)
|
route(route.parseAsName(), dataOverride, routeMeta).apply(block)
|
||||||
|
}
|
||||||
|
|
||||||
|
public inline fun SiteBuilder.site(
|
||||||
|
route: Name,
|
||||||
|
dataOverride: DataTree<*>? = null,
|
||||||
|
routeMeta: Meta = Meta.EMPTY,
|
||||||
|
block: SiteBuilder.() -> Unit,
|
||||||
|
) {
|
||||||
|
site(route, dataOverride, routeMeta).apply(block)
|
||||||
|
}
|
||||||
|
|
||||||
|
public inline fun SiteBuilder.site(
|
||||||
|
route: String,
|
||||||
|
dataOverride: DataTree<*>? = null,
|
||||||
|
routeMeta: Meta = Meta.EMPTY,
|
||||||
|
block: SiteBuilder.() -> Unit,
|
||||||
|
) {
|
||||||
|
site(route.parseAsName(), dataOverride, routeMeta).apply(block)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -107,3 +148,98 @@ public inline fun SiteBuilder.route(
|
|||||||
// withData(mountedData).block()
|
// withData(mountedData).block()
|
||||||
// }
|
// }
|
||||||
//}
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
internal fun SiteBuilder.assetsFrom(rootMeta: Meta) {
|
||||||
|
rootMeta.getIndexed("resource".asName()).forEach { (_, meta) ->
|
||||||
|
|
||||||
|
val path by meta.string()
|
||||||
|
val remotePath by meta.string()
|
||||||
|
|
||||||
|
path?.let { resourcePath ->
|
||||||
|
//If remote path provided, use a single resource
|
||||||
|
remotePath?.let {
|
||||||
|
resourceFile(it, resourcePath)
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
|
||||||
|
//otherwise use package resources
|
||||||
|
resourceDirectory(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") }
|
||||||
|
file(Path.of(path), remotePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
rootMeta.getIndexed("directory".asName()).forEach { (_, meta) ->
|
||||||
|
val path by meta.string { error("Directory path is not provided") }
|
||||||
|
file(Path.of(path), "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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].
|
||||||
|
*/
|
||||||
|
public fun SiteBuilder.pages(
|
||||||
|
data: DataTreeItem<*>,
|
||||||
|
dataRenderer: DataRenderer = DataRenderer.DEFAULT,
|
||||||
|
) {
|
||||||
|
val layoutMeta = data.meta[LAYOUT_KEY]
|
||||||
|
if (layoutMeta != null) {
|
||||||
|
//use layout if it is defined
|
||||||
|
snark.siteLayout(layoutMeta).render(data)
|
||||||
|
} else {
|
||||||
|
when (data) {
|
||||||
|
is DataTreeItem.Node -> {
|
||||||
|
data.tree.items.forEach { (token, item) ->
|
||||||
|
//Don't apply index token
|
||||||
|
if (token == SiteLayout.INDEX_PAGE_TOKEN) {
|
||||||
|
pages(item, dataRenderer)
|
||||||
|
} else if (item is DataTreeItem.Leaf) {
|
||||||
|
dataRenderer(token.asName(), item.data)
|
||||||
|
} else {
|
||||||
|
route(token.asName()) {
|
||||||
|
pages(item, dataRenderer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is DataTreeItem.Leaf -> {
|
||||||
|
dataRenderer(Name.EMPTY, data.data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data.meta[SiteLayout.ASSETS_KEY]?.let {
|
||||||
|
assetsFrom(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//TODO watch for changes
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render all pages in a node with given name
|
||||||
|
*/
|
||||||
|
public fun SiteBuilder.pages(
|
||||||
|
dataPath: Name,
|
||||||
|
remotePath: Name = dataPath,
|
||||||
|
dataRenderer: DataRenderer = DataRenderer.DEFAULT,
|
||||||
|
) {
|
||||||
|
val item = data.getItem(dataPath) ?: error("No data found by name $dataPath")
|
||||||
|
route(remotePath) {
|
||||||
|
pages(item, dataRenderer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun SiteBuilder.pages(
|
||||||
|
dataPath: String,
|
||||||
|
remotePath: Name = dataPath.parseAsName(),
|
||||||
|
dataRenderer: DataRenderer = DataRenderer.DEFAULT,
|
||||||
|
) {
|
||||||
|
pages(dataPath.parseAsName(), remotePath, dataRenderer = dataRenderer)
|
||||||
|
}
|
@ -1,121 +1,8 @@
|
|||||||
package space.kscience.snark.html
|
package space.kscience.snark.html
|
||||||
|
|
||||||
import kotlinx.html.body
|
|
||||||
import kotlinx.html.head
|
|
||||||
import kotlinx.html.title
|
|
||||||
import space.kscience.dataforge.data.Data
|
|
||||||
import space.kscience.dataforge.data.DataTreeItem
|
import space.kscience.dataforge.data.DataTreeItem
|
||||||
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.misc.Type
|
import space.kscience.dataforge.misc.Type
|
||||||
import space.kscience.dataforge.names.Name
|
|
||||||
import space.kscience.dataforge.names.NameToken
|
import space.kscience.dataforge.names.NameToken
|
||||||
import space.kscience.dataforge.names.asName
|
|
||||||
import space.kscience.dataforge.names.parseAsName
|
|
||||||
import space.kscience.snark.html.SiteLayout.Companion.ASSETS_KEY
|
|
||||||
import space.kscience.snark.html.SiteLayout.Companion.INDEX_PAGE_TOKEN
|
|
||||||
import space.kscience.snark.html.SiteLayout.Companion.LAYOUT_KEY
|
|
||||||
import java.nio.file.Path
|
|
||||||
import kotlin.reflect.typeOf
|
|
||||||
|
|
||||||
internal fun SiteBuilder.assetsFrom(rootMeta: Meta) {
|
|
||||||
rootMeta.getIndexed("resource".asName()).forEach { (_, meta) ->
|
|
||||||
|
|
||||||
val path by meta.string()
|
|
||||||
val remotePath by meta.string()
|
|
||||||
|
|
||||||
path?.let { resourcePath ->
|
|
||||||
//If remote path provided, use a single resource
|
|
||||||
remotePath?.let {
|
|
||||||
resourceFile(it, resourcePath)
|
|
||||||
return@forEach
|
|
||||||
}
|
|
||||||
|
|
||||||
//otherwise use package resources
|
|
||||||
resourceDirectory(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") }
|
|
||||||
file(Path.of(path), remotePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
rootMeta.getIndexed("directory".asName()).forEach { (_, meta) ->
|
|
||||||
val path by meta.string { error("Directory path is not provided") }
|
|
||||||
file(Path.of(path), "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render (or don't) given data piece
|
|
||||||
*/
|
|
||||||
public typealias DataRenderer = SiteBuilder.(name: Name, data: Data<Any>) -> Unit
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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].
|
|
||||||
*/
|
|
||||||
public fun SiteBuilder.pages(
|
|
||||||
data: DataTreeItem<*>,
|
|
||||||
dataRenderer: DataRenderer = SiteLayout.defaultDataRenderer,
|
|
||||||
) {
|
|
||||||
val layoutMeta = data.meta[LAYOUT_KEY]
|
|
||||||
if (layoutMeta != null) {
|
|
||||||
//use layout if it is defined
|
|
||||||
snark.siteLayout(layoutMeta).render(data)
|
|
||||||
} else {
|
|
||||||
when (data) {
|
|
||||||
is DataTreeItem.Node -> {
|
|
||||||
data.tree.items.forEach { (token, item) ->
|
|
||||||
//Don't apply index token
|
|
||||||
if (token == INDEX_PAGE_TOKEN) {
|
|
||||||
pages(item, dataRenderer)
|
|
||||||
} else if (item is DataTreeItem.Leaf) {
|
|
||||||
dataRenderer(this, token.asName(), item.data)
|
|
||||||
} else {
|
|
||||||
route(token.asName()) {
|
|
||||||
pages(item, dataRenderer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is DataTreeItem.Leaf -> {
|
|
||||||
dataRenderer.invoke(this, Name.EMPTY, data.data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
data.meta[ASSETS_KEY]?.let {
|
|
||||||
assetsFrom(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//TODO watch for changes
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render all pages in a node with given name
|
|
||||||
*/
|
|
||||||
public fun SiteBuilder.pages(
|
|
||||||
dataPath: Name,
|
|
||||||
remotePath: Name = dataPath,
|
|
||||||
dataRenderer: DataRenderer = SiteLayout.defaultDataRenderer,
|
|
||||||
) {
|
|
||||||
val item = data.getItem(dataPath) ?: error("No data found by name $dataPath")
|
|
||||||
route(remotePath) {
|
|
||||||
pages(item, dataRenderer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun SiteBuilder.pages(
|
|
||||||
dataPath: String,
|
|
||||||
remotePath: Name = dataPath.parseAsName(),
|
|
||||||
dataRenderer: DataRenderer = SiteLayout.defaultDataRenderer,
|
|
||||||
) {
|
|
||||||
pages(dataPath.parseAsName(), remotePath, dataRenderer = dataRenderer)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An abstraction to render singular data or a data tree.
|
* An abstraction to render singular data or a data tree.
|
||||||
@ -123,32 +10,20 @@ public fun SiteBuilder.pages(
|
|||||||
@Type(SiteLayout.TYPE)
|
@Type(SiteLayout.TYPE)
|
||||||
public fun interface SiteLayout {
|
public fun interface SiteLayout {
|
||||||
|
|
||||||
context(SiteBuilder) public fun render(item: DataTreeItem<*>)
|
context(SiteBuilder)
|
||||||
|
public fun render(item: DataTreeItem<*>)
|
||||||
|
|
||||||
public companion object {
|
public companion object {
|
||||||
public const val TYPE: String = "snark.layout"
|
public const val TYPE: String = "snark.layout"
|
||||||
public const val LAYOUT_KEY: String = "layout"
|
public const val LAYOUT_KEY: String = "layout"
|
||||||
public const val ASSETS_KEY: String = "assets"
|
public const val ASSETS_KEY: String = "assets"
|
||||||
public val INDEX_PAGE_TOKEN: NameToken = NameToken("index")
|
public val INDEX_PAGE_TOKEN: NameToken = NameToken("index")
|
||||||
|
|
||||||
public val defaultDataRenderer: SiteBuilder.(name: Name, data: Data<*>) -> Unit = { name: Name, data: Data<*> ->
|
|
||||||
if (data.type == typeOf<HtmlData>()) {
|
|
||||||
page(name) {
|
|
||||||
head {
|
|
||||||
title = data.meta["title"].string ?: "Untitled page"
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
htmlData(data as HtmlData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default [SiteLayout]. It renders all [HtmlData] pages as t with simple headers via [SiteLayout.defaultDataRenderer]
|
* The default [SiteLayout]. It renders all [HtmlData] pages with simple headers via [SiteLayout.defaultDataRenderer]
|
||||||
*/
|
*/
|
||||||
public object DefaultSiteLayout : SiteLayout {
|
public object DefaultSiteLayout : SiteLayout {
|
||||||
context(SiteBuilder) override fun render(item: DataTreeItem<*>) {
|
context(SiteBuilder) override fun render(item: DataTreeItem<*>) {
|
||||||
|
@ -4,10 +4,11 @@ import kotlinx.html.HTML
|
|||||||
import kotlinx.html.html
|
import kotlinx.html.html
|
||||||
import kotlinx.html.stream.createHTML
|
import kotlinx.html.stream.createHTML
|
||||||
import space.kscience.dataforge.data.DataTree
|
import space.kscience.dataforge.data.DataTree
|
||||||
|
import space.kscience.dataforge.meta.Laminate
|
||||||
import space.kscience.dataforge.meta.Meta
|
import space.kscience.dataforge.meta.Meta
|
||||||
import space.kscience.dataforge.meta.withDefault
|
|
||||||
import space.kscience.dataforge.names.Name
|
import space.kscience.dataforge.names.Name
|
||||||
import space.kscience.dataforge.names.isEmpty
|
import space.kscience.dataforge.names.isEmpty
|
||||||
|
import space.kscience.dataforge.names.plus
|
||||||
import space.kscience.snark.SnarkEnvironment
|
import space.kscience.snark.SnarkEnvironment
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
@ -24,6 +25,7 @@ internal class StaticSiteBuilder(
|
|||||||
override val data: DataTree<*>,
|
override val data: DataTree<*>,
|
||||||
override val siteMeta: Meta,
|
override val siteMeta: Meta,
|
||||||
private val baseUrl: String,
|
private val baseUrl: String,
|
||||||
|
override val route: Name,
|
||||||
private val outputPath: Path,
|
private val outputPath: Path,
|
||||||
) : SiteBuilder {
|
) : SiteBuilder {
|
||||||
private fun Path.copyRecursively(target: Path) {
|
private fun Path.copyRecursively(target: Path) {
|
||||||
@ -38,10 +40,10 @@ internal class StaticSiteBuilder(
|
|||||||
|
|
||||||
override fun file(file: Path, remotePath: String) {
|
override fun file(file: Path, remotePath: String) {
|
||||||
val targetPath = outputPath.resolve(remotePath)
|
val targetPath = outputPath.resolve(remotePath)
|
||||||
if(file.isDirectory()){
|
if (file.isDirectory()) {
|
||||||
targetPath.parent.createDirectories()
|
targetPath.parent.createDirectories()
|
||||||
file.copyRecursively(targetPath)
|
file.copyRecursively(targetPath)
|
||||||
} else if(remotePath.isBlank()) {
|
} else if (remotePath.isBlank()) {
|
||||||
error("Can't mount file to an empty route")
|
error("Can't mount file to an empty route")
|
||||||
} else {
|
} else {
|
||||||
targetPath.parent.createDirectories()
|
targetPath.parent.createDirectories()
|
||||||
@ -68,25 +70,25 @@ internal class StaticSiteBuilder(
|
|||||||
"${baseUrl.removeSuffix("/")}/$ref"
|
"${baseUrl.removeSuffix("/")}/$ref"
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class StaticWebPage : WebPage {
|
inner class StaticWebPage(override val pageMeta: Meta) : WebPage {
|
||||||
override val data: DataTree<*> get() = this@StaticSiteBuilder.data
|
override val data: DataTree<*> get() = this@StaticSiteBuilder.data
|
||||||
override val pageMeta: Meta get() = this@StaticSiteBuilder.siteMeta
|
|
||||||
override val snark: SnarkHtmlPlugin get() = this@StaticSiteBuilder.snark
|
override val snark: SnarkHtmlPlugin get() = this@StaticSiteBuilder.snark
|
||||||
|
|
||||||
|
|
||||||
override fun resolveRef(ref: String): String = resolveRef(baseUrl, ref)
|
override fun resolveRef(ref: String): String = resolveRef(baseUrl, ref)
|
||||||
|
|
||||||
override fun resolvePageRef(pageName: Name): String = resolveRef(
|
override fun resolvePageRef(pageName: Name, relative: Boolean): String = resolveRef(
|
||||||
pageName.toWebPath() + ".html"
|
(if (relative) route + pageName else pageName).toWebPath() + ".html"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun page(route: Name, content: context(WebPage, HTML) () -> Unit) {
|
override fun page(route: Name, pageMeta: Meta, content: context(WebPage, HTML) () -> Unit) {
|
||||||
val htmlBuilder = createHTML()
|
val htmlBuilder = createHTML()
|
||||||
|
|
||||||
htmlBuilder.html {
|
htmlBuilder.html {
|
||||||
content(StaticWebPage(), this)
|
content(StaticWebPage(pageMeta), this)
|
||||||
}
|
}
|
||||||
|
|
||||||
val newPath = if (route.isEmpty()) {
|
val newPath = if (route.isEmpty()) {
|
||||||
@ -102,23 +104,32 @@ internal class StaticSiteBuilder(
|
|||||||
override fun route(
|
override fun route(
|
||||||
routeName: Name,
|
routeName: Name,
|
||||||
dataOverride: DataTree<*>?,
|
dataOverride: DataTree<*>?,
|
||||||
metaOverride: Meta?,
|
routeMeta: Meta,
|
||||||
setAsRoot: Boolean,
|
|
||||||
): SiteBuilder = StaticSiteBuilder(
|
): SiteBuilder = StaticSiteBuilder(
|
||||||
snark = snark,
|
snark = snark,
|
||||||
data = dataOverride ?: data,
|
data = dataOverride ?: data,
|
||||||
siteMeta = metaOverride?.withDefault(siteMeta) ?: siteMeta,
|
siteMeta = Laminate(routeMeta, siteMeta),
|
||||||
baseUrl = if (setAsRoot) {
|
baseUrl = baseUrl,
|
||||||
resolveRef(baseUrl, routeName.toWebPath())
|
route = route + routeName,
|
||||||
} else {
|
outputPath = outputPath.resolve(routeName.toWebPath())
|
||||||
baseUrl
|
)
|
||||||
},
|
|
||||||
|
override fun site(
|
||||||
|
routeName: Name,
|
||||||
|
dataOverride: DataTree<*>?,
|
||||||
|
routeMeta: Meta,
|
||||||
|
): SiteBuilder = StaticSiteBuilder(
|
||||||
|
snark = snark,
|
||||||
|
data = dataOverride ?: data,
|
||||||
|
siteMeta = Laminate(routeMeta, siteMeta),
|
||||||
|
baseUrl = resolveRef(baseUrl, routeName.toWebPath()),
|
||||||
|
route = Name.EMPTY,
|
||||||
outputPath = outputPath.resolve(routeName.toWebPath())
|
outputPath = outputPath.resolve(routeName.toWebPath())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a static site using given [data] in provided [outputPath].
|
* Create a static site using given [SnarkEnvironment] in provided [outputPath].
|
||||||
* Use [siteUrl] as a base for all resolved URLs. By default, use [outputPath] absolute path as a base.
|
* Use [siteUrl] as a base for all resolved URLs. By default, use [outputPath] absolute path as a base.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@ -131,5 +142,5 @@ public fun SnarkEnvironment.static(
|
|||||||
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
|
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
|
||||||
}
|
}
|
||||||
val plugin = buildHtmlPlugin()
|
val plugin = buildHtmlPlugin()
|
||||||
StaticSiteBuilder(plugin, data, meta, siteUrl, outputPath).block()
|
StaticSiteBuilder(plugin, data, meta, siteUrl, Name.EMPTY, outputPath).block()
|
||||||
}
|
}
|
@ -11,7 +11,8 @@ import space.kscience.dataforge.names.parseAsName
|
|||||||
*/
|
*/
|
||||||
@Type(TextProcessor.TYPE)
|
@Type(TextProcessor.TYPE)
|
||||||
public fun interface TextProcessor {
|
public fun interface TextProcessor {
|
||||||
context(WebPage) public fun process(text: String): String
|
context(WebPage)
|
||||||
|
public fun process(text: String): String
|
||||||
|
|
||||||
public companion object {
|
public companion object {
|
||||||
public const val TYPE: String = "snark.textTransformation"
|
public const val TYPE: String = "snark.textTransformation"
|
||||||
@ -31,23 +32,27 @@ public object BasicTextProcessor : TextProcessor {
|
|||||||
|
|
||||||
private val regex = """\$\{([\w.]*)(?>\("(.*)"\))?}""".toRegex()
|
private val regex = """\$\{([\w.]*)(?>\("(.*)"\))?}""".toRegex()
|
||||||
|
|
||||||
context(WebPage) override fun process(text: String): String = text.replace(regex) { match ->
|
context(WebPage)
|
||||||
|
override fun process(text: String): String = text.replace(regex) { match ->
|
||||||
when (match.groups[1]!!.value) {
|
when (match.groups[1]!!.value) {
|
||||||
"homeRef" -> homeRef
|
"homeRef" -> homeRef
|
||||||
"resolveRef" -> {
|
"resolveRef" -> {
|
||||||
val refString = match.groups[2]?.value ?: error("resolveRef requires a string (quoted) argument")
|
val refString = match.groups[2]?.value ?: error("resolveRef requires a string (quoted) argument")
|
||||||
resolveRef(refString)
|
resolveRef(refString)
|
||||||
}
|
}
|
||||||
|
|
||||||
"resolvePageRef" -> {
|
"resolvePageRef" -> {
|
||||||
val refString = match.groups[2]?.value
|
val refString = match.groups[2]?.value
|
||||||
?: error("resolvePageRef requires a string (quoted) argument")
|
?: error("resolvePageRef requires a string (quoted) argument")
|
||||||
resolvePageRef(refString)
|
localisedPageRef(refString.parseAsName())
|
||||||
}
|
}
|
||||||
|
|
||||||
"pageMeta.get" -> {
|
"pageMeta.get" -> {
|
||||||
val nameString = match.groups[2]?.value
|
val nameString = match.groups[2]?.value
|
||||||
?: error("resolvePageRef requires a string (quoted) argument")
|
?: error("resolvePageRef requires a string (quoted) argument")
|
||||||
pageMeta[nameString.parseAsName()].string ?: "@null"
|
pageMeta[nameString.parseAsName()].string ?: "@null"
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> match.value
|
else -> match.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,8 @@ import space.kscience.dataforge.meta.string
|
|||||||
import space.kscience.dataforge.names.*
|
import space.kscience.dataforge.names.*
|
||||||
import space.kscience.snark.SnarkContext
|
import space.kscience.snark.SnarkContext
|
||||||
|
|
||||||
context(SnarkContext) public fun Name.toWebPath(): String = tokens.joinToString(separator = "/") {
|
context(SnarkContext)
|
||||||
|
public fun Name.toWebPath(): String = tokens.joinToString(separator = "/") {
|
||||||
if (it.hasIndex()) {
|
if (it.hasIndex()) {
|
||||||
"${it.body}[${it.index}]"
|
"${it.body}[${it.index}]"
|
||||||
} else {
|
} else {
|
||||||
@ -17,6 +18,9 @@ context(SnarkContext) public fun Name.toWebPath(): String = tokens.joinToString(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A context for building a single page
|
||||||
|
*/
|
||||||
public interface WebPage : ContextAware, SnarkContext {
|
public interface WebPage : ContextAware, SnarkContext {
|
||||||
|
|
||||||
public val snark: SnarkHtmlPlugin
|
public val snark: SnarkHtmlPlugin
|
||||||
@ -25,14 +29,28 @@ public interface WebPage : ContextAware, SnarkContext {
|
|||||||
|
|
||||||
public val data: DataTree<*>
|
public val data: DataTree<*>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A metadata for a page. It should include site metadata
|
||||||
|
*/
|
||||||
public val pageMeta: Meta
|
public val pageMeta: Meta
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve absolute url for given [ref]
|
||||||
|
*
|
||||||
|
*/
|
||||||
public fun resolveRef(ref: String): String
|
public fun resolveRef(ref: String): String
|
||||||
|
|
||||||
public fun resolvePageRef(pageName: Name): String
|
/**
|
||||||
|
* Resolve absolute url for a page with given [pageName].
|
||||||
|
*
|
||||||
|
* @param relative if true, add [SiteBuilder] route to the absolute page name
|
||||||
|
*/
|
||||||
|
public fun resolvePageRef(pageName: Name, relative: Boolean = false): String
|
||||||
}
|
}
|
||||||
|
|
||||||
context(WebPage) public val page: WebPage get() = this@WebPage
|
context(WebPage)
|
||||||
|
public val page: WebPage
|
||||||
|
get() = this@WebPage
|
||||||
|
|
||||||
public fun WebPage.resolvePageRef(pageName: String): String = resolvePageRef(pageName.parseAsName())
|
public fun WebPage.resolvePageRef(pageName: String): String = resolvePageRef(pageName.parseAsName())
|
||||||
|
|
||||||
@ -41,7 +59,8 @@ public val WebPage.homeRef: String get() = resolvePageRef(SiteBuilder.INDEX_PAGE
|
|||||||
/**
|
/**
|
||||||
* Resolve a Html builder by its full name
|
* Resolve a Html builder by its full name
|
||||||
*/
|
*/
|
||||||
context(SnarkContext) public fun DataTree<*>.resolveHtml(name: Name): HtmlData? {
|
context(SnarkContext)
|
||||||
|
public fun DataTree<*>.resolveHtml(name: Name): HtmlData? {
|
||||||
val resolved = (getByType<HtmlFragment>(name) ?: getByType<HtmlFragment>(name + SiteBuilder.INDEX_PAGE_TOKEN))
|
val resolved = (getByType<HtmlFragment>(name) ?: getByType<HtmlFragment>(name + SiteBuilder.INDEX_PAGE_TOKEN))
|
||||||
|
|
||||||
return resolved?.takeIf {
|
return resolved?.takeIf {
|
||||||
@ -52,7 +71,8 @@ context(SnarkContext) public fun DataTree<*>.resolveHtml(name: Name): HtmlData?
|
|||||||
/**
|
/**
|
||||||
* Find all Html blocks using given name/meta filter
|
* Find all Html blocks using given name/meta filter
|
||||||
*/
|
*/
|
||||||
context(SnarkContext) public fun DataTree<*>.resolveAllHtml(predicate: (name: Name, meta: Meta) -> Boolean): Map<Name, HtmlData> =
|
context(SnarkContext)
|
||||||
|
public fun DataTree<*>.resolveAllHtml(predicate: (name: Name, meta: Meta) -> Boolean): Map<Name, HtmlData> =
|
||||||
filterByType<HtmlFragment> { name, meta ->
|
filterByType<HtmlFragment> { name, meta ->
|
||||||
predicate(name, meta)
|
predicate(name, meta)
|
||||||
&& meta["published"].string != "false"
|
&& meta["published"].string != "false"
|
||||||
@ -60,7 +80,8 @@ context(SnarkContext) public fun DataTree<*>.resolveAllHtml(predicate: (name: Na
|
|||||||
}.asSequence().associate { it.name to it.data }
|
}.asSequence().associate { it.name to it.data }
|
||||||
|
|
||||||
|
|
||||||
context(SnarkContext) public fun DataTree<*>.findByContentType(
|
context(SnarkContext)
|
||||||
|
public fun DataTree<*>.findByContentType(
|
||||||
contentType: String,
|
contentType: String,
|
||||||
baseName: Name = Name.EMPTY,
|
baseName: Name = Name.EMPTY,
|
||||||
): Map<Name, Data<HtmlFragment>> = resolveAllHtml { name, meta ->
|
): Map<Name, Data<HtmlFragment>> = resolveAllHtml { name, meta ->
|
||||||
|
@ -0,0 +1,59 @@
|
|||||||
|
package space.kscience.snark.html
|
||||||
|
|
||||||
|
import space.kscience.dataforge.meta.Meta
|
||||||
|
import space.kscience.dataforge.meta.get
|
||||||
|
import space.kscience.dataforge.meta.string
|
||||||
|
import space.kscience.dataforge.names.Name
|
||||||
|
import space.kscience.dataforge.names.parseAsName
|
||||||
|
import space.kscience.dataforge.names.plus
|
||||||
|
|
||||||
|
public val SiteBuilder.languages: Map<String, Meta>
|
||||||
|
get() = siteMeta["site.languages"]?.items?.mapKeys { it.key.toStringUnescaped() } ?: emptyMap()
|
||||||
|
|
||||||
|
public val SiteBuilder.language: String
|
||||||
|
get() = siteMeta["site.language"].string ?: "en"
|
||||||
|
|
||||||
|
public fun SiteBuilder.withLanguages(languageMap: Map<String, Meta>, block: SiteBuilder.(language: String) -> Unit) {
|
||||||
|
languageMap.forEach { (languageKey, languageMeta) ->
|
||||||
|
val prefix = languageMeta["prefix"].string ?: languageKey
|
||||||
|
val routeMeta = Meta {
|
||||||
|
"site.language" put languageKey
|
||||||
|
"site.languages" put Meta {
|
||||||
|
languageMap.forEach {
|
||||||
|
it.key put it.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
route(prefix, routeMeta = routeMeta) {
|
||||||
|
block(languageKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun SiteBuilder.withLanguages(
|
||||||
|
vararg language: Pair<String, String>,
|
||||||
|
block: SiteBuilder.(language: String) -> Unit,
|
||||||
|
) {
|
||||||
|
val languageMap = language.associate {
|
||||||
|
it.first to Meta {
|
||||||
|
"prefix" put it.second
|
||||||
|
}
|
||||||
|
}
|
||||||
|
withLanguages(languageMap, block)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The language key of this page
|
||||||
|
*/
|
||||||
|
public val WebPage.language: String get() = pageMeta["language"]?.string ?: pageMeta["site.language"]?.string ?: "en"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapping of language keys to other language versions of this page
|
||||||
|
*/
|
||||||
|
public val WebPage.languages: Map<String, Meta>
|
||||||
|
get() = pageMeta["languages"]?.items?.mapKeys { it.key.toStringUnescaped() } ?: emptyMap()
|
||||||
|
|
||||||
|
public fun WebPage.localisedPageRef(pageName: Name, relative: Boolean = false): String {
|
||||||
|
val prefix = languages[language]?.get("prefix")?.string?.parseAsName() ?: Name.EMPTY
|
||||||
|
return resolvePageRef(prefix + pageName, relative)
|
||||||
|
}
|
@ -16,11 +16,12 @@ import kotlinx.html.CommonAttributeGroupFacade
|
|||||||
import kotlinx.html.HTML
|
import kotlinx.html.HTML
|
||||||
import kotlinx.html.style
|
import kotlinx.html.style
|
||||||
import space.kscience.dataforge.data.DataTree
|
import space.kscience.dataforge.data.DataTree
|
||||||
|
import space.kscience.dataforge.meta.Laminate
|
||||||
import space.kscience.dataforge.meta.Meta
|
import space.kscience.dataforge.meta.Meta
|
||||||
import space.kscience.dataforge.meta.withDefault
|
|
||||||
import space.kscience.dataforge.names.Name
|
import space.kscience.dataforge.names.Name
|
||||||
import space.kscience.dataforge.names.cutLast
|
import space.kscience.dataforge.names.cutLast
|
||||||
import space.kscience.dataforge.names.endsWith
|
import space.kscience.dataforge.names.endsWith
|
||||||
|
import space.kscience.dataforge.names.plus
|
||||||
import space.kscience.snark.SnarkEnvironment
|
import space.kscience.snark.SnarkEnvironment
|
||||||
import space.kscience.snark.html.*
|
import space.kscience.snark.html.*
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
@ -37,6 +38,7 @@ public class KtorSiteBuilder(
|
|||||||
override val data: DataTree<*>,
|
override val data: DataTree<*>,
|
||||||
override val siteMeta: Meta,
|
override val siteMeta: Meta,
|
||||||
private val baseUrl: String,
|
private val baseUrl: String,
|
||||||
|
override val route: Name,
|
||||||
private val ktorRoute: Route,
|
private val ktorRoute: Route,
|
||||||
) : SiteBuilder {
|
) : SiteBuilder {
|
||||||
|
|
||||||
@ -64,21 +66,27 @@ public class KtorSiteBuilder(
|
|||||||
|
|
||||||
private inner class KtorWebPage(
|
private inner class KtorWebPage(
|
||||||
val pageBaseUrl: String,
|
val pageBaseUrl: String,
|
||||||
override val pageMeta: Meta = this@KtorSiteBuilder.siteMeta,
|
override val pageMeta: Meta,
|
||||||
) : WebPage {
|
) : WebPage {
|
||||||
override val snark: SnarkHtmlPlugin get() = this@KtorSiteBuilder.snark
|
override val snark: SnarkHtmlPlugin get() = this@KtorSiteBuilder.snark
|
||||||
override val data: DataTree<*> get() = this@KtorSiteBuilder.data
|
override val data: DataTree<*> get() = this@KtorSiteBuilder.data
|
||||||
|
|
||||||
override fun resolveRef(ref: String): String = resolveRef(pageBaseUrl, ref)
|
override fun resolveRef(ref: String): String = resolveRef(pageBaseUrl, ref)
|
||||||
|
|
||||||
override fun resolvePageRef(pageName: Name): String = if (pageName.endsWith(SiteBuilder.INDEX_PAGE_TOKEN)) {
|
override fun resolvePageRef(
|
||||||
resolveRef(pageName.cutLast().toWebPath())
|
pageName: Name,
|
||||||
|
relative: Boolean,
|
||||||
|
): String {
|
||||||
|
val fullPageName = if(relative) route + pageName else pageName
|
||||||
|
return if (fullPageName.endsWith(SiteBuilder.INDEX_PAGE_TOKEN)) {
|
||||||
|
resolveRef(fullPageName.cutLast().toWebPath())
|
||||||
} else {
|
} else {
|
||||||
resolveRef(pageName.toWebPath())
|
resolveRef(fullPageName.toWebPath())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun page(route: Name, content: context(WebPage, HTML)() -> Unit) {
|
override fun page(route: Name, pageMeta: Meta, content: context(WebPage, HTML)() -> Unit) {
|
||||||
ktorRoute.get(route.toWebPath()) {
|
ktorRoute.get(route.toWebPath()) {
|
||||||
call.respondHtml {
|
call.respondHtml {
|
||||||
val request = call.request
|
val request = call.request
|
||||||
@ -89,7 +97,7 @@ public class KtorSiteBuilder(
|
|||||||
port = request.origin.port
|
port = request.origin.port
|
||||||
}
|
}
|
||||||
|
|
||||||
val pageBuilder = KtorWebPage(url.buildString())
|
val pageBuilder = KtorWebPage(url.buildString(), Laminate(pageMeta, siteMeta))
|
||||||
content(pageBuilder, this)
|
content(pageBuilder, this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -98,17 +106,26 @@ public class KtorSiteBuilder(
|
|||||||
override fun route(
|
override fun route(
|
||||||
routeName: Name,
|
routeName: Name,
|
||||||
dataOverride: DataTree<*>?,
|
dataOverride: DataTree<*>?,
|
||||||
metaOverride: Meta?,
|
routeMeta: Meta,
|
||||||
setAsRoot: Boolean,
|
|
||||||
): SiteBuilder = KtorSiteBuilder(
|
): SiteBuilder = KtorSiteBuilder(
|
||||||
snark = snark,
|
snark = snark,
|
||||||
data = dataOverride ?: data,
|
data = dataOverride ?: data,
|
||||||
siteMeta = metaOverride?.withDefault(siteMeta) ?: siteMeta,
|
siteMeta = Laminate(routeMeta, siteMeta),
|
||||||
baseUrl = if (setAsRoot) {
|
baseUrl = baseUrl,
|
||||||
resolveRef(baseUrl, routeName.toWebPath())
|
route = this.route + routeName,
|
||||||
} else {
|
ktorRoute = ktorRoute.createRouteFromPath(routeName.toWebPath())
|
||||||
baseUrl
|
)
|
||||||
},
|
|
||||||
|
override fun site(
|
||||||
|
routeName: Name,
|
||||||
|
dataOverride: DataTree<*>?,
|
||||||
|
routeMeta: Meta,
|
||||||
|
): SiteBuilder = KtorSiteBuilder(
|
||||||
|
snark = snark,
|
||||||
|
data = dataOverride ?: data,
|
||||||
|
siteMeta = Laminate(routeMeta, siteMeta),
|
||||||
|
baseUrl = resolveRef(baseUrl, routeName.toWebPath()),
|
||||||
|
route = Name.EMPTY,
|
||||||
ktorRoute = ktorRoute.createRouteFromPath(routeName.toWebPath())
|
ktorRoute = ktorRoute.createRouteFromPath(routeName.toWebPath())
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -122,17 +139,19 @@ public class KtorSiteBuilder(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context(Route, SnarkEnvironment) private fun siteInRoute(
|
context(Route, SnarkEnvironment)
|
||||||
|
private fun siteInRoute(
|
||||||
baseUrl: String = "",
|
baseUrl: String = "",
|
||||||
block: KtorSiteBuilder.() -> Unit,
|
block: KtorSiteBuilder.() -> Unit,
|
||||||
) {
|
) {
|
||||||
contract {
|
contract {
|
||||||
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
|
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
|
||||||
}
|
}
|
||||||
block(KtorSiteBuilder(buildHtmlPlugin(), data, meta, baseUrl, this@Route))
|
block(KtorSiteBuilder(buildHtmlPlugin(), data, meta, baseUrl, route = Name.EMPTY, this@Route))
|
||||||
}
|
}
|
||||||
|
|
||||||
context(Application) public fun SnarkEnvironment.site(
|
context(Application)
|
||||||
|
public fun SnarkEnvironment.site(
|
||||||
baseUrl: String = "",
|
baseUrl: String = "",
|
||||||
block: KtorSiteBuilder.() -> Unit,
|
block: KtorSiteBuilder.() -> Unit,
|
||||||
) {
|
) {
|
||||||
|
Loading…
Reference in New Issue
Block a user