From b113f9898773285e5c032f7814f12353658b8dbf Mon Sep 17 00:00:00 2001 From: Lanius Trolling Date: Sun, 6 Oct 2024 16:49:21 -0400 Subject: [PATCH] joinToString refactoring --- src/jvmMain/kotlin/info/mechyrdia/Utils.kt | 5 +++ .../kotlin/info/mechyrdia/data/Comments.kt | 3 +- .../kotlin/info/mechyrdia/data/DataFiles.kt | 9 +++--- .../info/mechyrdia/data/ViewsComment.kt | 3 +- .../info/mechyrdia/lore/ArticleListing.kt | 3 +- .../info/mechyrdia/lore/ArticleTitles.kt | 7 +++-- .../kotlin/info/mechyrdia/lore/Fonts.kt | 3 +- .../info/mechyrdia/lore/ParserBuilder.kt | 5 +-- .../kotlin/info/mechyrdia/lore/ParserHtml.kt | 3 +- .../kotlin/info/mechyrdia/lore/ParserPlain.kt | 4 ++- .../info/mechyrdia/lore/ParserPreprocess.kt | 3 +- .../mechyrdia/lore/ParserPreprocessJson.kt | 3 +- .../kotlin/info/mechyrdia/lore/ParserRobot.kt | 31 +++++++++---------- .../kotlin/info/mechyrdia/lore/ViewsRss.kt | 6 ++-- .../info/mechyrdia/robot/RobotService.kt | 4 +-- .../info/mechyrdia/route/ResourceTypes.kt | 9 +++--- .../info/mechyrdia/route/ResourceWebDav.kt | 3 +- 17 files changed, 62 insertions(+), 42 deletions(-) create mode 100644 src/jvmMain/kotlin/info/mechyrdia/Utils.kt diff --git a/src/jvmMain/kotlin/info/mechyrdia/Utils.kt b/src/jvmMain/kotlin/info/mechyrdia/Utils.kt new file mode 100644 index 0000000..3bfd683 --- /dev/null +++ b/src/jvmMain/kotlin/info/mechyrdia/Utils.kt @@ -0,0 +1,5 @@ +package info.mechyrdia + +fun Iterable.concat(delimiter: String = "", prefix: String = "", suffix: String = "") = joinToString(separator = delimiter, prefix = prefix, postfix = suffix) + +fun Iterable.concat(delimiter: String = "", prefix: String = "", suffix: String = "", converter: (T) -> String = Any?::toString) = joinToString(separator = delimiter, prefix = prefix, postfix = suffix, transform = converter) diff --git a/src/jvmMain/kotlin/info/mechyrdia/data/Comments.kt b/src/jvmMain/kotlin/info/mechyrdia/data/Comments.kt index f16fa05..37262ab 100644 --- a/src/jvmMain/kotlin/info/mechyrdia/data/Comments.kt +++ b/src/jvmMain/kotlin/info/mechyrdia/data/Comments.kt @@ -5,6 +5,7 @@ import com.mongodb.client.model.Sorts import com.mongodb.client.model.UpdateOneModel import com.mongodb.client.model.UpdateOptions import com.mongodb.client.model.Updates +import info.mechyrdia.concat import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.toList import kotlinx.serialization.SerialName @@ -34,7 +35,7 @@ data class Comment( } suspend fun getCommentsIn(page: List): Flow { - return Table.select(Filters.eq(Comment::submittedIn.serialName, page.joinToString(separator = "/")), Sorts.descending(Comment::submittedAt.serialName)) + return Table.select(Filters.eq(Comment::submittedIn.serialName, page.concat("/")), Sorts.descending(Comment::submittedAt.serialName)) } suspend fun getCommentsBy(user: Id): Flow { diff --git a/src/jvmMain/kotlin/info/mechyrdia/data/DataFiles.kt b/src/jvmMain/kotlin/info/mechyrdia/data/DataFiles.kt index 8f2cf22..7e081d7 100644 --- a/src/jvmMain/kotlin/info/mechyrdia/data/DataFiles.kt +++ b/src/jvmMain/kotlin/info/mechyrdia/data/DataFiles.kt @@ -5,6 +5,7 @@ import com.mongodb.client.model.Updates import com.mongodb.reactivestreams.client.gridfs.GridFSBucket import info.mechyrdia.Configuration import info.mechyrdia.FileStorageConfig +import info.mechyrdia.concat import info.mechyrdia.lore.StoragePathAttributeKey import info.mechyrdia.lore.forEachSuspend import io.ktor.http.ContentType @@ -64,7 +65,7 @@ value class StoragePath(val elements: List) { init { for ((i, element) in elements.withIndex()) require(element.any { it != '.' }) { - "Cannot have elements . or .. in path, got $element at index $i in path /${elements.joinToString(separator = "/")}" + elements.concat("/", prefix = "Cannot have elements . or .. in path, got $element at index $i in path /") } } @@ -84,7 +85,7 @@ value class StoragePath(val elements: List) { }.all { it } override fun toString(): String { - return elements.joinToString(separator = "/") + return elements.concat("/") } companion object { @@ -293,8 +294,8 @@ private data class GridFsEntry( ) : DataDocument private class GridFsStorage(val table: DocumentTable, val bucket: GridFSBucket) : FileStorage { - private fun toExactPath(path: StoragePath) = path.elements.joinToString(separator = "") { "/$it" } - private fun toPrefixPath(path: StoragePath) = "${toExactPath(path)}/" + private fun toExactPath(path: StoragePath) = path.elements.concat("/", prefix = "/") + private fun toPrefixPath(path: StoragePath) = path.elements.concat("/", prefix = "/", suffix = "/") private suspend fun testExact(path: StoragePath) = table.number(Filters.eq(GridFsEntry::path.serialName, toExactPath(path))) > 0L private suspend fun getExact(path: StoragePath) = table.locate(Filters.eq(GridFsEntry::path.serialName, toExactPath(path))) diff --git a/src/jvmMain/kotlin/info/mechyrdia/data/ViewsComment.kt b/src/jvmMain/kotlin/info/mechyrdia/data/ViewsComment.kt index 6a3d650..a7040c1 100644 --- a/src/jvmMain/kotlin/info/mechyrdia/data/ViewsComment.kt +++ b/src/jvmMain/kotlin/info/mechyrdia/data/ViewsComment.kt @@ -3,6 +3,7 @@ package info.mechyrdia.data import com.mongodb.client.model.Sorts import info.mechyrdia.OwnerNationId import info.mechyrdia.auth.ForbiddenException +import info.mechyrdia.concat import info.mechyrdia.lore.ParserTree import info.mechyrdia.lore.PokhwalishAlphabetFont import info.mechyrdia.lore.TylanAlphabetFont @@ -83,7 +84,7 @@ suspend fun ApplicationCall.newCommentRoute(pagePathParts: List, content val now = Instant.now() val comment = Comment( submittedBy = loggedInAs.id, - submittedIn = pagePathParts.joinToString("/"), + submittedIn = pagePathParts.concat("/"), submittedAt = now, numEdits = 0, diff --git a/src/jvmMain/kotlin/info/mechyrdia/lore/ArticleListing.kt b/src/jvmMain/kotlin/info/mechyrdia/lore/ArticleListing.kt index aa49aee..efa8271 100644 --- a/src/jvmMain/kotlin/info/mechyrdia/lore/ArticleListing.kt +++ b/src/jvmMain/kotlin/info/mechyrdia/lore/ArticleListing.kt @@ -3,6 +3,7 @@ package info.mechyrdia.lore import info.mechyrdia.Configuration import info.mechyrdia.OwnerNationId import info.mechyrdia.auth.UserSession +import info.mechyrdia.concat import info.mechyrdia.data.FileStorage import info.mechyrdia.data.StoragePath import info.mechyrdia.route.Root @@ -67,5 +68,5 @@ suspend fun StoragePath.toFriendlyPathTitle(): String { return lorePath.indices.mapSuspend { index -> StoragePath(lorePath.take(index + 1)).toFriendlyPageTitle().title - }.joinToString(separator = " - ") + }.concat(" - ") } diff --git a/src/jvmMain/kotlin/info/mechyrdia/lore/ArticleTitles.kt b/src/jvmMain/kotlin/info/mechyrdia/lore/ArticleTitles.kt index c0133a4..63e3eef 100644 --- a/src/jvmMain/kotlin/info/mechyrdia/lore/ArticleTitles.kt +++ b/src/jvmMain/kotlin/info/mechyrdia/lore/ArticleTitles.kt @@ -1,5 +1,6 @@ package info.mechyrdia.lore +import info.mechyrdia.concat import info.mechyrdia.data.StoragePath data class ArticleTitle(val title: String, val css: String = "") @@ -7,9 +8,9 @@ data class ArticleTitle(val title: String, val css: String = "") object ArticleTitleCache : FileDependentCache() { private val StoragePath.defaultTitle: String get() = if (elements.size > 1) - elements.lastOrNull()?.split('-')?.joinToString(separator = " ") { word -> + elements.last().split('-').concat(" ") { word -> word.lowercase().replaceFirstChar { it.titlecase() } - }.orEmpty() + } else TOC_TITLE private val StoragePath.defaultCssProps: Map @@ -18,7 +19,7 @@ object ArticleTitleCache : FileDependentCache() { if (name.endsWith(".old")) "text-decoration" to "line-through" else null, ) - private fun Map.toStyleString() = map { (k, v) -> "$k:$v" }.joinToString(separator = ";") + private fun Map.toStyleString() = asIterable().concat(";") { (k, v) -> "$k:$v" } override fun default(path: StoragePath): ArticleTitle { return ArticleTitle(path.defaultTitle, path.defaultCssProps.toStyleString()) diff --git a/src/jvmMain/kotlin/info/mechyrdia/lore/Fonts.kt b/src/jvmMain/kotlin/info/mechyrdia/lore/Fonts.kt index 3d0125f..7d57a64 100644 --- a/src/jvmMain/kotlin/info/mechyrdia/lore/Fonts.kt +++ b/src/jvmMain/kotlin/info/mechyrdia/lore/Fonts.kt @@ -3,6 +3,7 @@ package info.mechyrdia.lore import com.jaredrummler.fontreader.truetype.FontFileReader import com.jaredrummler.fontreader.truetype.TTFFile import com.jaredrummler.fontreader.util.GlyphSequence +import info.mechyrdia.concat import info.mechyrdia.data.FileStorage import info.mechyrdia.data.StoragePath import info.mechyrdia.data.XmlTagConsumer @@ -80,7 +81,7 @@ fun > C.svg(svgDoc: SvgDoc) = declaration(standalone = svgDoc.viewBoxY, svgDoc.viewBoxW, svgDoc.viewBoxH, - ).joinToString(separator = " ") { it.xmlValue } + ).concat(" ") { it.xmlValue } ) ) { "path"(attributes = mapOf("d" to svgDoc.path.d, "fill-rule" to svgDoc.path.fillRule)) } diff --git a/src/jvmMain/kotlin/info/mechyrdia/lore/ParserBuilder.kt b/src/jvmMain/kotlin/info/mechyrdia/lore/ParserBuilder.kt index 70d8be5..2e7204a 100644 --- a/src/jvmMain/kotlin/info/mechyrdia/lore/ParserBuilder.kt +++ b/src/jvmMain/kotlin/info/mechyrdia/lore/ParserBuilder.kt @@ -1,6 +1,7 @@ package info.mechyrdia.lore import info.mechyrdia.MainDomainName +import info.mechyrdia.concat import info.mechyrdia.data.Comment import info.mechyrdia.data.Id @@ -49,8 +50,8 @@ class TableOfContentsBuilder { levels.add(addedLevel) } - val number = levels.joinToString(separator = ".") { it.toString() } - links.add(NavLink("#$toAnchor", "$number. $text", aClasses = "left")) + val number = levels.concat(".", suffix = ". $text") + links.add(NavLink("#$toAnchor", number, aClasses = "left")) } private var description: String? = null diff --git a/src/jvmMain/kotlin/info/mechyrdia/lore/ParserHtml.kt b/src/jvmMain/kotlin/info/mechyrdia/lore/ParserHtml.kt index 76ed140..fea9d1e 100644 --- a/src/jvmMain/kotlin/info/mechyrdia/lore/ParserHtml.kt +++ b/src/jvmMain/kotlin/info/mechyrdia/lore/ParserHtml.kt @@ -1,6 +1,7 @@ package info.mechyrdia.lore import info.mechyrdia.JsonStorageCodec +import info.mechyrdia.concat import kotlinx.html.* import kotlinx.html.org.w3c.dom.events.* import kotlinx.html.stream.* @@ -203,7 +204,7 @@ class HtmlMetadataLexerTag(val absorb: Boolean) : HtmlLexerTag { } } -fun ParserTree.treeToText(): String = joinToString(separator = "") { +fun ParserTree.treeToText(): String = concat { when (it) { is ParserTreeNode.Text -> it.text ParserTreeNode.LineBreak -> " " diff --git a/src/jvmMain/kotlin/info/mechyrdia/lore/ParserPlain.kt b/src/jvmMain/kotlin/info/mechyrdia/lore/ParserPlain.kt index 093a91d..b516e7c 100644 --- a/src/jvmMain/kotlin/info/mechyrdia/lore/ParserPlain.kt +++ b/src/jvmMain/kotlin/info/mechyrdia/lore/ParserPlain.kt @@ -1,5 +1,7 @@ package info.mechyrdia.lore +import info.mechyrdia.concat + typealias PlainTextBuilderContext = Unit typealias PlainTextBuilderSubject = String @@ -30,7 +32,7 @@ abstract class PlainTextFormattingProcessor : LexerTagFallback, subjects: List): PlainTextBuilderSubject { - return subjects.joinToString(separator = "") + return subjects.concat() } } diff --git a/src/jvmMain/kotlin/info/mechyrdia/lore/ParserPreprocess.kt b/src/jvmMain/kotlin/info/mechyrdia/lore/ParserPreprocess.kt index 338a5df..6f1e07d 100644 --- a/src/jvmMain/kotlin/info/mechyrdia/lore/ParserPreprocess.kt +++ b/src/jvmMain/kotlin/info/mechyrdia/lore/ParserPreprocess.kt @@ -1,6 +1,7 @@ package info.mechyrdia.lore import info.mechyrdia.JsonStorageCodec +import info.mechyrdia.concat import info.mechyrdia.data.StoragePath import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll @@ -47,7 +48,7 @@ class PreProcessorContext private constructor( fun defaults(lorePath: StoragePath) = defaults(lorePath.elements.drop(1)) fun defaults(lorePath: List) = mapOf( - PAGE_PATH_KEY to "/${lorePath.joinToString(separator = "/")}".textToTree(), + PAGE_PATH_KEY to lorePath.concat("/", prefix = "/").textToTree(), INSTANT_NOW_KEY to Instant.now().toEpochMilli().numberToTree(), ) } diff --git a/src/jvmMain/kotlin/info/mechyrdia/lore/ParserPreprocessJson.kt b/src/jvmMain/kotlin/info/mechyrdia/lore/ParserPreprocessJson.kt index a9e4d15..f19ca50 100644 --- a/src/jvmMain/kotlin/info/mechyrdia/lore/ParserPreprocessJson.kt +++ b/src/jvmMain/kotlin/info/mechyrdia/lore/ParserPreprocessJson.kt @@ -1,6 +1,7 @@ package info.mechyrdia.lore import info.mechyrdia.JsonStorageCodec +import info.mechyrdia.concat import info.mechyrdia.data.FileStorage import info.mechyrdia.data.StoragePath import kotlinx.serialization.json.JsonArray @@ -45,7 +46,7 @@ fun ParserTreeNode.unparse(): String = when (this) { } } -fun ParserTree.unparse() = joinToString(separator = "") { it.unparse() } +fun ParserTree.unparse() = concat { it.unparse() } fun ParserTree.toPreProcessJson(): JsonElement { val noBlanks = filterNot { it.isWhitespace() } diff --git a/src/jvmMain/kotlin/info/mechyrdia/lore/ParserRobot.kt b/src/jvmMain/kotlin/info/mechyrdia/lore/ParserRobot.kt index 74e2193..889ffac 100644 --- a/src/jvmMain/kotlin/info/mechyrdia/lore/ParserRobot.kt +++ b/src/jvmMain/kotlin/info/mechyrdia/lore/ParserRobot.kt @@ -1,5 +1,6 @@ package info.mechyrdia.lore +import info.mechyrdia.concat import info.mechyrdia.robot.toOpenAiName import java.time.Instant @@ -7,7 +8,7 @@ fun String.toRobotUrl(context: RobotTextContext): String { val filePath = if (startsWith("/")) this.removePrefix("/") else - context.siblingFile(this).joinToString(separator = "/") + context.siblingFile(this).concat("/") return filePath.toOpenAiName() } @@ -32,7 +33,7 @@ object RobotTextLexerProcessor : LexerTagFallback, subjects: List): RobotTextSubject { - return subjects.joinToString(separator = "") + return subjects.concat() } } @@ -90,20 +91,18 @@ enum class FactbookRobotFormattingTag(val type: RobotTextTag) { }), UL(RobotTextTag { env, _, subNodes -> - subNodes - .mapNotNull { subNode -> - if (subNode is ParserTreeNode.Tag && subNode isTag "li") - " * ${env.processTree(subNode.subNodes)}" - else null - }.joinToString(separator = "") + subNodes.concat { subNode -> + if (subNode is ParserTreeNode.Tag && subNode isTag "li") + " * ${env.processTree(subNode.subNodes)}" + else "" + } }), OL(RobotTextTag { env, _, subNodes -> - subNodes - .mapIndexedNotNull { i, subNode -> - if (subNode is ParserTreeNode.Tag && subNode isTag "li") - " ${i + 1}. ${env.processTree(subNode.subNodes)}" - else null - }.joinToString(separator = "") + subNodes.withIndex().concat { (i, subNode) -> + if (subNode is ParserTreeNode.Tag && subNode isTag "li") + " ${i + 1}. ${env.processTree(subNode.subNodes)}" + else "" + } }), TABLE(RobotTextTag { env, _, subNodes -> @@ -200,7 +199,7 @@ object RobotFactbookLoader { return allPages(null).mapSuspend { pathStat -> val lorePath = pathStat.path.elements.drop(1) FactbookLoader.loadFactbook(lorePath)?.toFactbookRobotText(lorePath)?.let { robotText -> - lorePath.joinToString(separator = "/") to robotText + lorePath.concat("/") to robotText } }.filterNotNull().toMap() } @@ -210,7 +209,7 @@ object RobotFactbookLoader { if (pathStat.stat.updated >= lastUpdated) { val lorePath = pathStat.path.elements.drop(1) FactbookLoader.loadFactbook(lorePath)?.toFactbookRobotText(lorePath)?.let { robotText -> - lorePath.joinToString(separator = "/") to robotText + lorePath.concat("/") to robotText } } else null }.filterNotNull().toMap() diff --git a/src/jvmMain/kotlin/info/mechyrdia/lore/ViewsRss.kt b/src/jvmMain/kotlin/info/mechyrdia/lore/ViewsRss.kt index eef186c..dad60f3 100644 --- a/src/jvmMain/kotlin/info/mechyrdia/lore/ViewsRss.kt +++ b/src/jvmMain/kotlin/info/mechyrdia/lore/ViewsRss.kt @@ -3,6 +3,7 @@ package info.mechyrdia.lore import com.mongodb.client.model.Sorts import info.mechyrdia.MainDomainName import info.mechyrdia.OwnerNationId +import info.mechyrdia.concat import info.mechyrdia.data.Comment import info.mechyrdia.data.CommentRenderData import info.mechyrdia.data.FileStorage @@ -104,12 +105,13 @@ suspend fun generateRecentPageEdits(call: ApplicationCall): RssChannel { ) } + val pageHref = pageLink.concat("/", prefix = "$MainDomainName/lore/") RssItem( title = pageToC.toPageTitle(), description = pageOg?.description, - link = "$MainDomainName/lore${pageLink.joinToString(separator = "") { "/$it" }}", + link = pageHref, author = null, - comments = "$MainDomainName/lore${pageLink.joinToString(separator = "") { "/$it" }}#comments", + comments = "$pageHref#comments", enclosure = imageEnclosure, pubDate = page.stat.updated, categories = mechyrdiaCategories, diff --git a/src/jvmMain/kotlin/info/mechyrdia/robot/RobotService.kt b/src/jvmMain/kotlin/info/mechyrdia/robot/RobotService.kt index 1c45ced..ee8882c 100644 --- a/src/jvmMain/kotlin/info/mechyrdia/robot/RobotService.kt +++ b/src/jvmMain/kotlin/info/mechyrdia/robot/RobotService.kt @@ -3,6 +3,7 @@ package info.mechyrdia.robot import info.mechyrdia.Configuration import info.mechyrdia.MainDomainName import info.mechyrdia.OpenAiConfig +import info.mechyrdia.concat import info.mechyrdia.data.DataDocument import info.mechyrdia.data.DocumentTable import info.mechyrdia.data.Id @@ -59,7 +60,6 @@ import kotlin.collections.flatMap import kotlin.collections.fold import kotlin.collections.forEach import kotlin.collections.iterator -import kotlin.collections.joinToString import kotlin.collections.listOf import kotlin.collections.map import kotlin.collections.minus @@ -381,7 +381,7 @@ class RobotService( annotation.text to " [${annotationIndex + 1}]" } - val contents = eventData.delta.content.joinToString(separator = "") { textContent -> + val contents = eventData.delta.content.concat { textContent -> textContent.text.value } diff --git a/src/jvmMain/kotlin/info/mechyrdia/route/ResourceTypes.kt b/src/jvmMain/kotlin/info/mechyrdia/route/ResourceTypes.kt index 7254010..e7831bd 100644 --- a/src/jvmMain/kotlin/info/mechyrdia/route/ResourceTypes.kt +++ b/src/jvmMain/kotlin/info/mechyrdia/route/ResourceTypes.kt @@ -5,6 +5,7 @@ import info.mechyrdia.auth.adminRequestWebDavToken import info.mechyrdia.auth.loginPage import info.mechyrdia.auth.loginRoute import info.mechyrdia.auth.logoutRoute +import info.mechyrdia.concat import info.mechyrdia.data.Comment import info.mechyrdia.data.Id import info.mechyrdia.data.NationData @@ -551,7 +552,7 @@ class Root : ResourceHandler, ResourceFilter { override suspend fun PipelineContext.handleCall(payload: MechyrdiaSansPayload) { with(utils) { call.filterCall() } - val svgDoc = MechyrdiaSansFont.renderTextToSvg(payload.lines.joinToString(separator = "\n") { it.trim() }, payload.bold, payload.italic, payload.align) + val svgDoc = MechyrdiaSansFont.renderTextToSvg(payload.lines.concat("\n") { it.trim() }, payload.bold, payload.italic, payload.align) call.respondXml(contentType = ContentType.Image.SVG) { svg(svgDoc) } @@ -563,7 +564,7 @@ class Root : ResourceHandler, ResourceFilter { override suspend fun PipelineContext.handleCall(payload: TylanLanguagePayload) { with(utils) { call.filterCall() } - call.respondText(TylanAlphabetFont.tylanToFontAlphabet(payload.lines.joinToString(separator = "\n"))) + call.respondText(TylanAlphabetFont.tylanToFontAlphabet(payload.lines.concat("\n"))) } } @@ -572,7 +573,7 @@ class Root : ResourceHandler, ResourceFilter { override suspend fun PipelineContext.handleCall(payload: PokhwalishLanguagePayload) { with(utils) { call.filterCall() } - call.respondText(PokhwalishAlphabetFont.pokhwalToFontAlphabet(payload.lines.joinToString(separator = "\n"))) + call.respondText(PokhwalishAlphabetFont.pokhwalToFontAlphabet(payload.lines.concat("\n"))) } } @@ -582,7 +583,7 @@ class Root : ResourceHandler, ResourceFilter { with(utils) { call.filterCall() } call.respondText( - text = payload.lines.joinToString(separator = "\n").parseAs(ParserTree::toCommentHtml).toFragmentString(), + text = payload.lines.concat("\n").parseAs(ParserTree::toCommentHtml).toFragmentString(), contentType = ContentType.Text.Html ) } diff --git a/src/jvmMain/kotlin/info/mechyrdia/route/ResourceWebDav.kt b/src/jvmMain/kotlin/info/mechyrdia/route/ResourceWebDav.kt index f2a310d..0b63763 100644 --- a/src/jvmMain/kotlin/info/mechyrdia/route/ResourceWebDav.kt +++ b/src/jvmMain/kotlin/info/mechyrdia/route/ResourceWebDav.kt @@ -2,6 +2,7 @@ package info.mechyrdia.route import info.mechyrdia.auth.WebDavToken import info.mechyrdia.auth.toNationId +import info.mechyrdia.concat import info.mechyrdia.data.FileStorage import info.mechyrdia.data.Id import info.mechyrdia.data.StoragePath @@ -133,7 +134,7 @@ private suspend fun getWebDavPropertiesWithIncludeTags(path: StoragePath, webRoo .filterNotNull() .flatten() - val pathWithSuffix = path.elements.joinToString(separator = "") { "$it/" } + val pathWithSuffix = path.elements.concat("/", suffix = "/") listOf( WebDavProperties.Collection( creationDate = subProps.mapNotNull { it.first.creationDate }.maxOrNull(), -- 2.25.1