Skip to content

Commit

Permalink
Merge pull request #189 from jp7677/search
Browse files Browse the repository at this point in the history
Add search functionality
  • Loading branch information
dirkgroot authored Apr 18, 2023
2 parents ab33853 + 4ccf3d8 commit 92766a4
Show file tree
Hide file tree
Showing 25 changed files with 986 additions and 1 deletion.
2 changes: 2 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
plugins {
kotlin("jvm") version "1.8.20"
kotlin("plugin.serialization") version "1.8.20"
application
}

Expand Down Expand Up @@ -29,6 +30,7 @@ dependencies {
implementation("org.jsoup:jsoup:1.15.4")

implementation("org.jetbrains.kotlinx:kotlinx-html-jvm:0.8.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")

implementation("org.eclipse.jetty:jetty-server:11.0.15")
implementation("org.eclipse.jetty:jetty-servlet:11.0.15")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import java.security.MessageDigest

fun copySiteWideAssets(exportDir: File) {
copySiteWideAsset(exportDir, "/css/style.css")
copySiteWideAsset(exportDir, "/js/header.js")
copySiteWideAsset(exportDir, "/js/search.js")
copySiteWideAsset(exportDir, "/js/auto-reload.js")
}

Expand Down Expand Up @@ -89,6 +91,14 @@ private fun generateStyle(context: GeneratorContext, exportDir: File) {
color: $secondary!important;
background-color: $primary!important;
}
.input.has-site-branding {
color: dimgrey!important;
background-color: white!important;
}
.input.has-site-branding:focus {
border-color: $secondary!important;
box-shadow: 0 0 0 0.125em $secondary;
}
""".trimIndent()

file.writeText(content)
Expand All @@ -100,6 +110,7 @@ private fun generateHtmlFiles(context: GeneratorContext, exportDir: File) {
add { writeHtmlFile(branchDir, HomePageViewModel(context)) }
add { writeHtmlFile(branchDir, WorkspaceDecisionsPageViewModel(context)) }
add { writeHtmlFile(branchDir, SoftwareSystemsPageViewModel(context)) }
add { writeHtmlFile(branchDir, SearchViewModel(context)) }

context.workspace.documentation.sections
.filter { it.order != 1 }
Expand Down Expand Up @@ -140,6 +151,7 @@ private fun writeHtmlFile(exportDir: File, viewModel: PageViewModel) {
appendHTML().html {
when (viewModel) {
is HomePageViewModel -> homePage(viewModel)
is SearchViewModel -> searchPage(viewModel)
is SoftwareSystemsPageViewModel -> softwareSystemsPage(viewModel)
is SoftwareSystemHomePageViewModel -> softwareSystemHomePage(viewModel)
is SoftwareSystemContextPageViewModel -> softwareSystemContextPage(viewModel)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package nl.avisi.structurizr.site.generatr.site.model

import com.structurizr.documentation.Decision
import com.structurizr.documentation.Format
import com.structurizr.documentation.Section
import com.vladsch.flexmark.ast.Heading
import com.vladsch.flexmark.ast.Paragraph
import com.vladsch.flexmark.parser.Parser

private val parser = Parser.builder().build()

fun Decision.contentText(): String {
if (format != Format.Markdown)
return ""

return extractText(content)
}

fun Section.contentText(): String {
if (format != Format.Markdown)
return ""

return extractText(content)
}

private fun extractText(content: String): String {
val document = parser.parse(content)
if (!document.hasChildren())
return ""

return document
.children
.filterIsInstance<Heading>()
.drop(1) // ignore title
.joinToString(" ") { it.text.toString() }
.plus(" ")
.plus(
document
.children
.filterIsInstance<Paragraph>()
.joinToString(" ") { it.chars.toString().trim() }
)
.trim()
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ package nl.avisi.structurizr.site.generatr.site.model
import nl.avisi.structurizr.site.generatr.site.GeneratorContext

class HeaderBarViewModel(pageViewModel: PageViewModel, generatorContext: GeneratorContext) {
val url = pageViewModel.url
val logo = logoPath(generatorContext)?.let { ImageViewModel(pageViewModel, "/$it") }
val hasLogo = logo != null
val titleLink = LinkViewModel(pageViewModel, generatorContext.workspace.name, HomePageViewModel.url())
val searchLink = LinkViewModel(pageViewModel, generatorContext.workspace.name, SearchViewModel.url())
val branches = generatorContext.branches
.map { BranchHomeLinkViewModel(pageViewModel, it) }
val currentBranch = generatorContext.currentBranch
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package nl.avisi.structurizr.site.generatr.site.model

import nl.avisi.structurizr.site.generatr.includedSoftwareSystem
import nl.avisi.structurizr.site.generatr.site.GeneratorContext
import nl.avisi.structurizr.site.generatr.site.model.indexing.home
import nl.avisi.structurizr.site.generatr.site.model.indexing.softwareSystemComponents
import nl.avisi.structurizr.site.generatr.site.model.indexing.softwareSystemContainers
import nl.avisi.structurizr.site.generatr.site.model.indexing.softwareSystemContext
import nl.avisi.structurizr.site.generatr.site.model.indexing.softwareSystemDecisions
import nl.avisi.structurizr.site.generatr.site.model.indexing.softwareSystemHome
import nl.avisi.structurizr.site.generatr.site.model.indexing.softwareSystemRelationships
import nl.avisi.structurizr.site.generatr.site.model.indexing.softwareSystemSections
import nl.avisi.structurizr.site.generatr.site.model.indexing.workspaceDecisions
import nl.avisi.structurizr.site.generatr.site.model.indexing.workspaceSections

class SearchViewModel(generatorContext: GeneratorContext) : PageViewModel(generatorContext) {
override val pageSubTitle = "Search results"
override val url = url()

val language: String = generatorContext.workspace.views
.configuration.properties.getOrDefault("structurizr.style.search.language", "")

val documents = buildList {
add(home(generatorContext.workspace.documentation, this@SearchViewModel))
addAll(workspaceDecisions(generatorContext.workspace.documentation, this@SearchViewModel))
addAll(workspaceSections(generatorContext.workspace.documentation, this@SearchViewModel))
addAll(
generatorContext.workspace.model.softwareSystems
.filter { it.includedSoftwareSystem }
.flatMap {
buildList {
add(softwareSystemHome(it, this@SearchViewModel))
add(softwareSystemContext(it, this@SearchViewModel))
add(softwareSystemContainers(it, this@SearchViewModel))
add(softwareSystemComponents(it, this@SearchViewModel))
add(softwareSystemRelationships(it, this@SearchViewModel))
addAll(softwareSystemDecisions(it, this@SearchViewModel))
addAll(softwareSystemSections(it, this@SearchViewModel))
}
}
.mapNotNull { it },
)
}.mapNotNull { it }

companion object {
fun url() = "/search"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package nl.avisi.structurizr.site.generatr.site.model.indexing

import kotlinx.serialization.Serializable

@Serializable
data class Document(val href: String, val type: String, val title: String, val text: String)
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package nl.avisi.structurizr.site.generatr.site.model.indexing

import com.structurizr.documentation.Documentation
import nl.avisi.structurizr.site.generatr.site.asUrlToDirectory
import nl.avisi.structurizr.site.generatr.site.model.HomePageViewModel
import nl.avisi.structurizr.site.generatr.site.model.PageViewModel
import nl.avisi.structurizr.site.generatr.site.model.contentText
import nl.avisi.structurizr.site.generatr.site.model.contentTitle

fun home(documentation: Documentation, viewModel: PageViewModel) = documentation.sections.firstOrNull()
?.let { section ->
Document(
HomePageViewModel.url().asUrlToDirectory(viewModel.url),
"Home",
section.contentTitle(),
"${section.contentTitle()} ${section.contentText()}".trim()
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package nl.avisi.structurizr.site.generatr.site.model.indexing

import com.structurizr.model.SoftwareSystem
import nl.avisi.structurizr.site.generatr.site.asUrlToDirectory
import nl.avisi.structurizr.site.generatr.site.model.PageViewModel
import nl.avisi.structurizr.site.generatr.site.model.SoftwareSystemPageViewModel

fun softwareSystemComponents(softwareSystem: SoftwareSystem, viewModel: PageViewModel) = softwareSystem.containers
.sortedBy { it.name }
.flatMap { container ->
container.components
.sortedBy { it.name }
.flatMap { component ->
listOf(component.name, component.description, component.technology)
}
}
.filter { it != null && it.isNotBlank() }
.joinToString(" ")
.ifBlank { null }
?.let {
Document(
SoftwareSystemPageViewModel.url(softwareSystem, SoftwareSystemPageViewModel.Tab.COMPONENT)
.asUrlToDirectory(viewModel.url),
"Component views",
"${softwareSystem.name} | Component views",
it
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package nl.avisi.structurizr.site.generatr.site.model.indexing

import com.structurizr.model.SoftwareSystem
import nl.avisi.structurizr.site.generatr.site.asUrlToDirectory
import nl.avisi.structurizr.site.generatr.site.model.PageViewModel
import nl.avisi.structurizr.site.generatr.site.model.SoftwareSystemPageViewModel

fun softwareSystemContainers(softwareSystem: SoftwareSystem, viewModel: PageViewModel) = softwareSystem.containers
.sortedBy { it.name }
.flatMap { container ->
listOf(container.name, container.description, container.technology)
}
.filter { it != null && it.isNotBlank() }
.joinToString(" ")
.ifBlank { null }
?.let {
Document(
SoftwareSystemPageViewModel.url(softwareSystem, SoftwareSystemPageViewModel.Tab.CONTAINER)
.asUrlToDirectory(viewModel.url),
"Container views",
"${softwareSystem.name} | Container views",
it
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package nl.avisi.structurizr.site.generatr.site.model.indexing

import com.structurizr.model.SoftwareSystem
import nl.avisi.structurizr.site.generatr.site.asUrlToDirectory
import nl.avisi.structurizr.site.generatr.site.model.PageViewModel
import nl.avisi.structurizr.site.generatr.site.model.SoftwareSystemPageViewModel

fun softwareSystemContext(softwareSystem: SoftwareSystem, viewModel: PageViewModel) = Document(
SoftwareSystemPageViewModel.url(softwareSystem, SoftwareSystemPageViewModel.Tab.SYSTEM_CONTEXT)
.asUrlToDirectory(viewModel.url),
"Context views",
"${softwareSystem.name} | Context views",
"${softwareSystem.name} ${softwareSystem.description}".trim()
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package nl.avisi.structurizr.site.generatr.site.model.indexing

import com.structurizr.model.SoftwareSystem
import nl.avisi.structurizr.site.generatr.site.asUrlToDirectory
import nl.avisi.structurizr.site.generatr.site.model.PageViewModel
import nl.avisi.structurizr.site.generatr.site.model.SoftwareSystemPageViewModel
import nl.avisi.structurizr.site.generatr.site.model.contentText

fun softwareSystemDecisions(softwareSystem: SoftwareSystem, viewModel: PageViewModel) = softwareSystem.documentation
.decisions
.map { decision ->
Document(
"${
SoftwareSystemPageViewModel.url(
softwareSystem,
SoftwareSystemPageViewModel.Tab.HOME
)
}/decisions/${decision.id}".asUrlToDirectory(viewModel.url),
"Decision",
"${softwareSystem.name} | ${decision.title}",
"${decision.title} ${decision.contentText()}".trim()
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package nl.avisi.structurizr.site.generatr.site.model.indexing

import com.structurizr.model.SoftwareSystem
import nl.avisi.structurizr.site.generatr.site.asUrlToDirectory
import nl.avisi.structurizr.site.generatr.site.model.PageViewModel
import nl.avisi.structurizr.site.generatr.site.model.SoftwareSystemPageViewModel
import nl.avisi.structurizr.site.generatr.site.model.contentText
import nl.avisi.structurizr.site.generatr.site.model.contentTitle

fun softwareSystemHome(softwareSystem: SoftwareSystem, viewModel: PageViewModel): Document? {
val (contentTitle, contentText) = softwareSystem.section()
val propertyValues = softwareSystem.propertyValues()

if (contentTitle.isBlank() && contentText.isBlank() && propertyValues.isBlank())
return null

return Document(
SoftwareSystemPageViewModel.url(softwareSystem, SoftwareSystemPageViewModel.Tab.HOME)
.asUrlToDirectory(viewModel.url),
"Software System Info",
"${softwareSystem.name} | ${contentTitle.ifEmpty { "Info" }}",
"$contentTitle $contentText $propertyValues".trim(),
)
}

private fun SoftwareSystem.section() = documentation.sections.firstOrNull()?.let {
it.contentTitle() to it.contentText()
} ?: ("" to "")

private fun SoftwareSystem.propertyValues() = properties.values.joinToString(" ")
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package nl.avisi.structurizr.site.generatr.site.model.indexing

import com.structurizr.model.SoftwareSystem
import nl.avisi.structurizr.site.generatr.site.asUrlToDirectory
import nl.avisi.structurizr.site.generatr.site.model.PageViewModel
import nl.avisi.structurizr.site.generatr.site.model.SoftwareSystemPageViewModel

fun softwareSystemRelationships(softwareSystem: SoftwareSystem, viewModel: PageViewModel) = softwareSystem.relationships
.filter { r -> r.source == softwareSystem }
.sortedBy { it.destination.name }
.flatMap { relationship ->
listOf(relationship.destination.name, relationship.description, relationship.technology)
}
.plus(
softwareSystem.model.softwareSystems
.sortedBy { it.name }
.filterNot { system -> system == softwareSystem }
.flatMap { other ->
other.relationships
.filter { r -> r.destination == softwareSystem }
.sortedBy { it.source.name }
.flatMap { relationship ->
listOf(relationship.source.name, relationship.description, relationship.technology)
}
}
)
.filter { it != null && it.isNotBlank() }
.joinToString(" ")
.ifBlank { null }
?.let {
Document(
SoftwareSystemPageViewModel.url(softwareSystem, SoftwareSystemPageViewModel.Tab.DEPENDENCIES)
.asUrlToDirectory(viewModel.url),
"Dependencies",
"${softwareSystem.name} | Dependencies",
it
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package nl.avisi.structurizr.site.generatr.site.model.indexing

import com.structurizr.model.SoftwareSystem
import nl.avisi.structurizr.site.generatr.site.asUrlToDirectory
import nl.avisi.structurizr.site.generatr.site.model.PageViewModel
import nl.avisi.structurizr.site.generatr.site.model.SoftwareSystemPageViewModel
import nl.avisi.structurizr.site.generatr.site.model.contentText
import nl.avisi.structurizr.site.generatr.site.model.contentTitle

fun softwareSystemSections(softwareSystem: SoftwareSystem, viewModel: PageViewModel) = softwareSystem.documentation
.sections
.drop(1) // Drop software system home
.map { section ->
Document(
"${
SoftwareSystemPageViewModel.url(
softwareSystem,
SoftwareSystemPageViewModel.Tab.HOME
)
}/sections/${section.order}".asUrlToDirectory(viewModel.url),
"Documentation",
"${softwareSystem.name} | ${section.contentTitle()}",
"${section.contentTitle()} ${section.contentText()}".trim()
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package nl.avisi.structurizr.site.generatr.site.model.indexing

import com.structurizr.documentation.Documentation
import nl.avisi.structurizr.site.generatr.site.asUrlToDirectory
import nl.avisi.structurizr.site.generatr.site.model.PageViewModel
import nl.avisi.structurizr.site.generatr.site.model.contentText

fun workspaceDecisions(documentation: Documentation, viewModel: PageViewModel) = documentation.decisions
.map { decision ->
Document(
"/decisions/${decision.id}".asUrlToDirectory(viewModel.url),
"Workspace Decision",
decision.title,
"${decision.title} ${decision.contentText()}".trim()
)
}
Loading

0 comments on commit 92766a4

Please sign in to comment.