1
0
forked from SPC/spc-site

[WIP] refactor in progress

This commit is contained in:
Alexander Nozik 2024-01-04 11:26:23 +03:00
parent d2a31ce33e
commit 1923a1d296
14 changed files with 455 additions and 426 deletions

View File

@ -35,7 +35,7 @@ dependencies {
implementation("io.ktor:ktor-server-netty:$ktorVersion") implementation("io.ktor:ktor-server-netty:$ktorVersion")
implementation("io.ktor:ktor-server-http-redirect:$ktorVersion") implementation("io.ktor:ktor-server-http-redirect:$ktorVersion")
implementation("io.ktor:ktor-server-forwarded-header:$ktorVersion") implementation("io.ktor:ktor-server-forwarded-header:$ktorVersion")
implementation("ch.qos.logback:logback-classic:1.2.11") implementation("ch.qos.logback:logback-classic:1.4.12")
testImplementation("io.ktor:ktor-server-tests:$ktorVersion") testImplementation("io.ktor:ktor-server-tests:$ktorVersion")
} }

View File

@ -1,4 +1,4 @@
kotlin.code.style=official kotlin.code.style=official
toolsVersion=0.14.9-kotlin-1.8.20 toolsVersion=0.15.2-kotlin-1.9.21
snarkVersion=0.1.0-dev-1 snarkVersion=0.1.0-dev-1

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@ -10,12 +10,11 @@ import io.ktor.server.plugins.forwardedheaders.XForwardedHeaders
import io.ktor.server.response.respondRedirect import io.ktor.server.response.respondRedirect
import io.ktor.server.routing.get import io.ktor.server.routing.get
import io.ktor.server.routing.routing import io.ktor.server.routing.routing
import space.kscience.dataforge.context.Global import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.request import space.kscience.dataforge.context.request
import space.kscience.dataforge.data.DataTree import space.kscience.dataforge.data.DataTree
import space.kscience.snark.html.SiteBuilder import space.kscience.dataforge.workspace.readDataDirectory
import space.kscience.snark.html.SnarkHtmlPlugin import space.kscience.snark.html.*
import space.kscience.snark.html.readDirectory
import space.kscience.snark.ktor.prepareSnarkDataCacheDirectory import space.kscience.snark.ktor.prepareSnarkDataCacheDirectory
import space.kscience.snark.ktor.site import space.kscience.snark.ktor.site
import java.nio.file.FileSystems import java.nio.file.FileSystems
@ -69,7 +68,11 @@ fun Application.spcModule() {
install(ForwardedHeaders) install(ForwardedHeaders)
install(XForwardedHeaders) install(XForwardedHeaders)
val snark = Global.request(SnarkHtmlPlugin) val context = Context {
plugin(SnarkHtml)
}
val snark = context.request(SnarkHtml)
val dataDirectory = Path.of( val dataDirectory = Path.of(
environment.config.tryGetString("ktor.environment.dataDirectory") ?: "data" environment.config.tryGetString("ktor.environment.dataDirectory") ?: "data"
@ -81,14 +84,13 @@ fun Application.spcModule() {
copyResource("magprog", dataDirectory) copyResource("magprog", dataDirectory)
} }
val siteData: DataTree<Any> = snark.readDirectory(dataDirectory) val siteData: DataTree<Any> = snark.io.readDataDirectory(dataDirectory)
site(snark, siteData, block = SiteBuilder::spcSite)
routing { routing {
get("magprog") { get("magprog") {
call.respondRedirect("education/masters") call.respondRedirect("education/masters")
} }
site(context, siteData, content = spcSite)
} }
} }

View File

