1
0
forked from SPC/spc-site

Fix all errors with new SNARK architecture.

This commit is contained in:
Alexander Nozik 2022-06-22 21:37:22 +03:00
parent 4966bfc9b3
commit 9fd4620947
No known key found for this signature in database
GPG Key ID: F7FCF2DD25C71357
7 changed files with 231 additions and 259 deletions

View File

@ -20,7 +20,7 @@
}); });
// Hack: Enable IE flexbox workarounds. // Hack: Enable IE flexbox workarounds.
if (browser.name == 'ie') if (browser.name === 'ie')
$body.addClass('is-ie'); $body.addClass('is-ie');
// Play initial animations on page load. // Play initial animations on page load.
@ -47,7 +47,8 @@
// Sidebar. // Sidebar.
if ($sidebar.length > 0) { if ($sidebar.length > 0) {
var $sidebar_a = $sidebar.find('a'); //adding exclusion for home link
var $sidebar_a = $sidebar.find('a').not('.spc-home');
$sidebar_a $sidebar_a
.addClass('scrolly') .addClass('scrolly')
@ -56,7 +57,7 @@
var $this = $(this); var $this = $(this);
// External link? Bail. // External link? Bail.
if ($this.attr('href').charAt(0) != '#') if ($this.attr('href').charAt(0) !== '#')
return; return;
// Deactivate all links. // Deactivate all links.
@ -95,7 +96,7 @@
$section.removeClass('inactive'); $section.removeClass('inactive');
// No locked links? Deactivate all links and activate this section's one. // No locked links? Deactivate all links and activate this section's one.
if ($sidebar_a.filter('.active-locked').length == 0) { if ($sidebar_a.filter('.active-locked').length === 0) {
$sidebar_a.removeClass('active'); $sidebar_a.removeClass('active');
$this.addClass('active'); $this.addClass('active');
@ -159,7 +160,7 @@
$image.css('background-image', 'url(' + $img.attr('src') + ')'); $image.css('background-image', 'url(' + $img.attr('src') + ')');
// Set background position. // Set background position.
if (x = $img.data('position')) if (x === $img.data('position'))
$image.css('background-position', x); $image.css('background-position', x);
// Hide <img>. // Hide <img>.

View File

@ -6,7 +6,9 @@ import kotlinx.css.CssBuilder
import kotlinx.html.CommonAttributeGroupFacade import kotlinx.html.CommonAttributeGroupFacade
import kotlinx.html.style import kotlinx.html.style
import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.fetch
import space.kscience.snark.SnarkPlugin import space.kscience.snark.SnarkPlugin
import space.kscience.snark.mountSnark
import java.net.URI import java.net.URI
import java.nio.file.FileSystems import java.nio.file.FileSystems
import java.nio.file.Files import java.nio.file.Files
@ -48,6 +50,7 @@ fun Application.spcModule() {
val context = Context("spc-site") { val context = Context("spc-site") {
plugin(SnarkPlugin) plugin(SnarkPlugin)
} }
val snarkPlugin = context.fetch(SnarkPlugin)
val dataPath = Path.of("data") val dataPath = Path.of("data")
@ -83,21 +86,21 @@ fun Application.spcModule() {
dataPath.resolve(DEPLOY_DATE_FILE).writeText(date) dataPath.resolve(DEPLOY_DATE_FILE).writeText(date)
} }
mountSnark(snarkPlugin) {
val homeDataPath = resolveData( val homeDataPath = resolveData(
javaClass.getResource("/home")!!.toURI(), javaClass.getResource("/home")!!.toURI(),
dataPath / "home" dataPath / "home"
) )
spcHome(context, rootPath = homeDataPath) spcHome(rootPath = homeDataPath)
val mastersDataPath = resolveData( val mastersDataPath = resolveData(
javaClass.getResource("/magprog")!!.toURI(), javaClass.getResource("/magprog")!!.toURI(),
dataPath / "magprog" dataPath / "magprog"
) )
spcMaster(context, dataPath = mastersDataPath) spcMaster(dataPath = mastersDataPath)
}
} }

View File

@ -1,17 +1,7 @@
package ru.mipt.spc package ru.mipt.spc
import io.ktor.server.application.Application
import io.ktor.server.application.call
import io.ktor.server.html.respondHtml
import io.ktor.server.http.content.files
import io.ktor.server.http.content.static
import io.ktor.server.routing.get
import io.ktor.server.routing.route
import io.ktor.server.routing.routing
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.html.* import kotlinx.html.*
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.fetch
import space.kscience.dataforge.data.await import space.kscience.dataforge.data.await
import space.kscience.dataforge.data.getByType import space.kscience.dataforge.data.getByType
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
@ -219,11 +209,6 @@ context(SiteData) private fun FlowContent.mentors() {
} }
} }
context(SiteData) internal fun FlowContent.contacts() {
}
context(SiteData) internal fun HTML.magProgHead(title: String) { context(SiteData) internal fun HTML.magProgHead(title: String) {
head { head {
this.title = title this.title = title
@ -292,26 +277,15 @@ context(SiteData) internal fun BODY.magProgFooter() {
private val HtmlData.mentorPageId get() = "mentor-${id}" private val HtmlData.mentorPageId get() = "mentor-${id}"
internal fun Application.spcMaster(context: Context, dataPath: Path, prefix: String = "/magprog") { internal fun SiteBuilder.spcMaster(dataPath: Path, prefix: Name = "magprog".asName()) {
val snark = context.fetch(SnarkPlugin) val magProgSiteContext = snark.readDirectory(dataPath.resolve("content"))
val magProgSiteContext: SiteData = snark.readDirectory(dataPath.resolve("content"), prefix) mountSite(prefix, magProgSiteContext) {
assetDirectory("", dataPath.resolve("assets"))
assetDirectory("images", dataPath.resolve("images"))
routing { page {
route(prefix) {
with(magProgSiteContext) {
static {
files(dataPath.resolve("assets").toFile())
static("images") {
files(dataPath.resolve("images").toFile())
}
}
get {
call.respondHtml {
val sections = listOf<MagProgSection>( val sections = listOf<MagProgSection>(
wrapSection(resolveHtml(INTRO_PATH)!!, "intro"), wrapSection(resolveHtml(INTRO_PATH)!!, "intro"),
MagProgSection( MagProgSection(
@ -350,7 +324,7 @@ internal fun Application.spcMaster(context: Context, dataPath: Path, prefix: Str
nav { nav {
ul { ul {
li { li {
a(href = "/"){ a(classes = "spc-home", href = "/") {
i("fa fa-home") { i("fa fa-home") {
attributes["aria-hidden"] = "true" attributes["aria-hidden"] = "true"
} }
@ -380,15 +354,15 @@ internal fun Application.spcMaster(context: Context, dataPath: Path, prefix: Str
magProgFooter() magProgFooter()
} }
} }
}
val mentors = findByType("magprog_mentor").values.sortedBy {
val mentors = data.findByType("magprog_mentor").values.sortedBy {
it.order it.order
} }
mentors.forEach { mentor -> mentors.forEach { mentor ->
get(mentor.mentorPageId) { page(mentor.mentorPageId.asName()) {
call.respondHtml {
magProgHead("Научное программирование: ${mentor.name}") magProgHead("Научное программирование: ${mentor.name}")
body("is-preload") { body("is-preload") {
header { header {
@ -434,7 +408,4 @@ internal fun Application.spcMaster(context: Context, dataPath: Path, prefix: Str
} }
} }
} }
}
}
}
} }

View File

@ -15,7 +15,7 @@ import kotlin.collections.component1
import kotlin.collections.component2 import kotlin.collections.component2
import kotlin.collections.set import kotlin.collections.set
context(SiteData) private fun FlowContent.spcSpotlightContent( context(SiteData, FlowContent) private fun spcSpotlightContent(
landing: HtmlData, landing: HtmlData,
content: Map<Name, HtmlData>, content: Map<Name, HtmlData>,
) { ) {
@ -88,13 +88,13 @@ context(SiteData) private fun FlowContent.spcSpotlightContent(
} }
context(SiteData) internal fun SiteBuilder.spcSpotlight( internal fun SiteBuilder.spcSpotlight(
address: String, address: String,
contentFilter: (Name, Meta) -> Boolean, contentFilter: (Name, Meta) -> Boolean,
) { ) {
val name = address.parseAsName() val name = address.parseAsName()
val body = resolveHtml(name) ?: error("Could not find body for $name") val body = data.resolveHtml(name) ?: error("Could not find body for $name")
val content = resolveAllHtml(contentFilter) val content = data.resolveAllHtml(contentFilter)
val meta = body.meta val meta = body.meta
page(name) { page(name) {
@ -110,7 +110,7 @@ context(SiteData) internal fun SiteBuilder.spcSpotlight(
} }
content.forEach { (name, contentBody) -> content.forEach { (name, contentBody) ->
page(name){ page(name) {
spcPageContent(contentBody.meta) { spcPageContent(contentBody.meta) {
htmlData(contentBody) htmlData(contentBody)
} }

View File

@ -1,14 +1,7 @@
package ru.mipt.spc package ru.mipt.spc
import html5up.forty.fortyScripts import html5up.forty.fortyScripts
import io.ktor.server.application.Application
import io.ktor.server.routing.route
import io.ktor.server.routing.routing
import kotlinx.html.* import kotlinx.html.*
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.Data import space.kscience.dataforge.data.Data
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.get import space.kscience.dataforge.meta.get
@ -59,32 +52,9 @@ context(SiteData) internal fun HTML.spcPageContent(
} }
} }
internal fun SiteBuilder.spcPage(subRoute: Name, meta: Meta, fragment: FlowContent.() -> Unit) {
page(subRoute) {
spcPageContent(meta, fragment = fragment)
}
}
internal fun SiteBuilder.spcPage(
subRoute: Name,
dataPath: Name = subRoute,
more: FlowContent.() -> Unit = {},
) {
val data = data.resolveHtml(dataPath)
if (data != null) {
spcPage(subRoute, data.meta) {
htmlData(data)
more()
}
} else {
logger.error { "Content for page with path $dataPath not found" }
}
}
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
internal val FortyDataRenderer: SiteBuilder.(Data<*>) -> Unit = { data -> internal val FortyDataRenderer: SiteBuilder.(Data<*>) -> Unit = { data ->
if(data.type == typeOf<HtmlFragment>()) { if (data.type == typeOf<HtmlFragment>()) {
data as Data<HtmlFragment> data as Data<HtmlFragment>
page { page {
spcPageContent(data.meta) { spcPageContent(data.meta) {
@ -94,32 +64,6 @@ internal val FortyDataRenderer: SiteBuilder.(Data<*>) -> Unit = { data ->
} }
} }
///**
// * Route a directory
// */
//internal fun SiteBuilder.spcDirectory(
// subRoute: String,
// dataPath: Name = subRoute.replace("/", ".").parseAsName(),
//) {
// data.filterByType<HtmlFragment> { 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, name, more)
}
context(SiteData, HTML) private fun HTML.spcHome() { context(SiteData, HTML) private fun HTML.spcHome() {
spcHead() spcHead()
@ -308,15 +252,11 @@ context(SiteData, HTML) private fun HTML.spcHome() {
} }
internal fun Application.spcHome(context: Context, rootPath: Path, prefix: String = "") { internal fun SiteBuilder.spcHome(rootPath: Path, prefix: Name = Name.EMPTY) {
val snark = context.fetch(SnarkPlugin) val homePageData = snark.readDirectory(rootPath.resolve("content"))
val homePageContext = snark.readDirectory(rootPath.resolve("content"), prefix) mountSite(prefix, homePageData) {
routing {
route(prefix) {
snarkSite(homePageContext) {
assetDirectory("assets", rootPath.resolve("assets")) assetDirectory("assets", rootPath.resolve("assets"))
assetDirectory("images", rootPath.resolve("images")) assetDirectory("images", rootPath.resolve("images"))
@ -325,9 +265,12 @@ internal fun Application.spcHome(context: Context, rootPath: Path, prefix: Strin
pages("consulting", dataRenderer = FortyDataRenderer) pages("consulting", dataRenderer = FortyDataRenderer)
//pages("ru.consulting".parseAsName(), dataRenderer = FortyDataRenderer) //pages("ru.consulting".parseAsName(), dataRenderer = FortyDataRenderer)
spcSpotlight("team") { _, m -> m["type"].string == "team" } spcSpotlight("team") { _, m ->
spcSpotlight("research") { name, m -> name.startsWith("projects".asName()) && m["type"].string == "project" } m["type"].string == "team"
} }
spcSpotlight("research") { name, m ->
name.startsWith("projects".asName()) && m["type"].string == "project"
} }
} }
} }

View File

@ -1,19 +1,23 @@
package space.kscience.snark package space.kscience.snark
import io.ktor.server.application.Application
import io.ktor.server.application.call import io.ktor.server.application.call
import io.ktor.server.html.respondHtml import io.ktor.server.html.respondHtml
import io.ktor.server.http.content.* import io.ktor.server.http.content.*
import io.ktor.server.routing.Route import io.ktor.server.routing.Route
import io.ktor.server.routing.createRouteFromPath import io.ktor.server.routing.createRouteFromPath
import io.ktor.server.routing.get import io.ktor.server.routing.get
import io.ktor.server.routing.routing
import kotlinx.html.HTML 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.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.parseAsName import space.kscience.dataforge.names.parseAsName
import java.nio.file.Path import java.nio.file.Path
internal fun Name.toWebPath() = tokens.joinToString(separator = "/") internal fun Name.toWebPath() = tokens.joinToString(separator = "/")
/** /**
* An abstraction, which is used to render sites to the different rendering engines * An abstraction, which is used to render sites to the different rendering engines
*/ */
@ -33,12 +37,19 @@ interface SiteBuilder : ContextAware {
fun page(route: Name = Name.EMPTY, content: context(SiteData, HTML) () -> Unit) fun page(route: Name = Name.EMPTY, content: context(SiteData, HTML) () -> Unit)
/**
* Create a new branch builder with replaced [data]
*/
fun withData(newData: SiteData): SiteBuilder
/** /**
* Create a route * Create a route
*/ */
fun route(subRoute: Name): SiteBuilder fun route(subRoute: Name): SiteBuilder
} }
val SiteBuilder.snark get() = data.snark
public inline fun SiteBuilder.route(route: Name, block: SiteBuilder.() -> Unit) { public inline fun SiteBuilder.route(route: Name, block: SiteBuilder.() -> Unit) {
route(route).apply(block) route(route).apply(block)
} }
@ -47,7 +58,22 @@ public inline fun SiteBuilder.route(route: String, block: SiteBuilder.() -> Unit
route(route.parseAsName()).apply(block) route(route.parseAsName()).apply(block)
} }
/**
* Create a stand-alone site at a given node
*/
public fun SiteBuilder.mountSite(route: Name, dataRoot: DataTree<*>, block: SiteBuilder.() -> Unit) {
val mountedData = data.copy(
data = dataRoot,
baseUrlPath = data.baseUrlPath.removeSuffix("/") + "/" + route.toWebPath(),
meta = dataRoot.meta // TODO consider meshing sub-site meta with the parent site
)
route(route) {
withData(mountedData).block()
}
}
class KtorSiteRoute(override val data: SiteData, private val ktorRoute: Route) : SiteBuilder { class KtorSiteRoute(override val data: SiteData, private val ktorRoute: Route) : SiteBuilder {
override fun assetFile(remotePath: String, file: Path) { override fun assetFile(remotePath: String, file: Path) {
ktorRoute.file(remotePath, file.toFile()) ktorRoute.file(remotePath, file.toFile())
} }
@ -61,7 +87,8 @@ class KtorSiteRoute(override val data: SiteData, private val ktorRoute: Route) :
override fun page(route: Name, content: context(SiteData, HTML)() -> Unit) { override fun page(route: Name, content: context(SiteData, HTML)() -> Unit) {
ktorRoute.get(route.toWebPath()) { ktorRoute.get(route.toWebPath()) {
call.respondHtml { call.respondHtml {
content(data.copyWithRequestHost(call.request), this) val dataWithUrl = data.copyWithRequestHost(call.request)
content(dataWithUrl, this)
} }
} }
} }
@ -76,11 +103,30 @@ class KtorSiteRoute(override val data: SiteData, private val ktorRoute: Route) :
override fun assetResourceDirectory(resourcesPath: String) { override fun assetResourceDirectory(resourcesPath: String) {
ktorRoute.resources(resourcesPath) ktorRoute.resources(resourcesPath)
} }
override fun withData(newData: SiteData): SiteBuilder = KtorSiteRoute(newData, ktorRoute)
} }
inline fun Route.snarkSite( inline fun Route.mountSnark(
siteContext: SiteData, data: SiteData,
block: context(SiteData, SiteBuilder)() -> Unit, block: context(SiteData, SiteBuilder)() -> Unit,
) { ) {
block(siteContext, KtorSiteRoute(siteContext, this@snarkSite)) block(data, KtorSiteRoute(data, this@mountSnark))
} }
fun Application.mountSnark(
data: SiteData,
block: context(SiteData, SiteBuilder)() -> Unit,
) {
routing {
mountSnark(data, block)
}
}
fun Application.mountSnark(
snarkPlugin: SnarkPlugin,
block: context(SiteData, SiteBuilder)() -> Unit,
) {
mountSnark(SiteData.empty(snarkPlugin), block)
}

View File

@ -13,16 +13,18 @@ import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.get import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.string 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.plus import space.kscience.dataforge.names.plus
import space.kscience.dataforge.names.startsWith import space.kscience.dataforge.names.startsWith
import space.kscience.snark.SiteData.Companion.INDEX_PAGE_NAME import space.kscience.snark.SiteData.Companion.INDEX_PAGE_NAME
import java.nio.file.Path import kotlin.reflect.KType
import kotlin.reflect.typeOf
data class SiteData( data class SiteData(
val snark: SnarkPlugin, val snark: SnarkPlugin,
val data: DataTree<*>, val data: DataTree<*>,
val urlPath: String, val baseUrlPath: String,
override val meta: Meta = data.meta override val meta: Meta = data.meta,
) : ContextAware, DataTree<Any> by data { ) : ContextAware, DataTree<Any> by data {
override val context: Context get() = snark.context override val context: Context get() = snark.context
@ -30,6 +32,19 @@ data class SiteData(
val language: String? by meta.string() val language: String? by meta.string()
companion object { companion object {
fun empty(
snark: SnarkPlugin,
baseUrlPath: String = "/",
meta: Meta = Meta.EMPTY,
): SiteData {
val emptyData = object : DataTree<Any> {
override val items: Map<NameToken, DataTreeItem<Any>> get() = emptyMap()
override val dataType: KType get() = typeOf<Any>()
override val meta: Meta get() = meta
}
return SiteData(snark, emptyData, baseUrlPath)
}
const val INDEX_PAGE_NAME: String = "index" const val INDEX_PAGE_NAME: String = "index"
} }
} }
@ -37,9 +52,9 @@ data class SiteData(
/** /**
* Resolve a resource full path by its name * Resolve a resource full path by its name
*/ */
fun SiteData.resolveRef(name: String): String = "${urlPath.removeSuffix("/")}/$name" fun SiteData.resolveRef(name: String): String = "${baseUrlPath.removeSuffix("/")}/$name"
fun SiteData.resolveRef(name: Name): String = "${urlPath.removeSuffix("/")}/${name.tokens.joinToString("/")}" fun SiteData.resolveRef(name: Name): String = "${baseUrlPath.removeSuffix("/")}/${name.tokens.joinToString("/")}"
/** /**
* Resolve a Html builder by its full name * Resolve a Html builder by its full name
@ -70,15 +85,15 @@ fun SiteData.findByType(contentType: String, baseName: Name = Name.EMPTY) = reso
} }
internal val Data<*>.published: Boolean get() = meta["published"].string != "false" internal val Data<*>.published: Boolean get() = meta["published"].string != "false"
//
fun SnarkPlugin.readData(data: DataTree<*>, rootUrl: String = "/"): SiteData = //fun SnarkPlugin.readData(data: DataTree<*>, rootUrl: String = "/"): SiteData =
SiteData(this, data, rootUrl) // SiteData(this, data, rootUrl)
//
fun SnarkPlugin.readDirectory(path: Path, rootUrl: String = "/"): SiteData { //fun SnarkPlugin.readDirectory(path: Path, rootUrl: String = "/"): SiteData {
val parsedData: DataTree<Any> = readDirectory(path) // val parsedData: DataTree<Any> = readDirectory(path)
//
return readData(parsedData, rootUrl) // return readData(parsedData, rootUrl)
} //}
@PublishedApi @PublishedApi
internal fun SiteData.copyWithRequestHost(request: ApplicationRequest): SiteData { internal fun SiteData.copyWithRequestHost(request: ApplicationRequest): SiteData {
@ -86,14 +101,7 @@ internal fun SiteData.copyWithRequestHost(request: ApplicationRequest): SiteData
protocol = URLProtocol.createOrDefault(request.origin.scheme), protocol = URLProtocol.createOrDefault(request.origin.scheme),
host = request.host(), host = request.host(),
port = request.port(), port = request.port(),
pathSegments = urlPath.split("/"), pathSegments = baseUrlPath.split("/"),
) )
return copy(urlPath = uri.buildString()) return copy(baseUrlPath = uri.buildString())
}
/**
* Substitute uri in [SiteData] with uri in the call to properly resolve relative refs. Only host properties are substituted.
*/
context(SiteData) inline fun withRequest(request: ApplicationRequest, block: context(SiteData) () -> Unit) {
block(copyWithRequestHost(request))
} }