Skip to content

Commit

Permalink
Merge pull request #621 from typelevel/theme/includes
Browse files Browse the repository at this point in the history
promote most internals of Helium's include directive to public API in laika.theme.*
  • Loading branch information
jenshalm authored Jul 7, 2024
2 parents 38a67fe + 403ef34 commit 4cade29
Show file tree
Hide file tree
Showing 9 changed files with 278 additions and 49 deletions.
3 changes: 2 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,8 @@ lazy val io = project.in(file("io"))
.settings(
name := "laika-io",
libraryDependencies ++= Seq(catsEffect, fs2IO, munit, munitCE3),
Test / scalacOptions ~= disableMissingInterpolatorWarning
Test / scalacOptions ~= disableMissingInterpolatorWarning,
mimaBinaryIssueFilters ++= MimaFilters.includeRefactoring
)

lazy val pdf = project.in(file("pdf"))
Expand Down
42 changes: 18 additions & 24 deletions io/src/main/scala/laika/helium/config/api.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import laika.theme.config.{
Color,
DocumentMetadata,
FontDefinition,
IncludeCSSConfig,
IncludeJSConfig,
ScriptAttributes,
StyleAttributes
}
Expand Down Expand Up @@ -445,12 +447,12 @@ private[helium] trait SiteOps extends SingleConfigOps with CopyOps {

}

