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.
if (browser.name == 'ie')
if (browser.name === 'ie')
$body.addClass('is-ie');
// Play initial animations on page load.
@ -47,7 +47,8 @@
// Sidebar.
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
.addClass('scrolly')
@ -56,7 +57,7 @@
var $this = $(this);
// External link? Bail.
if ($this.attr('href').charAt(0) != '#')
if ($this.attr('href').charAt(0) !== '#')
return;
// Deactivate all links.
@ -95,7 +96,7 @@
$section.removeClass('inactive');
// 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');
$this.addClass('active');
@ -159,7 +160,7 @@
$image.css('background-image', 'url(' + $img.attr('src') + ')');
// Set background position.
if (x = $img.data('position'))
if (x === $img.data('position'))
$image.css('background-position', x);
// Hide <img>.

View File

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

View File

@ -1,17 +1,7 @@
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.html.*
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.fetch
import space.kscience.dataforge.data.await
import space.kscience.dataforge.data.getByType
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) {
head {
this.title = title
@ -292,147 +277,133 @@ context(SiteData) internal fun BODY.magProgFooter() {
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 {
route(prefix) {
with(magProgSiteContext) {
static {
files(dataPath.resolve("assets").toFile())
static("images") {
files(dataPath.resolve("images").toFile())
}
page {
val sections = listOf<MagProgSection>(
wrapSection(resolveHtml(INTRO_PATH)!!, "intro"),
MagProgSection(
id = "partners",
title = "Партнеры",
style = "wrapper style3 fullscreen fade-up"
) {
partners()
},
// section(props.data.partners),
MagProgSection(
id = "mentors",
title = "Научные руководители",
style = "wrapper style2 spotlights",
) {
mentors()
},
MagProgSection(
id = "program",
title = "Учебная программа",
style = "wrapper style3 fullscreen fade-up"
) {
programSection()
},
wrapSection(resolveHtml(ENROLL_PATH)!!, "enroll"),
wrapSection(id = "contacts", title = "Контакты") {
htmlData(resolveHtml(CONTACTS_PATH)!!)
team()
}
get {
call.respondHtml {
val sections = listOf<MagProgSection>(
wrapSection(resolveHtml(INTRO_PATH)!!, "intro"),
MagProgSection(
id = "partners",
title = "Партнеры",
style = "wrapper style3 fullscreen fade-up"
) {
partners()
},
// section(props.data.partners),
MagProgSection(
id = "mentors",
title = "Научные руководители",
style = "wrapper style2 spotlights",
) {
mentors()
},
MagProgSection(
id = "program",
title = "Учебная программа",
style = "wrapper style3 fullscreen fade-up"
) {
programSection()
},
wrapSection(resolveHtml(ENROLL_PATH)!!, "enroll"),
wrapSection(id = "contacts", title = "Контакты") {
htmlData(resolveHtml(CONTACTS_PATH)!!)
team()
}
)
magProgHead("Магистратура \"Научное программирование\"")
body("is-preload magprog-body") {
section {
id = "sidebar"
div("inner") {
nav {
ul {
li {
a(href = "/"){
i("fa fa-home") {
attributes["aria-hidden"] = "true"
}
+"SPC"
}
}
sections.forEach { section ->
li {
a(href = "#${section.id}") {
+section.title
}
}
}
)
magProgHead("Магистратура \"Научное программирование\"")
body("is-preload magprog-body") {
section {
id = "sidebar"
div("inner") {
nav {
ul {
li {
a(classes = "spc-home", href = "/") {
i("fa fa-home") {
attributes["aria-hidden"] = "true"
}
+"SPC"
}
}
sections.forEach { section ->
li {
a(href = "#${section.id}") {
+section.title
}
}
}
}
div {
id = "wrapper"
sections.forEach { sec ->
section(sec.style) {
id = sec.id
with(sec) { content() }
}
}
}
magProgFooter()
}
}
}
val mentors = findByType("magprog_mentor").values.sortedBy {
it.order
div {
id = "wrapper"
sections.forEach { sec ->
section(sec.style) {
id = sec.id
with(sec) { content() }
}
}
}
magProgFooter()
}
}
mentors.forEach { mentor ->
get(mentor.mentorPageId) {
call.respondHtml {
magProgHead("Научное программирование: ${mentor.name}")
body("is-preload") {
header {
id = "header"
a(classes = "title") {
href = "$homeRef#mentors"
+"Научные руководители"
}
nav {
ul {
mentors.forEach {
li {
a {
href = resolveRef(it.mentorPageId)
+it.name.substringAfterLast(" ")
}
}
}
val mentors = data.findByType("magprog_mentor").values.sortedBy {
it.order
}
mentors.forEach { mentor ->
page(mentor.mentorPageId.asName()) {
magProgHead("Научное программирование: ${mentor.name}")
body("is-preload") {
header {
id = "header"
a(classes = "title") {
href = "$homeRef#mentors"
+"Научные руководители"
}
nav {
ul {
mentors.forEach {
li {
a {
href = resolveRef(it.mentorPageId)
+it.name.substringAfterLast(" ")
}
}
}
div {
id = "wrapper"
section("wrapper") {
id = "main"
div("inner") {
h1("major") { +mentor.name }
val imageClass = mentor.meta["image.position"].string ?: "left"
span("image $imageClass") {
mentor.imagePath?.let { photoPath ->
img(
src = resolveRef(photoPath),
alt = mentor.name
)
}
}
htmlData(mentor)
}
}
}
magProgFooter()
}
}
}
div {
id = "wrapper"
section("wrapper") {
id = "main"
div("inner") {
h1("major") { +mentor.name }
val imageClass = mentor.meta["image.position"].string ?: "left"
span("image $imageClass") {
mentor.imagePath?.let { photoPath ->
img(
src = resolveRef(photoPath),
alt = mentor.name
)
}
}
htmlData(mentor)
}
}
}
magProgFooter()
}
}
}

View File

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

View File

@ -1,14 +1,7 @@
package ru.mipt.spc
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 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.meta.Meta
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")
internal val FortyDataRenderer: SiteBuilder.(Data<*>) -> Unit = { data ->
if(data.type == typeOf<HtmlFragment>()) {
if (data.type == typeOf<HtmlFragment>()) {
data as Data<HtmlFragment>
page {
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() {
spcHead()
@ -308,26 +252,25 @@ 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) {
assetDirectory("assets", rootPath.resolve("assets"))
assetDirectory("images", rootPath.resolve("images"))
routing {
route(prefix) {
snarkSite(homePageContext) {
assetDirectory("assets", rootPath.resolve("assets"))
assetDirectory("images", rootPath.resolve("images"))
page { spcHome() }
page { spcHome() }
pages("consulting", dataRenderer = FortyDataRenderer)
//pages("ru.consulting".parseAsName(), dataRenderer = FortyDataRenderer)
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" }
}
spcSpotlight("team") { _, m ->
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
import io.ktor.server.application.Application
import io.ktor.server.application.call
import io.ktor.server.html.respondHtml
import io.ktor.server.http.content.*
import io.ktor.server.routing.Route
import io.ktor.server.routing.createRouteFromPath
import io.ktor.server.routing.get
import io.ktor.server.routing.routing
import kotlinx.html.HTML
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.ContextAware
import space.kscience.dataforge.data.DataTree
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
*/
@ -33,12 +37,19 @@ interface SiteBuilder : ContextAware {
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
*/
fun route(subRoute: Name): SiteBuilder
}
val SiteBuilder.snark get() = data.snark
public inline fun SiteBuilder.route(route: Name, block: SiteBuilder.() -> Unit) {
route(route).apply(block)
}
@ -47,7 +58,22 @@ public inline fun SiteBuilder.route(route: String, block: SiteBuilder.() -> Unit
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 {
override fun assetFile(remotePath: String, file: Path) {
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) {
ktorRoute.get(route.toWebPath()) {
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) {
ktorRoute.resources(resourcesPath)
}
override fun withData(newData: SiteData): SiteBuilder = KtorSiteRoute(newData, ktorRoute)
}
inline fun Route.snarkSite(
siteContext: SiteData,
inline fun Route.mountSnark(
data: SiteData,
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.string
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.plus
import space.kscience.dataforge.names.startsWith
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(
val snark: SnarkPlugin,
val data: DataTree<*>,
val urlPath: String,
override val meta: Meta = data.meta
val baseUrlPath: String,
override val meta: Meta = data.meta,
) : ContextAware, DataTree<Any> by data {
override val context: Context get() = snark.context
@ -30,6 +32,19 @@ data class SiteData(
val language: String? by meta.string()
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"
}
}
@ -37,9 +52,9 @@ data class SiteData(
/**
* 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
@ -70,15 +85,15 @@ fun SiteData.findByType(contentType: String, baseName: Name = Name.EMPTY) = reso
}
internal val Data<*>.published: Boolean get() = meta["published"].string != "false"
fun SnarkPlugin.readData(data: DataTree<*>, rootUrl: String = "/"): SiteData =
SiteData(this, data, rootUrl)
fun SnarkPlugin.readDirectory(path: Path, rootUrl: String = "/"): SiteData {
val parsedData: DataTree<Any> = readDirectory(path)
return readData(parsedData, rootUrl)
}
//
//fun SnarkPlugin.readData(data: DataTree<*>, rootUrl: String = "/"): SiteData =
// SiteData(this, data, rootUrl)
//
//fun SnarkPlugin.readDirectory(path: Path, rootUrl: String = "/"): SiteData {
// val parsedData: DataTree<Any> = readDirectory(path)
//
// return readData(parsedData, rootUrl)
//}
@PublishedApi
internal fun SiteData.copyWithRequestHost(request: ApplicationRequest): SiteData {
@ -86,14 +101,7 @@ internal fun SiteData.copyWithRequestHost(request: ApplicationRequest): SiteData
protocol = URLProtocol.createOrDefault(request.origin.scheme),
host = request.host(),
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))
}