Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions core/src/ba/sake/flatmark/FlatmarkGenerator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -417,11 +417,18 @@ class FlatmarkGenerator(ssrServerUrl: String, webDriverHolder: WebDriverHolder,
// prepend base URL to all relative URLs
layoutContext.site.baseUrl.foreach { baseUrl =>
// TODO handle srcset
// Combine all attribute selectors into a single query for better performance
val selector = """[href^="/"],[src^="/"],[cite^="/"],[action^="/"],[formaction^="/"],[data^="/"],[poster^="/"],[manifest^="/"]"""
val urlAttrs = List("href", "src", "cite", "action", "formaction", "data", "poster", "manifest")
urlAttrs.foreach { attrName =>
document.select(s"""[${attrName}^="/"]""").forEach { elem =>
val attrValue = elem.attr(attrName)
elem.attr(attrName, baseUrl + attrValue)
document.select(selector).forEach { elem =>
// Each element matches on at least one attribute, check which ones need updating
urlAttrs.foreach { attrName =>
if (elem.hasAttr(attrName)) {
val attrValue = elem.attr(attrName)
if (attrValue.startsWith("/")) {
elem.attr(attrName, baseUrl + attrValue)
}
}
}
}
}
Expand Down
26 changes: 12 additions & 14 deletions core/src/ba/sake/flatmark/FrontMatterUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ object FrontMatterUtils {
var hasYamlFrontMatter = false
var firstTripleDashIndex = -1
var secondTripleDashIndex = -1

// Single pass through the lines
val lines = templateRaw.split('\n')
var i = 0
boundary {
val iter = templateRaw.linesIterator
var i = 0
while iter.hasNext do {
val line = iter.next().trim
if line.nonEmpty then {
if line == "---" then {
while (i < lines.length) {
val line = lines(i).trim
if (line.nonEmpty) {
if (line == "---") {
if (firstTripleDashIndex == -1) firstTripleDashIndex = i
else if (secondTripleDashIndex == -1) {
secondTripleDashIndex = i
Expand All @@ -32,14 +34,10 @@ object FrontMatterUtils {
i += 1
}
}
if hasYamlFrontMatter then {
val yaml = templateRaw.linesIterator
.slice(firstTripleDashIndex + 1, firstTripleDashIndex + 1 + secondTripleDashIndex - firstTripleDashIndex - 1)
.mkString("\n")
val content = templateRaw.linesIterator
.drop(secondTripleDashIndex + 1)
.mkString("\n")
.trim

if (hasYamlFrontMatter) {
val yaml = lines.slice(firstTripleDashIndex + 1, secondTripleDashIndex).mkString("\n")
val content = lines.drop(secondTripleDashIndex + 1).mkString("\n").trim
(yaml, content)
} else ("", templateRaw)
}
Expand Down
16 changes: 15 additions & 1 deletion core/src/ba/sake/flatmark/HashUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@ object HashUtils {
val md = MessageDigest.getInstance("MD5")
val theMD5digest = md.digest(bytesOfMessage)
val b64 = Base64.getEncoder.encode(theMD5digest)
new String(b64, "UTF-8").replace('/', '-').replace('=', '_').replace('+', '$')
val encoded = new String(b64, "UTF-8")

// Use StringBuilder for efficient string manipulation
val result = new StringBuilder(encoded.length)
var i = 0
while (i < encoded.length) {
encoded.charAt(i) match {
case '/' => result.append('-')
case '=' => result.append('_')
case '+' => result.append('$')
case c => result.append(c)
}
i += 1
}
result.toString
}
}
47 changes: 25 additions & 22 deletions core/src/ba/sake/flatmark/HeadingHierarchyExtractor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,52 +23,55 @@ object HeadingHierarchyExtractor {

// This will hold the current "parent" for each heading level.
// Index 0 for H1, index 1 for H2, etc. Max level is 6, so size 6.
val currentParents: Array[Option[Heading]] = Array.fill(6)(None)
// Using Array[Heading | Null] for better performance
val currentParents: Array[Heading | Null] = new Array[Heading | Null](6)

// Select all heading tags in document order
val allHeadings = doc.select("h1, h2, h3, h4, h5, h6").asScala // Convert Java Elements to Scala Seq

for (headingElement <- allHeadings) {
val tagName = headingElement.tagName() // e.g., "h1", "h2"
val level = tagName.substring(1).toInt // Extract level (1-6)
val level = tagName.charAt(1) - '0' // Extract level (1-6) more efficiently

val newHeading = Heading(level, headingElement.text(), headingElement.attr("id").trim)

if (level == 1) {
// H1 is always a top-level heading
topLevelHeadings += newHeading
currentParents(0) = Some(newHeading) // Set H1 as the current parent for H1s
currentParents(0) = newHeading // Set H1 as the current parent for H1s
// Reset parents for lower levels
for (i <- 1 until 6) {
currentParents(i) = None
var i = 1
while (i < 6) {
currentParents(i) = null
i += 1
}
} else {
// Find the appropriate parent for this heading
// Iterate upwards from the previous level
var parentFound: Option[Heading] = None
boundary {
for (i <- (level - 2) to 0 by -1) { // level - 2 because array is 0-indexed and we need the *next* higher level
if (currentParents(i).isDefined) {
parentFound = currentParents(i)
boundary.break() // Break out of the loop once a parent is found
}
var parentFound: Heading | Null = null
var i = level - 2
while (i >= 0 && parentFound == null) {
if (currentParents(i) != null) {
parentFound = currentParents(i)
}
i -= 1
}

parentFound match {
case Some(parent) =>
parent.children += newHeading
case None =>
// If no higher-level parent is found, it's a top-level heading
// (e.g., an H2 without a preceding H1)
topLevelHeadings += newHeading
if (parentFound != null) {
parentFound.children += newHeading
} else {
// If no higher-level parent is found, it's a top-level heading
// (e.g., an H2 without a preceding H1)
topLevelHeadings += newHeading
}

// Set this heading as the current parent for its own level
currentParents(level - 1) = Some(newHeading)
currentParents(level - 1) = newHeading
// Reset parents for lower levels (any subsequent Hx that are children of this one)
for (i <- level until 6) {
currentParents(i) = None
var j = level
while (j < 6) {
currentParents(j) = null
j += 1
}
}
}
Expand Down
1 change: 0 additions & 1 deletion ssr/src/ba/sake/flatmark/ssr/FlatmarkCodeHighlighter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ class FlatmarkCodeHighlighter(ssrServerUrl: String, webDriverHolder: WebDriverHo
val encodedCodeStr = URLEncoder.encode(codeStr, "utf-8")
val encodedCodeLang = codeLang.map(lang => URLEncoder.encode(lang, "utf-8")).getOrElse("plaintext")
val url = s"${ssrServerUrl}/ssr/highlightjs?code=${encodedCodeStr}&lang=${encodedCodeLang}"
webDriverHolder.driver.manage().deleteAllCookies()
webDriverHolder.driver.get(url)
val waitCondition = new WebDriverWait(webDriverHolder.driver, Duration.ofSeconds(5))
waitCondition.until(_ => webDriverHolder.driver.executeScript("return renderFinished;") == true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ class FlatmarkGraphvizRenderer(ssrServerUrl: String, webDriverHolder: WebDriverH
val encodedDotStr = URLEncoder.encode(dotStr, "utf-8")
val encodedEngine = URLEncoder.encode(engine, "utf-8")
val url = s"${ssrServerUrl}/ssr/graphviz?source=${encodedDotStr}&engine=${encodedEngine}"
webDriverHolder.driver.manage().deleteAllCookies()
webDriverHolder.driver.get(url)
val waitCondition = new WebDriverWait(webDriverHolder.driver, Duration.ofSeconds(5))
waitCondition.until(_ => webDriverHolder.driver.executeScript("return renderFinished;") == true)
Expand Down
1 change: 0 additions & 1 deletion ssr/src/ba/sake/flatmark/ssr/FlatmarkMathRenderer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ class FlatmarkMathRenderer(ssrServerUrl: String, webDriverHolder: WebDriverHolde
logger.debug("Render math start")
val encodedMathStr = URLEncoder.encode(mathStr, "utf-8")
val url = s"${ssrServerUrl}/ssr/katex?source=${encodedMathStr}"
webDriverHolder.driver.manage().deleteAllCookies()
webDriverHolder.driver.get(url)
val waitCondition = new WebDriverWait(webDriverHolder.driver, Duration.ofSeconds(5))
waitCondition.until(_ => webDriverHolder.driver.executeScript("return renderFinished;") == true)
Expand Down
1 change: 0 additions & 1 deletion ssr/src/ba/sake/flatmark/ssr/FlatmarkMermaidRenderer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ class FlatmarkMermaidRenderer(ssrServerUrl: String, webDriverHolder: WebDriverHo
logger.debug("Render mermaid start")
val encodedSource = URLEncoder.encode(source, "utf-8")
val url = s"${ssrServerUrl}/ssr/mermaid?source=${encodedSource}"
webDriverHolder.driver.manage().deleteAllCookies()
webDriverHolder.driver.get(url)
val waitCondition = new WebDriverWait(webDriverHolder.driver, Duration.ofSeconds(5))
waitCondition.until(_ => webDriverHolder.driver.executeScript("return renderFinished;") == true)
Expand Down