diff --git a/core/src/ba/sake/flatmark/FlatmarkGenerator.scala b/core/src/ba/sake/flatmark/FlatmarkGenerator.scala index f4702b9..61854e4 100644 --- a/core/src/ba/sake/flatmark/FlatmarkGenerator.scala +++ b/core/src/ba/sake/flatmark/FlatmarkGenerator.scala @@ -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) + } + } } } } diff --git a/core/src/ba/sake/flatmark/FrontMatterUtils.scala b/core/src/ba/sake/flatmark/FrontMatterUtils.scala index d880478..a59637e 100644 --- a/core/src/ba/sake/flatmark/FrontMatterUtils.scala +++ b/core/src/ba/sake/flatmark/FrontMatterUtils.scala @@ -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 @@ -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) } diff --git a/core/src/ba/sake/flatmark/HashUtils.scala b/core/src/ba/sake/flatmark/HashUtils.scala index 6988b1f..82c4623 100644 --- a/core/src/ba/sake/flatmark/HashUtils.scala +++ b/core/src/ba/sake/flatmark/HashUtils.scala @@ -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 } } diff --git a/core/src/ba/sake/flatmark/HeadingHierarchyExtractor.scala b/core/src/ba/sake/flatmark/HeadingHierarchyExtractor.scala index ee5d5f1..df70436 100644 --- a/core/src/ba/sake/flatmark/HeadingHierarchyExtractor.scala +++ b/core/src/ba/sake/flatmark/HeadingHierarchyExtractor.scala @@ -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 } } } diff --git a/ssr/src/ba/sake/flatmark/ssr/FlatmarkCodeHighlighter.scala b/ssr/src/ba/sake/flatmark/ssr/FlatmarkCodeHighlighter.scala index dc186d0..c283294 100644 --- a/ssr/src/ba/sake/flatmark/ssr/FlatmarkCodeHighlighter.scala +++ b/ssr/src/ba/sake/flatmark/ssr/FlatmarkCodeHighlighter.scala @@ -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) diff --git a/ssr/src/ba/sake/flatmark/ssr/FlatmarkGraphvizRenderer.scala b/ssr/src/ba/sake/flatmark/ssr/FlatmarkGraphvizRenderer.scala index 7ac6fc9..47fa524 100644 --- a/ssr/src/ba/sake/flatmark/ssr/FlatmarkGraphvizRenderer.scala +++ b/ssr/src/ba/sake/flatmark/ssr/FlatmarkGraphvizRenderer.scala @@ -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) diff --git a/ssr/src/ba/sake/flatmark/ssr/FlatmarkMathRenderer.scala b/ssr/src/ba/sake/flatmark/ssr/FlatmarkMathRenderer.scala index 116423a..40866a4 100644 --- a/ssr/src/ba/sake/flatmark/ssr/FlatmarkMathRenderer.scala +++ b/ssr/src/ba/sake/flatmark/ssr/FlatmarkMathRenderer.scala @@ -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) diff --git a/ssr/src/ba/sake/flatmark/ssr/FlatmarkMermaidRenderer.scala b/ssr/src/ba/sake/flatmark/ssr/FlatmarkMermaidRenderer.scala index 48301e8..8ef6aa5 100644 --- a/ssr/src/ba/sake/flatmark/ssr/FlatmarkMermaidRenderer.scala +++ b/ssr/src/ba/sake/flatmark/ssr/FlatmarkMermaidRenderer.scala @@ -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)