Skip to content

Commit 65e93fa

Browse files
authored
Add relative routing path support (#166)
Co-authored-by: hfhbd <[email protected]>
1 parent 0acf1e7 commit 65e93fa

File tree

10 files changed

+50
-13
lines changed

10 files changed

+50
-13
lines changed

integrationTest/src/jsMain/kotlin/demo.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ fun RouteBuilder.Users() {
7171
NavLink(to = "/") {
7272
Text("Go back to main page")
7373
}
74+
Br()
75+
NavLink(to = "./answer") {
76+
Text("Relative route to ./answer")
77+
}
7478
}
7579
}
7680

src/commonMain/kotlin/app/softwork/routingcompose/Path.kt

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,24 @@
11
package app.softwork.routingcompose
22

3-
internal data class Path(val path: String, val parameters: Parameters?) {
4-
fun newPath(currentPath: String) = Path(path = path.removePrefix("/$currentPath"), parameters)
3+
public data class Path(val path: String, val parameters: Parameters?) {
4+
internal fun newPath(currentPath: String) = Path(path = path.removePrefix("/$currentPath"), parameters)
5+
6+
/**
7+
* https://datatracker.ietf.org/doc/html/rfc1808
8+
*/
9+
internal fun relative(to: String): Path {
10+
val paths = path.split("/")
11+
val split = to.split("./")
12+
val result = split.last().let {
13+
if (it.isNotEmpty()) "/$it" else it
14+
}
15+
val number = split.count() - 1
16+
return from(
17+
paths.dropLast(number).joinToString(postfix = result, separator = "/") {
18+
it
19+
}
20+
)
21+
}
522

623
internal companion object {
724
fun from(rawPath: String): Path {
@@ -10,9 +27,11 @@ internal data class Path(val path: String, val parameters: Parameters?) {
1027
1 -> {
1128
pathAndQuery.first() to null
1229
}
30+
1331
2 -> {
1432
pathAndQuery.first() to pathAndQuery.last().let { Parameters.from(it) }
1533
}
34+
1635
else -> {
1736
error("path contains more than 1 '?' delimiter: $rawPath")
1837
}
@@ -29,5 +48,5 @@ internal data class Path(val path: String, val parameters: Parameters?) {
2948
"$path?$parameters"
3049
}
3150

32-
val currentPath get() = path.removePrefix("/").takeWhile { it != '/' }
51+
internal val currentPath get() = path.removePrefix("/").takeWhile { it != '/' }
3352
}

src/commonMain/kotlin/app/softwork/routingcompose/RouteBuilder.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,10 @@ private class DelegatingRouter(val basePath: String, val router: Router) : Route
162162
basePath == "/" -> {
163163
router.navigate("/$to", hide)
164164
}
165+
to.startsWith(".") -> {
166+
val newPath = router.currentPath.relative(to)
167+
router.navigate(newPath.path)
168+
}
165169
else -> {
166170
router.navigate("$basePath/$to", hide)
167171
}

src/commonMain/kotlin/app/softwork/routingcompose/Router.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ public interface Router {
77
/**
88
* The current path
99
*/
10-
public val currentPath: String
10+
public val currentPath: Path
1111

1212
public fun navigate(to: String, hide: Boolean = false)
1313

src/commonTest/kotlin/app/softwork/routingcompose/MockRouter.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ package app.softwork.routingcompose
33
import androidx.compose.runtime.*
44

55
class MockRouter : Router {
6-
override val currentPath: String
7-
get() = currentState.value!!
6+
override val currentPath: Path
7+
get() = Path.from(currentState.value!!)
88

99
private val currentState = mutableStateOf<String?>(null)
1010

src/commonTest/kotlin/app/softwork/routingcompose/PathTest.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,14 @@ class PathTest {
99
assertEquals(Path("/a", null), Path.from(prefixPath))
1010
assertEquals(Path("/a", null), Path.from("a"))
1111
}
12+
13+
@Test
14+
fun relative() {
15+
val base = Path.from("/a/b/c/d")
16+
assertEquals("/a/b/c", base.relative("./").path)
17+
assertEquals("/a/b", base.relative("././").path)
18+
assertEquals("/a/b/g", base.relative("././g").path)
19+
20+
assertEquals("/a/b/g?foo=bar", base.relative("././g?foo=bar").toString())
21+
}
1222
}

src/jsMain/kotlin/app/softwork/routingcompose/BrowserRouter.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ public fun BrowserRouter(
3030
}
3131

3232
public class BrowserRouter : Router {
33-
override val currentPath: String
34-
get() = currentLocation.value
33+
override val currentPath: Path
34+
get() = Path.from(currentLocation.value)
3535

3636
private val currentLocation: MutableState<String> = mutableStateOf(window.location.newPath())
3737

src/jsMain/kotlin/app/softwork/routingcompose/HashRouter.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ public fun HashRouter(
1818
}
1919

2020
public class HashRouter : Router {
21-
override val currentPath: String
22-
get() = currentHash.value
21+
override val currentPath: Path
22+
get() = Path.from(currentHash.value)
2323

2424
private val currentHash: MutableState<String> = mutableStateOf(window.location.hash.currentURL(null))
2525

src/jsMain/kotlin/app/softwork/routingcompose/NavLink.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public fun NavLink(
2020
A(
2121
attrs = {
2222
attrs?.invoke(this)
23-
if (router.currentPath.startsWith(to.removePrefix("/"))) {
23+
if (router.currentPath.path.startsWith(to.removePrefix("/"))) {
2424
classes("active")
2525
}
2626
onClick {

src/jvmMain/kotlin/app/softwork/routingcompose/DesktopRouter.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ package app.softwork.routingcompose
33
import androidx.compose.runtime.*
44

55
public class DesktopRouter private constructor() : Router {
6-
override val currentPath: String
7-
get() = stack.last().path
6+
override val currentPath: Path
7+
get() = Path.from(stack.last().path)
88

99
private data class Entry(val path: String, val hide: Boolean)
1010
private val stack = mutableStateListOf<Entry>()

0 commit comments

Comments
 (0)