Fix all errors with new SNARK architecture.
This commit is contained in:
parent
4966bfc9b3
commit
9fd4620947
@ -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>.
|
||||||
|
@ -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(
|
||||||
|
javaClass.getResource("/home")!!.toURI(),
|
||||||
|
dataPath / "home"
|
||||||
|
)
|
||||||
|
|
||||||
val homeDataPath = resolveData(
|
spcHome(rootPath = homeDataPath)
|
||||||
javaClass.getResource("/home")!!.toURI(),
|
|
||||||
dataPath / "home"
|
|
||||||
)
|
|
||||||
|
|
||||||
spcHome(context, rootPath = homeDataPath)
|
val mastersDataPath = resolveData(
|
||||||
|
javaClass.getResource("/magprog")!!.toURI(),
|
||||||
|
dataPath / "magprog"
|
||||||
|
)
|
||||||
|
|
||||||
|
spcMaster(dataPath = mastersDataPath)
|
||||||
val mastersDataPath = resolveData(
|
}
|
||||||
javaClass.getResource("/magprog")!!.toURI(),
|
|
||||||
dataPath / "magprog"
|
|
||||||
)
|
|
||||||
|
|
||||||
spcMaster(context, dataPath = mastersDataPath)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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,147 +277,133 @@ 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) {
|
val sections = listOf<MagProgSection>(
|
||||||
with(magProgSiteContext) {
|
wrapSection(resolveHtml(INTRO_PATH)!!, "intro"),
|
||||||
static {
|
MagProgSection(
|
||||||
files(dataPath.resolve("assets").toFile())
|
id = "partners",
|
||||||
|
title = "Партнеры",
|
||||||
static("images") {
|
style = "wrapper style3 fullscreen fade-up"
|
||||||
files(dataPath.resolve("images").toFile())
|
) {
|
||||||
}
|
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("Магистратура \"Научное программирование\"")
|
||||||
get {
|
body("is-preload magprog-body") {
|
||||||
call.respondHtml {
|
section {
|
||||||
val sections = listOf<MagProgSection>(
|
id = "sidebar"
|
||||||
wrapSection(resolveHtml(INTRO_PATH)!!, "intro"),
|
div("inner") {
|
||||||
MagProgSection(
|
nav {
|
||||||
id = "partners",
|
ul {
|
||||||
title = "Партнеры",
|
li {
|
||||||
style = "wrapper style3 fullscreen fade-up"
|
a(classes = "spc-home", href = "/") {
|
||||||
) {
|
i("fa fa-home") {
|
||||||
partners()
|
attributes["aria-hidden"] = "true"
|
||||||
},
|
}
|
||||||
// section(props.data.partners),
|
+"SPC"
|
||||||
MagProgSection(
|
}
|
||||||
id = "mentors",
|
}
|
||||||
title = "Научные руководители",
|
sections.forEach { section ->
|
||||||
style = "wrapper style2 spotlights",
|
li {
|
||||||
) {
|
a(href = "#${section.id}") {
|
||||||
mentors()
|
+section.title
|
||||||
},
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
div {
|
|
||||||
id = "wrapper"
|
|
||||||
sections.forEach { sec ->
|
|
||||||
section(sec.style) {
|
|
||||||
id = sec.id
|
|
||||||
with(sec) { content() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
magProgFooter()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
div {
|
||||||
val mentors = findByType("magprog_mentor").values.sortedBy {
|
id = "wrapper"
|
||||||
it.order
|
sections.forEach { sec ->
|
||||||
|
section(sec.style) {
|
||||||
|
id = sec.id
|
||||||
|
with(sec) { content() }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
magProgFooter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mentors.forEach { mentor ->
|
|
||||||
get(mentor.mentorPageId) {
|
val mentors = data.findByType("magprog_mentor").values.sortedBy {
|
||||||
call.respondHtml {
|
it.order
|
||||||
magProgHead("Научное программирование: ${mentor.name}")
|
}
|
||||||
body("is-preload") {
|
|
||||||
header {
|
mentors.forEach { mentor ->
|
||||||
id = "header"
|
page(mentor.mentorPageId.asName()) {
|
||||||
a(classes = "title") {
|
|
||||||
href = "$homeRef#mentors"
|
magProgHead("Научное программирование: ${mentor.name}")
|
||||||
+"Научные руководители"
|
body("is-preload") {
|
||||||
}
|
header {
|
||||||
nav {
|
id = "header"
|
||||||
ul {
|
a(classes = "title") {
|
||||||
mentors.forEach {
|
href = "$homeRef#mentors"
|
||||||
li {
|
+"Научные руководители"
|
||||||
a {
|
}
|
||||||
href = resolveRef(it.mentorPageId)
|
nav {
|
||||||
+it.name.substringAfterLast(" ")
|
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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,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 {
|
page { spcHome() }
|
||||||
route(prefix) {
|
|
||||||
snarkSite(homePageContext) {
|
|
||||||
assetDirectory("assets", rootPath.resolve("assets"))
|
|
||||||
assetDirectory("images", rootPath.resolve("images"))
|
|
||||||
|
|
||||||
page { spcHome() }
|
pages("consulting", dataRenderer = FortyDataRenderer)
|
||||||
|
//pages("ru.consulting".parseAsName(), dataRenderer = FortyDataRenderer)
|
||||||
|
|
||||||
pages("consulting", dataRenderer = FortyDataRenderer)
|
spcSpotlight("team") { _, m ->
|
||||||
//pages("ru.consulting".parseAsName(), dataRenderer = FortyDataRenderer)
|
m["type"].string == "team"
|
||||||
|
}
|
||||||
spcSpotlight("team") { _, m -> m["type"].string == "team" }
|
spcSpotlight("research") { name, m ->
|
||||||
spcSpotlight("research") { name, m -> name.startsWith("projects".asName()) && m["type"].string == "project" }
|
name.startsWith("projects".asName()) && m["type"].string == "project"
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -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))
|
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user