@ -3,7 +3,6 @@ package center.sciprog
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.html.* import kotlinx.html.*
import space.kscience.dataforge.data.Data import space.kscience.dataforge.data.Data
import space.kscience.dataforge.data.DataTree
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
@ -11,7 +10,6 @@ import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.getIndexed import space.kscience.dataforge.meta.getIndexed
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.parseAsName
import space.kscience.snark.html.* import space.kscience.snark.html.*
@ -22,58 +20,55 @@ private val Data<*>.fragment: String
get() = meta["fragment"].string ?: "" get() = meta["fragment"].string ?: ""
internal fun SiteBuilder.bmk(data: DataTree<Any>, prefix: Name = "bmk".parseAsName()) { internal val bmk = HtmlSite {
// val data: DataTree<Any> = snark.readDirectory(dataPath.resolve("content")) static("assets")
static("images")
static("common.assets.webfonts", "assets/webfonts")
static("common", "")
site(prefix, data) { val about: Data<PageFragment> = siteData.resolveHtml("about")
static("assets") val team: Data<PageFragment> = siteData.resolveHtml("team.index")
static("images") val teamData: Map<Name, Data<PageFragment>> = siteData.resolveAllHtml { _, meta -> meta["type"].string == "team" }
static("common.assets.webfonts", "assets/webfonts") val solutions: Data<PageFragment> = siteData.resolveHtml("lotSeis")
static("common", "") val partners: Data<PageFragment> = siteData.resolveHtml("partners")
val partnersData = runBlocking { siteData.getByType<Meta>("partnersData")!!.await() }
val about: Data<HtmlFragment> = data.resolveHtml("about") page {
val team: Data<HtmlFragment> = data.resolveHtml("team.index") head {
val teamData: Map<Name, Data<HtmlFragment>> = data.resolveAllHtml { _, meta -> meta["type"].string == "team" } title = "БМК-Сервис"
val solutions: Data<HtmlFragment> = data.resolveHtml("lotSeis") meta {
val partners: Data<HtmlFragment> = data.resolveHtml("partners") charset = "utf-8"
val partnersData = runBlocking { data.getByType<Meta>("partnersData")!!.await() } }
meta {
page { name = "viewport"
head { content = "width=device-width, initial-scale=1, user-scalable=no"
title = "БМК-Сервис" }
meta { link {
charset = "utf-8" rel = "stylesheet"
} href = resolveRef("assets/css/main.css")
meta { }
name = "viewport" noScript {
content = "width=device-width, initial-scale=1, user-scalable=no"
}
link { link {
rel = "stylesheet" rel = "stylesheet"
href = resolveRef("assets/css/main.css") href = resolveRef("assets/css/noscript.css")
}
noScript {
link {
rel = "stylesheet"
href = resolveRef("assets/css/noscript.css")
}
} }
} }
body("is-preload") { }
body("is-preload") {
// Wrapper // Wrapper
div { div {
id = "wrapper" id = "wrapper"
// Header // Header
header("alt") { header("alt") {
id = "header" id = "header"
span("logo") { span("logo") {
img { img {
src = "images/logo.svg" src = "images/logo.svg"
alt = "" alt = ""
}
} }
h1 { +"""БМК-Сервис""" } }
h1 { +"""БМК-Сервис""" }
// p { // p {
// +"""Just another free, fully responsive site template""" // +"""Just another free, fully responsive site template"""
// br { // br {
@ -90,99 +85,98 @@ internal fun SiteBuilder.bmk(data: DataTree<Any>, prefix: Name = "bmk".parseAsNa
// } // }
// +""".""" // +"""."""
// } // }
} }
// Nav // Nav
nav { nav {
id = "nav" id = "nav"
ul { ul {
li { li {
a(classes = "active") { a(classes = "active") {
href = "#${about.fragment}" href = "#${about.fragment}"
+about.title +about.title
}
}
li {
a {
href = "#${team.fragment}"
+team.title
}
}
li {
a {
href = "#${solutions.fragment}"
+solutions.title
}
}
li {
a {
href = "#${partners.fragment}"
+partners.title
}
}
}
}
div {
id = "main"
section("main") {
id = about.fragment
div("spotlight") {
div("content") {
header("major") {
h2 { +about.title }
}
htmlData(about)
}
}
}
section("main") {
id = team.fragment
header("major") {
h2 { +team.title }
}
fragment(team)
teamData.values.sortedBy { it.order }.forEach { data ->
span("image left") {
img {
src = resolveRef("images/${data.meta["image"].string!!}")
height = "120dp"
} }
} }
li { h3 { +data.title }
a { fragment(data)
href = "#${team.fragment}" }
+team.title }
} section("main") {
} id = solutions.fragment
li { header("major") {
a { h2 { +solutions.title }
href = "#${solutions.fragment}" fragment(solutions)
+solutions.title span("image fit") {
} img {
} src = resolveRef("images/fresnel_lands_critdepth2.png")
li {
a {
href = "#${partners.fragment}"
+partners.title
} }
} }
} }
} }
div { section("main") {
id = "main" id = partners.fragment
section("main") { header("major") {
id = about.fragment h2 { +partners.title }
div("spotlight") { fragment(partners)
div("content") { table {
header("major") { partnersData.getIndexed("content").values.forEach {
h2 { +about.title } tr {
} td {
htmlData(about) span("image right") {
} img {
} src = resolveRef(it["image"].string!!)
} height = "120dp"
section("main") { width = "auto"
id = team.fragment
header("major") {
h2 { +team.title }
}
htmlData(team)
teamData.values.sortedBy { it.order }.forEach { data ->
span("image left") {
img {
src = resolveRef("images/${data.meta["image"].string!!}")
height = "120dp"
}
}
h3 { +data.title }
htmlData(data)
}
}
section("main") {
id = solutions.fragment
header("major") {
h2 { +solutions.title }
htmlData(solutions)
span("image fit") {
img {
src = resolveRef("images/fresnel_lands_critdepth2.png")
}
}
}
}
section("main") {
id = partners.fragment
header("major") {
h2 { +partners.title }
htmlData(partners)
table {
partnersData.getIndexed("content").values.forEach {
tr {
td {
span("image right") {
img {
src = resolveRef(it["image"].string!!)
height = "120dp"
width = "auto"
}
} }
h3 { }
a(href = it["target"].string!!) { h3 {
+it["title"].string!! a(href = it["target"].string!!) {
} +it["title"].string!!
} }
} }
} }
@ -191,8 +185,9 @@ internal fun SiteBuilder.bmk(data: DataTree<Any>, prefix: Name = "bmk".parseAsNa
} }
} }
} }
}
// Footer // Footer
footer { footer {
// id = "footer" // id = "footer"
// section { // section {
// h2 { +"""Aliquam sed mauris""" } // h2 { +"""Aliquam sed mauris""" }
@ -254,38 +249,37 @@ internal fun SiteBuilder.bmk(data: DataTree<Any>, prefix: Name = "bmk".parseAsNa
// } // }
// } // }
// } // }
p("copyright") { p("copyright") {
+"""SPC. Design:""" +"""SPC. Design:"""
a { a {
href = "https://html5up.net" href = "https://html5up.net"
+"""HTML5 UP""" +"""HTML5 UP"""
}
+"""."""
} }
+"""."""
} }
} }
}
// Scripts // Scripts
script { script {
src = resolveRef("assets/js/jquery.min.js") src = resolveRef("assets/js/jquery.min.js")
} }
script { script {
src = resolveRef("assets/js/jquery.scrollex.min.js") src = resolveRef("assets/js/jquery.scrollex.min.js")
} }
script { script {
src = resolveRef("assets/js/jquery.scrolly.min.js") src = resolveRef("assets/js/jquery.scrolly.min.js")
} }
script { script {
src = resolveRef("assets/js/browser.min.js") src = resolveRef("assets/js/browser.min.js")
} }
script { script {
src = resolveRef("assets/js/breakpoints.min.js") src = resolveRef("assets/js/breakpoints.min.js")
} }
script { script {
src = resolveRef("assets/js/util.js") src = resolveRef("assets/js/util.js")
} }
script { script {
src = resolveRef("assets/js/main.js") src = resolveRef("assets/js/main.js")
}
} }
} }
} }

View File

@ -13,9 +13,11 @@ import kotlin.collections.component1
import kotlin.collections.component2 import kotlin.collections.component2
import kotlin.collections.set import kotlin.collections.set
context(WebPage) private fun FlowContent.spcSpotlightContent(
landing: HtmlData, context(PageContextWithData)
content: Map<Name, HtmlData>, private fun FlowContent.spcSpotlightContent(
landing: Data<PageFragment>,
content: Map<Name, Data<PageFragment>>,
) { ) {
// Banner // Banner
// Note: The "styleN" class below should match that of the header element. // Note: The "styleN" class below should match that of the header element.
@ -32,7 +34,7 @@ context(WebPage) private fun FlowContent.spcSpotlightContent(
h1 { +(landing.meta["title"].string ?: "???") } h1 { +(landing.meta["title"].string ?: "???") }
} }
div("content") { div("content") {
htmlData(landing) fragment(landing)
} }
} }
} }
@ -63,8 +65,9 @@ context(WebPage) private fun FlowContent.spcSpotlightContent(
header("major") { header("major") {
h3 { +(entry.meta["title"].string ?: "???") } h3 { +(entry.meta["title"].string ?: "???") }
} }
val infoData = data.resolveHtmlOrNull(name.replaceLast { NameToken(it.body + "[info]") }) ?: entry val infoData =
htmlData(infoData) data.resolveHtmlOrNull(name.replaceLast { NameToken(it.body + "[info]") }) ?: entry
fragment(infoData)
ul("actions") { ul("actions") {
li { li {
a(classes = "button") { a(classes = "button") {
@ -82,16 +85,16 @@ context(WebPage) private fun FlowContent.spcSpotlightContent(
} }
internal fun SiteBuilder.spcSpotlight( internal fun SiteContextWithData.spcSpotlight(
address: String, address: String,
contentFilter: (Name, Meta) -> Boolean, contentFilter: (Name, Meta) -> Boolean,
) { ) {
val pageName = address.parseAsName() val pageName = address.parseAsName()
val languagePrefix = languagePrefix val languagePrefix = languagePrefix
val body = data.resolveHtmlOrNull(languagePrefix + pageName) val body = siteData.resolveHtmlOrNull(languagePrefix + pageName)
?: data.resolveHtmlOrNull(pageName) ?: error("Could not find body for $pageName") ?: siteData.resolveHtmlOrNull(pageName) ?: error("Could not find body for $pageName")
val content: Map<Name, Data<HtmlFragment>> = val content: Map<Name, Data<PageFragment>> =
data.resolveAllHtml { name, meta -> name.startsWith(languagePrefix) && contentFilter(name, meta) } siteData.resolveAllHtml { name, meta -> name.startsWith(languagePrefix) && contentFilter(name, meta) }
val meta = body.meta val meta = body.meta
page(pageName) { page(pageName) {
@ -107,10 +110,6 @@ internal fun SiteBuilder.spcSpotlight(
} }
content.forEach { (name, contentBody) -> content.forEach { (name, contentBody) ->
page(name, contentBody.meta) { page(name, contentBody.meta, spcPage(contentBody))
spcPageContent {
htmlData(contentBody)
}
}
} }
} }

View File

@ -2,19 +2,15 @@ package center.sciprog
import html5up.forty.fortyScripts import html5up.forty.fortyScripts
import kotlinx.html.* import kotlinx.html.*
import space.kscience.dataforge.data.Data import space.kscience.dataforge.data.*
import space.kscience.dataforge.data.DataTree import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.* import space.kscience.dataforge.meta.string
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.startsWith import space.kscience.dataforge.names.startsWith
import space.kscience.snark.html.* import space.kscience.snark.html.*
import kotlin.reflect.typeOf
context(WebPage) internal fun HTML.spcPageContent( internal fun spcPage(content: Data<PageFragment>) = HtmlPage {
fragment: FlowContent.() -> Unit,
) {
val title by pageMeta.string { SPC_TITLE } val title by pageMeta.string { SPC_TITLE }
val pageName by pageMeta.string { title } val pageName by pageMeta.string { title }
spcHead(pageName) spcHead(pageName)
@ -30,7 +26,8 @@ context(WebPage) internal fun HTML.spcPageContent(
} }
pageMeta["image"]?.let { imageMeta -> pageMeta["image"]?.let { imageMeta ->
val imagePath = val imagePath =
imageMeta.value?.string ?: imageMeta["path"].string ?: error("Image path not provided") imageMeta.value?.string ?: imageMeta["path"].string
?: error("Image path not provided")
val imageClass = imageMeta["position"].string ?: "main" val imageClass = imageMeta["position"].string ?: "main"
span("image $imageClass") { span("image $imageClass") {
img { img {
@ -39,7 +36,7 @@ context(WebPage) internal fun HTML.spcPageContent(
} }
} }
} }
fragment() fragment(content)
} }
} }
} }
@ -49,34 +46,7 @@ context(WebPage) internal fun HTML.spcPageContent(
} }
} }
@Suppress("UNCHECKED_CAST") internal val spcHomePage = HtmlPage {
internal val FortyDataRenderer: DataRenderer = object : DataRenderer {
context(SiteBuilder)
override fun invoke(name: Name, data: Data<Any>) {
if (data.type == typeOf<HtmlFragment>()) {
data as Data<HtmlFragment>
val languageMeta: Meta = Language.forName(name)
val dataMeta: Meta = if (languageMeta.isEmpty()) {
data.meta
} else {
data.meta.toMutableMeta().apply {
Language.LANGUAGES_KEY put languageMeta
}
}
page(name, dataMeta) {
spcPageContent {
htmlData(data)
}
}
}
}
}
context(WebPage) private fun HTML.spcHomePage() {
spcHead() spcHead()
body("is-preload") { body("is-preload") {
wrapper { wrapper {
@ -156,14 +126,14 @@ context(WebPage) private fun HTML.spcHomePage() {
article { article {
span("image") { span("image") {
img { img {
src = resolveRef("images/pic01.jpg") src = page.resolveRef("images/pic01.jpg")
alt = "" alt = ""
} }
} }
header("major") { header("major") {
h3 { h3 {
a(classes = "link") { a(classes = "link") {
href = resolvePageRef("education") href = page.resolvePageRef("education")
+"""Education""" +"""Education"""
} }
} }
@ -173,14 +143,14 @@ context(WebPage) private fun HTML.spcHomePage() {
article { article {
span("image") { span("image") {
img { img {
src = resolveRef("images/pic02.jpg") src = page.resolveRef("images/pic02.jpg")
alt = "" alt = ""
} }
} }
header("major") { header("major") {
h3 { h3 {
a(classes = "link") { a(classes = "link") {
href = resolvePageRef("research") href = page.resolvePageRef("research")
+"""Research""" +"""Research"""
} }
} }
@ -192,14 +162,14 @@ context(WebPage) private fun HTML.spcHomePage() {
article { article {
span("image") { span("image") {
img { img {
src = resolveRef("images/pic03.jpg") src = page.resolveRef("images/pic03.jpg")
alt = "" alt = ""
} }
} }
header("major") { header("major") {
h3 { h3 {
a(classes = "link") { a(classes = "link") {
href = resolvePageRef("consulting.index") href = page.resolvePageRef("consulting.index")
+"""Consulting""" +"""Consulting"""
} }
} }
@ -209,14 +179,14 @@ context(WebPage) private fun HTML.spcHomePage() {
article { article {
span("image") { span("image") {
img { img {
src = resolveRef("images/pic04.jpg") src = page.resolveRef("images/pic04.jpg")
alt = "" alt = ""
} }
} }
header("major") { header("major") {
h3 { h3 {
a(classes = "link") { a(classes = "link") {
href = resolvePageRef("team") href = page.resolvePageRef("team")
+"""Team""" +"""Team"""
} }
} }
@ -263,32 +233,88 @@ context(WebPage) private fun HTML.spcHomePage() {
} }
} }
internal fun SiteBuilder.spcHome(homePageData: DataTree<Any>, prefix: Name = Name.EMPTY) { private fun SiteContextWithData.allPagesIn(location: String){
siteData.filterByType<PageFragment> { name, meta ->
//val homePageData: DataTree<Any> = snark.readDirectory(dataPath.resolve("content")) name.startsWith(location) && meta["type"].string == "page"
}.forEach { (name, content) ->
site(prefix, homePageData) { page(name, content = spcPage(content))
static("assets")
static("images")
static("common", "")
withLanguages(
"en" to "",
"ru" to "ru"
) {
page { spcHomePage() }
localizedPages("consulting", dataRenderer = FortyDataRenderer)
localizedPages("education", dataRenderer = FortyDataRenderer)
spcSpotlight("team") { _, meta ->
meta["type"].string == "team"
}
spcSpotlight("research") { name, meta ->
name.startsWith("projects".asName()) && meta["type"].string == "project"
}
}
} }
} }
internal val spcHome: HtmlSite = HtmlSite {
static("assets")
static("images")
static("common", "")
multiLanguageSite(
siteData,
mapOf(
"en" to Language(""),
"ru" to Language("ru"),
)
) {
page(content = spcHomePage)
allPagesIn("consulting")
allPagesIn("education")
spcSpotlight("team") { _, meta ->
meta["type"].string == "team"
}
spcSpotlight("research") { name, meta ->
name.startsWith("projects".asName()) && meta["type"].string == "project"
}
}
// withLanguages(
// "en" to "",
// "ru" to "ru"
// ) {
// page { spcHomePage() }
//
// localizedPages("consulting", dataRenderer = FortyDataRenderer)
//
// localizedPages("education", dataRenderer = FortyDataRenderer)
//
// spcSpotlight("team") { _, meta ->
// meta["type"].string == "team"
// }
//
// spcSpotlight("research") { name, meta ->
// name.startsWith("projects".asName()) && meta["type"].string == "project"
// }
// }
}
//
//internal fun SiteBuilder.spcHome(homePageData: DataTree<Any>, prefix: Name = Name.EMPTY) {
//
// //val homePageData: DataTree<Any> = snark.readDirectory(dataPath.resolve("content"))
//
// site(prefix, homePageData) {
// static("assets")
// static("images")
// static("common", "")
//
// withLanguages(
// "en" to "",
// "ru" to "ru"
// ) {
// page { spcHomePage() }
//
// localizedPages("consulting", dataRenderer = FortyDataRenderer)
//
// localizedPages("education", dataRenderer = FortyDataRenderer)
//
// spcSpotlight("team") { _, meta ->
// meta["type"].string == "team"
// }
//
// spcSpotlight("research") { name, meta ->
// name.startsWith("projects".asName()) && meta["type"].string == "project"
// }
// }
// }
//}

View File

@ -2,7 +2,7 @@ package center.sciprog
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.html.* import kotlinx.html.*
import space.kscience.dataforge.data.DataTree import space.kscience.dataforge.data.Data
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
@ -29,17 +29,19 @@ import kotlin.collections.set
// } // }
//} //}
private val HtmlData.imagePath: String? get() = meta["image"]?.string ?: meta["image.path"].string private val Data<PageFragment>.imagePath: String? get() = meta["image"]?.string ?: meta["image.path"].string
private val HtmlData.name: String get() = meta["name"].string ?: error("Name not found") private val Data<PageFragment>.name: String get() = meta["name"].string ?: error("Name not found")
context(WebPage) class MagProgSection( context(PageContext)
class MagProgSection(
val id: String, val id: String,
val title: String, val title: String,
val style: String, val style: String,
val content: FlowContent.() -> Unit, val content: FlowContent.() -> Unit,
) )
context(WebPage) private fun wrapSection( context(PageContext)
private fun wrapSection(
id: String, id: String,
title: String, title: String,
sectionContent: FlowContent.() -> Unit, sectionContent: FlowContent.() -> Unit,
@ -50,14 +52,15 @@ context(WebPage) private fun wrapSection(
} }
} }
context(WebPage) private fun wrapSection( context(PageContextWithData)
block: HtmlData, private fun wrapSection(
section: Data<PageFragment>,
idOverride: String? = null, idOverride: String? = null,
): MagProgSection = wrapSection( ): MagProgSection = wrapSection(
idOverride ?: block.id, idOverride ?: section.id,
block.meta["section_title"]?.string ?: error("Section without title"), section.meta["section_title"]?.string ?: error("Section without title"),
) { ) {
htmlData(block) fragment(section)
} }
private val CONTENT_NODE_NAME = Name.EMPTY//"content".asName() private val CONTENT_NODE_NAME = Name.EMPTY//"content".asName()
@ -68,24 +71,25 @@ private val PROGRAM_PATH: Name = CONTENT_NODE_NAME + "program"
private val RECOMMENDED_COURSES_PATH: Name = CONTENT_NODE_NAME + "recommendedCourses" private val RECOMMENDED_COURSES_PATH: Name = CONTENT_NODE_NAME + "recommendedCourses"
private val PARTNERS_PATH: Name = CONTENT_NODE_NAME + "partners" private val PARTNERS_PATH: Name = CONTENT_NODE_NAME + "partners"
context(WebPage) private fun FlowContent.programSection() { private val programSection = PageFragment {
val programBlock = data.resolveHtmlOrNull(PROGRAM_PATH)!! val programBlock = data.resolveHtmlOrNull(PROGRAM_PATH)!!
val recommendedBlock = data.resolveHtmlOrNull(RECOMMENDED_COURSES_PATH)!! val recommendedBlock = data.resolveHtmlOrNull(RECOMMENDED_COURSES_PATH)!!
div("inner") { div("inner") {
h2 { +"Учебная программа" } h2 { +"Учебная программа" }
htmlData(programBlock) fragment(programBlock)
button(classes = "fit collapsible") { button(classes = "fit collapsible") {
attributes["data-target"] = "recommended-courses-content" attributes["data-target"] = "recommended-courses-content"
+"Рекомендованные курсы" +"Рекомендованные курсы"
} }
div(classes = "collapsible-content") { div(classes = "collapsible-content") {
id = "recommended-courses-content" id = "recommended-courses-content"
htmlData(recommendedBlock) fragment(recommendedBlock)
} }
} }
} }
context(WebPage) private fun FlowContent.partners() {
private val partners = PageFragment {
//val partnersData: Meta = resolve<Any>(PARTNERS_PATH)?.meta ?: Meta.EMPTY //val partnersData: Meta = resolve<Any>(PARTNERS_PATH)?.meta ?: Meta.EMPTY
val partnersData: Meta = runBlocking { data.getByType<Meta>(PARTNERS_PATH)?.await() } ?: Meta.EMPTY val partnersData: Meta = runBlocking { data.getByType<Meta>(PARTNERS_PATH)?.await() } ?: Meta.EMPTY
div("inner") { div("inner") {
@ -115,8 +119,8 @@ context(WebPage) private fun FlowContent.partners() {
// val photo: String? by meta.string() // val photo: String? by meta.string()
//} //}
context(WebPage) private fun FlowContent.team() { private val team = PageFragment {
val team = data.findByContentType("magprog_team").values.sortedBy { it.order } val team = data.findHtmlByContentType("magprog_team").values.sortedBy { it.order }
div("inner") { div("inner") {
h2 { +"Команда" } h2 { +"Команда" }
@ -131,7 +135,7 @@ context(WebPage) private fun FlowContent.team() {
alt = imagePath alt = imagePath
) { ) {
h3 { +member.name } h3 { +member.name }
htmlData(member) fragment(member)
} }
} }
} }
@ -170,8 +174,8 @@ context(WebPage) private fun FlowContent.team() {
// } // }
} }
context(WebPage) private fun FlowContent.mentors() { val mentors = PageFragment {
val mentors = data.findByContentType("magprog_mentor").entries.sortedBy { it.value.id } val mentors = data.findHtmlByContentType("magprog_mentor").entries.sortedBy { it.value.id }
div("inner") { div("inner") {
h2 { h2 {
@ -200,7 +204,7 @@ context(WebPage) private fun FlowContent.mentors() {
} }
val info = data.resolveHtmlOrNull(name.replaceLast { NameToken(it.body + "[info]") }) val info = data.resolveHtmlOrNull(name.replaceLast { NameToken(it.body + "[info]") })
if (info != null) { if (info != null) {
htmlData(info) fragment(info)
} }
} }
} }
@ -208,7 +212,8 @@ context(WebPage) private fun FlowContent.mentors() {
} }
} }
context(WebPage) internal fun HTML.magProgHead(title: String) { context(PageContext)
internal fun HTML.magProgHead(title: String) {
head { head {
title { title {
+title +title
@ -258,7 +263,8 @@ context(WebPage) internal fun HTML.magProgHead(title: String) {
} }
} }
context(WebPage) internal fun BODY.magProgFooter() { context(PageContext)
internal fun BODY.magProgFooter() {
footer("wrapper style1-alt") { footer("wrapper style1-alt") {
id = "footer" id = "footer"
div("inner") { div("inner") {
@ -297,72 +303,110 @@ context(WebPage) internal fun BODY.magProgFooter() {
} }
} }
context(SnarkContext) private val HtmlData.mentorPageId get() = "mentor-${id}" context(SnarkContext) private val Data<PageFragment>.mentorPageId get() = "mentor-${id}"
internal fun SiteBuilder.spcMasters(magProgData: DataTree<Any>, prefix: Name = "education.masters".parseAsName()) {
//val magProgData: DataTree<Any> = snark.readDirectory(dataPath.resolve("content")) internal val spcMasters = HtmlSite {
static("assets")
static("images")
static("common", "")
site(prefix, magProgData) { page{
static("assets") val sections = listOf(
static("images") wrapSection(data.resolveHtmlOrNull(INTRO_PATH)!!, "intro"),
static("common", "") MagProgSection(
id = "partners",
title = "Партнеры",
style = "wrapper style3 fullscreen fade-up"
) {
fragment(partners)
},
// section(props.data.partners),
MagProgSection(
id = "mentors",
title = "Научные руководители",
style = "wrapper style2 spotlights",
) {
fragment(mentors)
},
MagProgSection(
id = "program",
title = "Учебная программа",
style = "wrapper style3 fullscreen fade-up"
) {
fragment(programSection)
},
wrapSection(data.resolveHtmlOrNull(ENROLL_PATH)!!, "enroll"),
wrapSection(id = "contacts", title = "Контакты") {
fragment(data.resolveHtmlOrNull(CONTACTS_PATH)!!)
fragment(team)
}
)
page { magProgHead("Магистратура \"Научное программирование\"")
val sections = listOf<MagProgSection>( body("is-preload magprog-body") {
wrapSection(page.data.resolveHtmlOrNull(INTRO_PATH)!!, "intro"), section {
MagProgSection( id = "sidebar"
id = "partners", div("inner") {
title = "Партнеры", nav {
style = "wrapper style3 fullscreen fade-up" ul {
) { li {
partners() a(
}, classes = "spc-home",
// section(props.data.partners), href = "/" //TODO provide a way to navigate out-of-site
MagProgSection( ) {
id = "mentors", i("fa fa-home") {
title = "Научные руководители", attributes["aria-hidden"] = "true"
style = "wrapper style2 spotlights", }
) { +"SPC"
mentors() }
}, }
MagProgSection( sections.forEach { section ->
id = "program",
title = "Учебная программа",
style = "wrapper style3 fullscreen fade-up"
) {
programSection()
},
wrapSection(page.data.resolveHtmlOrNull(ENROLL_PATH)!!, "enroll"),
wrapSection(id = "contacts", title = "Контакты") {
htmlData(page.data.resolveHtmlOrNull(CONTACTS_PATH)!!)
team()
}
)
magProgHead("Магистратура \"Научное программирование\"")
body("is-preload magprog-body") {
section {
id = "sidebar"
div("inner") {
nav {
ul {
li { li {
a( a(href = "#${section.id}") {
classes = "spc-home", +section.title
href = "/" //TODO provide a way to navigate out-of-site
) {
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 = siteData.findHtmlByContentType("magprog_mentor").values.sortedBy {
it.order
}
mentors.forEach { mentor ->
page(mentor.mentorPageId.asName(), siteData) {
magProgHead("Научное программирование: ${mentor.name}")
body("is-preload") {
header {
id = "header"
a(classes = "title") {
href = "${page.homeRef}#mentors"
+"Научные руководители"
}
nav {
ul {
mentors.forEach {
li {
a {
href = page.resolvePageRef(it.mentorPageId)
+it.name.substringAfterLast(" ")
} }
} }
} }
@ -371,68 +415,25 @@ internal fun SiteBuilder.spcMasters(magProgData: DataTree<Any>, prefix: Name = "
} }
div { div {
id = "wrapper" id = "wrapper"
sections.forEach { sec -> section("wrapper") {
section(sec.style) { id = "main"
id = sec.id div("inner") {
with(sec) { content() } h1("major") { +mentor.name }
val imageClass = mentor.meta["image.position"].string ?: "left"
span("image $imageClass") {
mentor.imagePath?.let { photoPath ->
img(
src = page.resolveRef(photoPath),
alt = mentor.name
)
}
}
fragment(mentor)
} }
} }
} }
magProgFooter() magProgFooter()
} }
} }
val mentors = data.findByContentType("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 = resolvePageRef(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()
}
}
}
} }
} }

View File

@ -3,16 +3,16 @@ package center.sciprog
import kotlinx.html.* import kotlinx.html.*
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.snark.html.WebPage import space.kscience.snark.html.PageContext
import space.kscience.snark.html.getLanguageMap
import space.kscience.snark.html.homeRef import space.kscience.snark.html.homeRef
import space.kscience.snark.html.languages
import space.kscience.snark.html.resolvePageRef import space.kscience.snark.html.resolvePageRef
import java.time.LocalDate import java.time.LocalDate
internal const val SPC_TITLE = "Scientific Programming Centre" internal const val SPC_TITLE = "Scientific Programming Centre"
context(WebPage) context(PageContext)
internal fun HTML.spcHead(title: String = SPC_TITLE) { internal fun HTML.spcHead(title: String = SPC_TITLE) {
head { head {
title { title {
@ -53,7 +53,7 @@ internal fun HTML.spcHead(title: String = SPC_TITLE) {
} }
} }
context(WebPage) context(PageContext)
internal fun FlowContent.spcHomeMenu() { internal fun FlowContent.spcHomeMenu() {
nav { nav {
id = "menu" id = "menu"
@ -106,7 +106,7 @@ internal fun FlowContent.spcHomeMenu() {
} }
} }
context(WebPage) context(PageContext)
internal fun FlowContent.spcFooter() { internal fun FlowContent.spcFooter() {
footer { footer {
id = "footer" id = "footer"
@ -158,7 +158,7 @@ internal fun FlowContent.spcFooter() {
} }
} }
context(WebPage) context(PageContext)
internal fun FlowContent.wrapper(contentBody: FlowContent.() -> Unit) { internal fun FlowContent.wrapper(contentBody: FlowContent.() -> Unit) {
div { div {
id = "wrapper" id = "wrapper"
@ -172,9 +172,9 @@ internal fun FlowContent.wrapper(contentBody: FlowContent.() -> Unit) {
} }
if (languages.isNotEmpty()) { getLanguageMap().takeIf { it.isNotEmpty() }?.let { languageMap->
div { div {
languages.forEach { (key, meta) -> languageMap.forEach { (key, meta) ->
a(classes = "button primary small") { a(classes = "button primary small") {
href = resolvePageRef(meta["target"].string ?: "#") href = resolvePageRef(meta["target"].string ?: "#")
+key +key

View File

@ -3,18 +3,19 @@ package center.sciprog
import space.kscience.dataforge.data.* import space.kscience.dataforge.data.*
import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.snark.html.SiteBuilder import space.kscience.snark.html.HtmlSite
import space.kscience.snark.html.site
@OptIn(DFExperimental::class) @OptIn(DFExperimental::class)
private fun <T : Any> DataSet<T>.siteData(branchName: String): DataTree<T> = DataTree(dataType) { private fun <T : Any> DataSet<T>.contentFor(branchName: String): DataTree<T> = DataTree(dataType) {
populateFrom(branch(Name.of(branchName, "content"))) populateFrom(branch(Name.of(branchName, "content")))
node("common", branch("common")) node("common", branch("common"))
node("assets", branch(Name.of(branchName, "assets"))) node("assets", branch(Name.of(branchName, "assets")))
node("images", branch(Name.of(branchName, "images"))) node("images", branch(Name.of(branchName, "images")))
} }
fun SiteBuilder.spcSite() { val spcSite = HtmlSite {
spcHome(data.siteData("home")) route(Name.EMPTY, siteData.contentFor("home"), content = spcHome)
spcMasters(data.siteData("magprog")) site("education.masters", siteData.contentFor("magprog"), content = spcMasters)
// bmk(data.branch("bmk").withBranch("common", commonData)) // bmk(data.branch("bmk").withBranch("common", commonData))
} }

View File

@ -1,18 +1,21 @@
package center.sciprog package center.sciprog
import space.kscience.dataforge.context.Global import kotlinx.coroutines.coroutineScope
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.request import space.kscience.dataforge.context.request
import space.kscience.snark.html.SiteBuilder import space.kscience.snark.html.*
import space.kscience.snark.html.SnarkHtmlPlugin import space.kscience.snark.html.static.staticSite
import space.kscience.snark.html.readResources
import space.kscience.snark.html.static
import java.nio.file.Path import java.nio.file.Path
fun main(args: Array<String>) { suspend fun main(args: Array<String>) = coroutineScope{
val context = Context{
plugin(SnarkHtml)
}
val destinationPath = args.firstOrNull() ?: "build/public" val destinationPath = args.firstOrNull() ?: "build/public"
val snark = Global.request(SnarkHtmlPlugin) val snark = context.request(SnarkHtml)
val siteData = snark.readResources("common", "home", "magprog") val siteData = snark.readResources("common", "home", "magprog")
snark.static(siteData, Path.of(destinationPath), block = SiteBuilder::spcSite) snark.staticSite(siteData, Path.of(destinationPath), content = spcSite)
} }

View File

@ -1,7 +1,7 @@
package html5up.forty package html5up.forty
import kotlinx.html.* import kotlinx.html.*
import space.kscience.snark.html.WebPage import space.kscience.snark.html.PageContext
internal fun FlowContent.fortyMenu() { internal fun FlowContent.fortyMenu() {
@ -200,7 +200,8 @@ internal fun FlowContent.fortyFooter() {
} }
} }
context(WebPage) internal fun BODY.fortyScripts() { context(PageContext)
internal fun BODY.fortyScripts() {
script { script {
src = resolveRef("assets/js/jquery.min.js") src = resolveRef("assets/js/jquery.min.js")
} }

View File

@ -1,9 +1,10 @@
package html5up.forty package html5up.forty
import kotlinx.html.* import kotlinx.html.*
import space.kscience.snark.html.WebPage import space.kscience.snark.html.PageContext
context(WebPage) internal fun HTML.landing(){ context(PageContext)
internal fun HTML.landing() {
head { head {
title { title {
} }
@ -30,7 +31,7 @@ context(WebPage) internal fun HTML.landing(){
div { div {
id = "wrapper" id = "wrapper"
// Header // Header
// Note: The "styleN" class below should match that of the banner element. --> // Note: The "styleN" class below should match that of the banner element. -->
header("alt style2") { header("alt style2") {
id = "header" id = "header"
@ -48,7 +49,7 @@ context(WebPage) internal fun HTML.landing(){
} }
fortyMenu() fortyMenu()
// Banner // Banner
// Note: The "styleN" class below should match that of the header element. // Note: The "styleN" class below should match that of the header element.
section("style2") { section("style2") {
id = "banner" id = "banner"
div("inner") { div("inner") {

View File

@ -1,9 +1,10 @@
package html5up.forty package html5up.forty
import kotlinx.html.* import kotlinx.html.*
import space.kscience.snark.html.WebPage import space.kscience.snark.html.PageContext
context(WebPage) internal fun HTML.fortyPage(){ context(PageContext)
internal fun HTML.fortyPage() {
head { head {
title { title {
} }