inject back button inline for docs' H1 headers, fetch markdown titles progressively, and add deployment script
This commit is contained in:
parent
b82af3dceb
commit
d307ed2a04
111
bin/deploy_site
Executable file
111
bin/deploy_site
Executable file
@ -0,0 +1,111 @@
|
||||
#!/bin/bash
|
||||
|
||||
#
|
||||
# Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
#
|
||||
|
||||
function checkState() {
|
||||
if [[ $? != 0 ]]; then
|
||||
echo
|
||||
echo -- rsync failed. deploy was not finished. deployed version has not been affected
|
||||
echo
|
||||
exit 100
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
# default target settings
|
||||
case $1 in
|
||||
com)
|
||||
SSH_HOST=sergeych@lynglang.com # host to deploy to
|
||||
SSH_PORT=22 # ssh port on it
|
||||
ROOT=/bigstore/sergeych_pub/lyng # directory to rsync to
|
||||
;;
|
||||
# com)
|
||||
# SSH_HOST=vvk@front-01.neurodatalab.com
|
||||
# ROOT=/home/vvk
|
||||
# ;;
|
||||
*)
|
||||
echo "*** ERROR: target not specified (use deploy com | dev)"
|
||||
echo "*** stop"
|
||||
exit 101
|
||||
esac
|
||||
|
||||
die() { echo "ERROR: $*" 1>&2 ; exit 1; }
|
||||
#rm build/distributions/*.js > /dev/null
|
||||
#rm build/distributions/*.js.map > /dev/null
|
||||
|
||||
#./gradlew clean incrementRevision jsBrowserDistribution || die "compilation failed"
|
||||
#./gradlew incrementRevision jsBrowserDistribution
|
||||
#./gradlew jsBrowserDistribution
|
||||
./gradlew incrementRevision jsBrowserDistribution
|
||||
#./gradlew jsBrowserDistribution
|
||||
|
||||
if [[ $? != 0 ]]; then
|
||||
echo
|
||||
echo -- build failed. deploy aborted.
|
||||
echo
|
||||
exit 100
|
||||
fi
|
||||
|
||||
#exit 0
|
||||
|
||||
# Prepare working dir
|
||||
ssh -p ${SSH_PORT} ${SSH_HOST} "
|
||||
cd ${ROOT}
|
||||
rm -rd build 2>/dev/null
|
||||
if [ -d release ]; then
|
||||
echo copying current release
|
||||
cp -r release build
|
||||
else
|
||||
echo creating first release
|
||||
mkdir release
|
||||
mkdir build
|
||||
fi
|
||||
";
|
||||
|
||||
# sync files
|
||||
SRC=./build/dist/js/productionExecutable
|
||||
rsync -e "ssh -p ${SSH_PORT}" -avz -r -d --delete ${SRC}/* ${SSH_HOST}:${ROOT}/build/dist
|
||||
checkState
|
||||
rsync -e "ssh -p ${SSH_PORT}" -avz ./static/* ${SSH_HOST}:${ROOT}/build/dist
|
||||
#checkState
|
||||
#rsync -e "ssh -p ${SSH_PORT}" -avz -r -d --delete private_data/* ${SSH_HOST}:${ROOT}/build/private_data
|
||||
#checkState
|
||||
|
||||
echo
|
||||
echo finalizing the deploy...
|
||||
ssh -p ${SSH_PORT} ${SSH_HOST} "
|
||||
cd ${ROOT}
|
||||
rm -rd release~
|
||||
mv release release~
|
||||
mv build release
|
||||
cd release
|
||||
# in this project we needn't restart back when we deploy the front
|
||||
# ~/bin/restart_service
|
||||
";
|
||||
|
||||
if [[ $? != 0 ]]; then
|
||||
echo
|
||||
echo -- finalization failed. the rease might be corrupt. rolling back is not yet implemented.
|
||||
echo
|
||||
exit 100
|
||||
fi
|
||||
|
||||
echo
|
||||
echo Deploy successful
|
||||
echo
|
||||
|
||||
@ -371,7 +371,9 @@ private fun DocsPage(
|
||||
}
|
||||
}
|
||||
|
||||
PageTemplate(title = title, showBack = true) {
|
||||
// Do not render a separate header here. We will show the markdown's own H1
|
||||
// and inject a back button inline to that H1 after the content mounts.
|
||||
PageTemplate(title = null, showBack = false) {
|
||||
if (error != null) {
|
||||
Div({ classes("alert", "alert-danger") }) { Text(error) }
|
||||
} else if (html == null) {
|
||||
@ -395,6 +397,31 @@ private fun DocsPage(
|
||||
val el = contentEl ?: return@LaunchedEffect
|
||||
if (html == null) return@LaunchedEffect
|
||||
window.requestAnimationFrame {
|
||||
// Insert an inline back button to the left of the first H1
|
||||
try {
|
||||
val firstH1 = el.querySelector("h1") as? HTMLElement
|
||||
if (firstH1 != null && firstH1.querySelector(".doc-back-btn") == null) {
|
||||
val back = el.ownerDocument!!.createElement("div") as HTMLDivElement
|
||||
back.className = "btn btn-outline btn-sm me-2 align-middle doc-back-btn "
|
||||
back.setAttribute("aria-label","Back")
|
||||
back.onclick = { ev ->
|
||||
ev.preventDefault()
|
||||
try {
|
||||
if (window.history.length > 1) window.history.back() else window.location.hash = "#"
|
||||
} catch (e: dynamic) {
|
||||
window.location.hash = "#"
|
||||
}
|
||||
null
|
||||
}
|
||||
val icon = el.ownerDocument!!.createElement("i") as HTMLElement
|
||||
icon.className = "bi bi-arrow-left"
|
||||
back.appendChild(icon)
|
||||
// Insert at the start of H1 content
|
||||
firstH1.insertBefore(back, firstH1.firstChild)
|
||||
}
|
||||
} catch (_: Throwable) {
|
||||
// best-effort; ignore DOM issues
|
||||
}
|
||||
val currentPath = routeToPath(route) // without fragment
|
||||
val basePath = currentPath.substringBeforeLast('/', "docs")
|
||||
rewriteImages(el, basePath)
|
||||
@ -872,6 +899,8 @@ private object MainScopeProvider {
|
||||
private fun ReferencePage() {
|
||||
var docs by remember { mutableStateOf<List<String>?>(null) }
|
||||
var error by remember { mutableStateOf<String?>(null) }
|
||||
// Titles resolved from the first H1 of each markdown document
|
||||
var titles by remember { mutableStateOf<Map<String, String>>(emptyMap()) }
|
||||
|
||||
// Load docs index once
|
||||
LaunchedEffect(Unit) {
|
||||
@ -890,6 +919,28 @@ private fun ReferencePage() {
|
||||
}
|
||||
}
|
||||
|
||||
// Once we have the docs list, fetch their titles (H1) progressively
|
||||
LaunchedEffect(docs) {
|
||||
val list = docs ?: return@LaunchedEffect
|
||||
// Reset titles when list changes
|
||||
titles = emptyMap()
|
||||
// Fetch sequentially to avoid flooding; fast enough for small/medium doc sets
|
||||
for (path in list) {
|
||||
try {
|
||||
val mdPath = if (path.startsWith("docs/")) path else "docs/$path"
|
||||
val url = "./" + encodeURI(mdPath)
|
||||
val resp = window.fetch(url).await()
|
||||
if (!resp.ok) continue
|
||||
val text = resp.text().await()
|
||||
val title = extractTitleFromMarkdown(text) ?: path.substringAfterLast('/')
|
||||
// Update state incrementally
|
||||
titles = titles + (path to title)
|
||||
} catch (_: Throwable) {
|
||||
// ignore individual failures; fallback will be filename
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
H2({ classes("h5", "mb-3") }) { Text("Reference") }
|
||||
P({ classes("text-muted") }) { Text("Browse all documentation pages included in this build.") }
|
||||
|
||||
@ -897,7 +948,24 @@ private fun ReferencePage() {
|
||||
error != null -> Div({ classes("alert", "alert-danger") }) { Text(error!!) }
|
||||
docs == null -> P { Text("Loading index…") }
|
||||
docs!!.isEmpty() -> P { Text("No documents found.") }
|
||||
else -> UnsafeRawHtml(renderReferenceListHtml(docs!!))
|
||||
else -> {
|
||||
Ul({ classes("list-group") }) {
|
||||
docs!!.sorted().forEach { path ->
|
||||
val displayTitle = titles[path] ?: path.substringAfterLast('/')
|
||||
Li({ classes("list-group-item", "d-flex", "justify-content-between", "align-items-center") }) {
|
||||
Div({}) {
|
||||
A(attrs = {
|
||||
classes("link-body-emphasis", "text-decoration-none")
|
||||
attr("href", "#/$path")
|
||||
}) { Text(displayTitle) }
|
||||
Br()
|
||||
Small({ classes("text-muted") }) { Text(path) }
|
||||
}
|
||||
I({ classes("bi", "bi-chevron-right") })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1316,6 +1384,8 @@ internal fun rewriteAnchors(
|
||||
val asEl = root.querySelectorAll("a")
|
||||
for (i in 0 until asEl.length) {
|
||||
val a = asEl.item(i) as? HTMLAnchorElement ?: continue
|
||||
// Skip the inline Docs back button we inject before the first H1
|
||||
if (a.classList.contains("doc-back-btn") || a.getAttribute("data-no-spa") == "true") continue
|
||||
val href = a.getAttribute("href") ?: continue
|
||||
// Skip external and already-SPA hashes
|
||||
if (
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user