private def withStyleIncludes(newValue: StyleIncludes): Helium = {
private def withStyleIncludes(newValue: IncludeCSSConfig): Helium = {
val newContent = currentContent.copy(styleIncludes = newValue)
copyWith(helium.siteSettings.copy(content = newContent))
}

private def withScriptIncludes(newValue: ScriptIncludes): Helium = {
private def withScriptIncludes(newValue: IncludeJSConfig): Helium = {
val newContent = currentContent.copy(scriptIncludes = newValue)
copyWith(helium.siteSettings.copy(content = newContent))
}
Expand All @@ -464,8 +466,7 @@ private[helium] trait SiteOps extends SingleConfigOps with CopyOps {
attributes: StyleAttributes = StyleAttributes.defaults,
condition: Document => Boolean = _ => true
): Helium = {
val newInclude = ExternalCSS(url, attributes, condition)
val styleIncludes = currentContent.styleIncludes.add(newInclude)
val styleIncludes = currentContent.styleIncludes.externalCSS(url, attributes, condition)
withStyleIncludes(styleIncludes)
}

Expand All @@ -481,8 +482,7 @@ private[helium] trait SiteOps extends SingleConfigOps with CopyOps {
attributes: StyleAttributes = StyleAttributes.defaults,
condition: Document => Boolean = _ => true
): Helium = {
val newInclude = InternalCSS(searchPath, attributes, condition)
val styleIncludes = currentContent.styleIncludes.add(newInclude)
val styleIncludes = currentContent.styleIncludes.internalCSS(searchPath, attributes, condition)
withStyleIncludes(styleIncludes)
}

Expand All @@ -494,8 +494,7 @@ private[helium] trait SiteOps extends SingleConfigOps with CopyOps {
content: String,
condition: Document => Boolean = _ => true
): Helium = {
val newInclude = InlineCSS(content, condition)
val styleIncludes = currentContent.styleIncludes.add(newInclude)
val styleIncludes = currentContent.styleIncludes.inlineCSS(content, condition)
withStyleIncludes(styleIncludes)
}

Expand All @@ -508,8 +507,7 @@ private[helium] trait SiteOps extends SingleConfigOps with CopyOps {
attributes: ScriptAttributes = ScriptAttributes.defaults,
condition: Document => Boolean = _ => true
): Helium = {
val newInclude = ExternalJS(url, attributes, condition)
val scriptIncludes = currentContent.scriptIncludes.add(newInclude)
val scriptIncludes = currentContent.scriptIncludes.externalJS(url, attributes, condition)
withScriptIncludes(scriptIncludes)
}

Expand All @@ -525,8 +523,7 @@ private[helium] trait SiteOps extends SingleConfigOps with CopyOps {
attributes: ScriptAttributes = ScriptAttributes.defaults,
condition: Document => Boolean = _ => true
): Helium = {
val newInclude = InternalJS(searchPath, attributes, condition)
val scriptIncludes = currentContent.scriptIncludes.add(newInclude)
val scriptIncludes = currentContent.scriptIncludes.internalJS(searchPath, attributes, condition)
withScriptIncludes(scriptIncludes)
}

Expand All @@ -539,8 +536,7 @@ private[helium] trait SiteOps extends SingleConfigOps with CopyOps {
isModule: Boolean = false,
condition: Document => Boolean = _ => true
): Helium = {
val newInclude = InlineJS(content, isModule, condition)
val scriptIncludes = currentContent.scriptIncludes.add(newInclude)
val scriptIncludes = currentContent.scriptIncludes.inlineJS(content, isModule, condition)
withScriptIncludes(scriptIncludes)
}

Expand Down Expand Up @@ -976,10 +972,10 @@ private[helium] trait EPUBOps extends SingleConfigOps with CopyOps {
copyWith(helium.epubSettings.copy(layout = newLayout))
}

private def withStyleIncludes(newValue: StyleIncludes): Helium =
private def withStyleIncludes(newValue: IncludeCSSConfig): Helium =
copyWith(helium.epubSettings.copy(styleIncludes = newValue))

private def withScriptIncludes(newValue: ScriptIncludes): Helium =
private def withScriptIncludes(newValue: IncludeJSConfig): Helium =
copyWith(helium.epubSettings.copy(scriptIncludes = newValue))

/** Auto-links CSS documents from the specified path, which may point to a single CSS document
Expand All @@ -994,8 +990,8 @@ private[helium] trait EPUBOps extends SingleConfigOps with CopyOps {
attributes: StyleAttributes = StyleAttributes.defaults,
condition: Document => Boolean = _ => true
): Helium = {
val newInclude = InternalCSS(searchPath, attributes, condition)
val styleIncludes = helium.epubSettings.styleIncludes.add(newInclude)
val styleIncludes =
helium.epubSettings.styleIncludes.internalCSS(searchPath, attributes, condition)
withStyleIncludes(styleIncludes)
}

Expand All @@ -1007,8 +1003,7 @@ private[helium] trait EPUBOps extends SingleConfigOps with CopyOps {
content: String,
condition: Document => Boolean = _ => true
): Helium = {
val newInclude = InlineCSS(content, condition)
val styleIncludes = helium.epubSettings.styleIncludes.add(newInclude)
val styleIncludes = helium.epubSettings.styleIncludes.inlineCSS(content, condition)
withStyleIncludes(styleIncludes)
}

Expand All @@ -1024,8 +1019,8 @@ private[helium] trait EPUBOps extends SingleConfigOps with CopyOps {
attributes: ScriptAttributes = ScriptAttributes.defaults,
condition: Document => Boolean = _ => true
): Helium = {
val newInclude = InternalJS(searchPath, attributes, condition)
val scriptIncludes = helium.epubSettings.scriptIncludes.add(newInclude)
val scriptIncludes =
helium.epubSettings.scriptIncludes.internalJS(searchPath, attributes, condition)
withScriptIncludes(scriptIncludes)
}

Expand All @@ -1038,8 +1033,7 @@ private[helium] trait EPUBOps extends SingleConfigOps with CopyOps {
isModule: Boolean = false,
condition: Document => Boolean = _ => true
): Helium = {
val newInclude = InlineJS(content, isModule, condition)
val scriptIncludes = helium.epubSettings.scriptIncludes.add(newInclude)
val scriptIncludes = helium.epubSettings.scriptIncludes.inlineJS(content, isModule, condition)
withScriptIncludes(scriptIncludes)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import laika.api.bundle.{ PathTranslator, TemplateDirectives }
import laika.ast.{ TemplateSpanSequence, TemplateString }
import laika.config.{ LaikaKeys, Versions }
import laika.helium.Helium
import laika.theme.config.IncludeDirective

/** @author Jens Halm
*/
Expand Down Expand Up @@ -68,11 +69,11 @@ private[helium] object HeliumDirectives {
Seq(
initVersions,
initPreview,
HeliumHeadDirectives.includeCSS(
IncludeDirective.forCSS(
helium.siteSettings.content.styleIncludes,
helium.epubSettings.styleIncludes
),
HeliumHeadDirectives.includeJS(
IncludeDirective.forJS(
helium.siteSettings.content.scriptIncludes,
helium.epubSettings.scriptIncludes
)
Expand Down
5 changes: 3 additions & 2 deletions io/src/main/scala/laika/helium/internal/config/layout.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import laika.ast.Path.Root
import laika.ast.*
import laika.helium.config.*
import laika.parse.{ SourceCursor, SourceFragment }
import laika.theme.config.{ IncludeCSSConfig, IncludeJSConfig }

private[helium] sealed trait CommonLayout {
def defaultBlockSpacing: Length
Expand All @@ -21,8 +22,8 @@ private[helium] case class WebLayout(

private[helium] case class WebContent(
favIcons: Seq[Favicon] = Nil,
styleIncludes: StyleIncludes = StyleIncludes.empty,
scriptIncludes: ScriptIncludes = ScriptIncludes.empty,
styleIncludes: IncludeCSSConfig = IncludeCSSConfig.empty,
scriptIncludes: IncludeJSConfig = IncludeJSConfig.empty,
topNavigationBar: TopNavigationBar = TopNavigationBar.default,
mainNavigation: MainNavigation = MainNavigation(),
pageNavigation: PageNavigation = PageNavigation(),
Expand Down
12 changes: 9 additions & 3 deletions io/src/main/scala/laika/helium/internal/config/settings.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
package laika.helium.internal.config

import laika.config.{ CoverImage, Versions }
import laika.theme.config.{ BookConfig, DocumentMetadata, FontDefinition }
import laika.theme.config.{
BookConfig,
DocumentMetadata,
FontDefinition,
IncludeCSSConfig,
IncludeJSConfig
}

private[helium] trait CommonSettings {
def themeFonts: ThemeFonts
Expand Down Expand Up @@ -45,8 +51,8 @@ private[helium] case class EPUBSettings(
fontSizes: FontSizes,
colors: ColorSet,
darkMode: Option[ColorSet],
styleIncludes: StyleIncludes = StyleIncludes.empty,
scriptIncludes: ScriptIncludes = ScriptIncludes.empty,
styleIncludes: IncludeCSSConfig = IncludeCSSConfig.empty,
scriptIncludes: IncludeJSConfig = IncludeJSConfig.empty,
layout: EPUBLayout,
coverImages: Seq[CoverImage]
) extends DarkModeSupport {
Expand Down
181 changes: 181 additions & 0 deletions io/src/main/scala/laika/theme/config/IncludeDirective.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
/*
* Copyright 2012-2024 the original author or authors.
*
* 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.
*/

package laika.theme.config

import laika.api.bundle.TemplateDirectives
import laika.ast.{ Document, Path }
import laika.theme.config.internal.{
ExternalCSS,
ExternalJS,
IncludeDirectiveBuilder,
InlineCSS,
InlineJS,
InternalCSS,
InternalJS,
ScriptIncludes,
StyleIncludes
}

/** Represents the configuration for the `@:includeCSS` directive
* for a single format (e.g. HTML or EPUB).
*/
class IncludeCSSConfig private (
private[config] val includes: StyleIncludes = StyleIncludes.empty
) {

/** Links an external CSS resource from the specified URL.
*
* The `condition` attribute can be used to only include the CSS when some user-defined predicates are satisfied.
*/
def externalCSS(
url: String,
attributes: StyleAttributes = StyleAttributes.defaults,
condition: Document => Boolean = _ => true
): IncludeCSSConfig = {
val newInclude = ExternalCSS(url, attributes, condition)
new IncludeCSSConfig(includes.add(newInclude))
}

/** Auto-links CSS documents from the specified path, which may point to a single CSS document
* or a directory.
* In case of a directory it will be searched recursively and all CSS files found within it
* will be linked in the HTML head.
*
* The `condition` attribute can be used to only include the CSS when some user-defined predicates are satisfied.
*/
def internalCSS(
searchPath: Path,
attributes: StyleAttributes = StyleAttributes.defaults,
condition: Document => Boolean = _ => true
): IncludeCSSConfig = {
val newInclude = InternalCSS(searchPath, attributes, condition)
new IncludeCSSConfig(includes.add(newInclude))
}

/** Inserts inline style declarations into the HTML head.
*
* The `condition` attribute can be used to only include the CSS when some user-defined predicates are satisfied.
*/
def inlineCSS(
content: String,
condition: Document => Boolean = _ => true
): IncludeCSSConfig = {
val newInclude = InlineCSS(content, condition)
new IncludeCSSConfig(includes.add(newInclude))
}

}

object IncludeCSSConfig {

val empty: IncludeCSSConfig = new IncludeCSSConfig()

}

/** Represents the configuration for the `@:includeJS` directive
* for a single format (e.g. HTML or EPUB).
*/
class IncludeJSConfig private (
private[config] val includes: ScriptIncludes = ScriptIncludes.empty
) {

/** Links an external JavaScript resource from the specified URL.
*
* The `condition` attribute can be used to only include the CSS when some user-defined predicates are satisfied.
*/
def externalJS(
url: String,
attributes: ScriptAttributes = ScriptAttributes.defaults,
condition: Document => Boolean = _ => true
): IncludeJSConfig = {
val newInclude = ExternalJS(url, attributes, condition)
new IncludeJSConfig(includes.add(newInclude))
}

/** Auto-links JavaScript documents from the specified path, which may point to a single JS document
* or a directory.
* In case of a directory it will be searched recursively and all `*.js` files found within it
* will be linked in the HTML head.
*
* The `condition` attribute can be used to only include the CSS when some user-defined predicates are satisfied.
*/
def internalJS(
searchPath: Path,
attributes: ScriptAttributes = ScriptAttributes.defaults,
condition: Document => Boolean = _ => true
): IncludeJSConfig = {
val newInclude = InternalJS(searchPath, attributes, condition)
new IncludeJSConfig(includes.add(newInclude))
}

/** Inserts inline scripts into the HTML head.
*
* The `condition` attribute can be used to only include the CSS when some user-defined predicates are satisfied.
*/
def inlineJS(
content: String,
isModule: Boolean = false,
condition: Document => Boolean = _ => true
): IncludeJSConfig = {
val newInclude = InlineJS(content, isModule, condition)
new IncludeJSConfig(includes.add(newInclude))
}

}

object IncludeJSConfig {

val empty: IncludeJSConfig = new IncludeJSConfig()

}

/** Builders for the `@:includeCSS` and `@:includeJS` directives,
* which can be used in templates for HTML and EPUB output.
*/
object IncludeDirective {

/** Creates an instance of the `@:includeCSS` directive that
* can be added to any `ExtensionBundle`.
*
* The configurations for HTML and EPUB are separate and either
* of the two can be empty.
*
* They can also point to the same instance in case the two formats
* should use the same style includes.
*/
def forCSS(
htmlConfig: IncludeCSSConfig,
epubConfig: IncludeCSSConfig = IncludeCSSConfig.empty
): TemplateDirectives.Directive =
IncludeDirectiveBuilder.includeCSS(htmlConfig.includes, epubConfig.includes)

/** Creates an instance of the `@:includeJS` directive that
* can be added to any `ExtensionBundle`.
*
* The configurations for HTML and EPUB are separate and either
* of the two can be empty.
*
* They can also point to the same instance in case the two formats
* should use the same script includes.
*/
def forJS(
htmlConfig: IncludeJSConfig,
epubConfig: IncludeJSConfig = IncludeJSConfig.empty
): TemplateDirectives.Directive =
IncludeDirectiveBuilder.includeJS(htmlConfig.includes, epubConfig.includes)

}
Loading

0 comments on commit 4cade29

Please sign in to comment.