tangled
alpha
login
or
join now
futur.blue
/
pegasus
57
fork
atom
objective categorical abstract machine language personal data server
57
fork
atom
overview
issues
2
pulls
pipelines
Nicer root page
futur.blue
2 months ago
604d88cd
118d429f
verified
This commit was signed with the committer's
known signature
.
futur.blue
SSH Key Fingerprint:
SHA256:QHGqHWNpqYyw9bt8KmPuJIyeZX9SZewBZ0PR1COtKQ0=
+100
-72
8 changed files
expand all
collapse all
unified
split
frontend
client
Router.mlx
src
components
HandleInput.mlx
Input.mlx
templates
Layout.mlx
OauthAuthorizePage.mlx
RootPage.mlx
SignupPage.mlx
pegasus
lib
api
root.ml
+2
-1
frontend/client/Router.mlx
···
13
type route = {path: string; template: (module Template)}
14
15
let routes =
16
-
[ {path= "/oauth/authorize"; template= (module OauthAuthorizePage)}
0
17
; {path= "/account/login"; template= (module LoginPage)}
18
; {path= "/account/signup"; template= (module SignupPage)}
19
; {path= "/account"; template= (module AccountPage)}
···
13
type route = {path: string; template: (module Template)}
14
15
let routes =
16
+
[ {path= "/"; template= (module RootPage)}
17
+
; {path= "/oauth/authorize"; template= (module OauthAuthorizePage)}
18
; {path= "/account/login"; template= (module LoginPage)}
19
; {path= "/account/signup"; template= (module SignupPage)}
20
; {path= "/account"; template= (module AccountPage)}
+29
-31
frontend/src/components/HandleInput.mlx
···
2
3
open React
4
5
-
type handle_status =
6
-
| Idle
7
-
| Checking
8
-
| Valid
9
-
| Invalid of string
10
11
let[@react.component] make ~name ?(label = "handle") ?(sr_only = false)
12
-
?(required = false) ?(showIndicator = true) ?(subdomainOnly = false) ?placeholder ?hostname ?value ?onChange () =
0
13
let internalValue, setInternalValue = useState (fun () -> "") in
14
let handleValue = Option.value value ~default:internalValue in
15
let handleStatus, setHandleStatus = useState (fun () -> Idle) in
···
20
setHandleStatus (fun _ -> Checking) ;
21
let fullHandle =
22
match hostname with
23
-
| Some host -> handle ^ "." ^ host
24
-
| None -> handle
0
0
25
in
26
let _ =
27
Fetch.fetch
28
-
("/account/signup/check-handle?handle="
29
^ Js.Global.encodeURIComponent handle )
30
|> Js.Promise.then_ (fun response ->
31
-
if Fetch.Response.ok response then
32
-
Fetch.Response.json response
33
else Js.Promise.reject (Js.Exn.raiseError "Request failed") )
34
|> Js.Promise.then_ (fun json ->
35
let valid =
···
42
|> Option.map Obj.magic
43
|> Option.value ~default:false
44
in
45
-
let error = Option.bind
46
-
(Js.Dict.get (Obj.magic json) "error"
47
-
|> Option.map Obj.magic)
48
-
(fun x ->
49
-
if Js.Nullable.isNullable (Obj.magic x) then None
50
-
else Some x )
0
51
in
52
-
( if valid && available then setHandleStatus (fun _ -> Valid)
53
-
else
54
-
setHandleStatus (fun _ ->
55
-
Invalid (Option.value error ~default:"Invalid handle")
56
-
) ) ;
57
Js.Promise.resolve () )
58
|> Js.Promise.catch (fun _ ->
59
setHandleStatus (fun _ ->
···
67
fun e ->
68
let inputValue = (Event.Form.target e)##value in
69
( match onChange with
70
-
| Some f -> f e
71
-
| None -> setInternalValue (fun _ -> inputValue) ) ;
0
0
72
( match checkTimeoutRef.current with
73
| Some id ->
74
Js.Global.clearTimeout id
···
84
let trailing =
85
match hostname with
86
| Some h ->
87
-
Some (
88
<span className="font-serif text-mist-80 whitespace-nowrap">
89
(string ("." ^ h))
90
</span>
91
-
)
92
-
| _ -> None
93
in
94
<div>
95
<Input
···
110
(string "Checking availability...")
111
</span>
112
| Valid ->
113
-
<span
114
-
className="inline-flex items-center text-mana-100 text-sm mt-1">
115
<CheckmarkIcon className="w-4 h-4 mr-1" />
116
(string "Handle is available")
117
</span>
118
| Invalid msg ->
119
<span
120
className="inline-flex items-center text-phoenix-100 text-sm mt-1">
121
-
<CircleAlertIcon className="w-4 h-4 mr-1" />
122
-
(string msg)
123
</span>
124
| Idle ->
125
null )
···
2
3
open React
4
5
+
type handle_status = Idle | Checking | Valid | Invalid of string
0
0
0
0
6
7
let[@react.component] make ~name ?(label = "handle") ?(sr_only = false)
8
+
?(required = false) ?(showIndicator = true) ?(subdomainOnly = false)
9
+
?placeholder ?hostname ?value ?onChange () =
10
let internalValue, setInternalValue = useState (fun () -> "") in
11
let handleValue = Option.value value ~default:internalValue in
12
let handleStatus, setHandleStatus = useState (fun () -> Idle) in
···
17
setHandleStatus (fun _ -> Checking) ;
18
let fullHandle =
19
match hostname with
20
+
| Some host ->
21
+
handle ^ "." ^ host
22
+
| None ->
23
+
handle
24
in
25
let _ =
26
Fetch.fetch
27
+
( "/account/signup/check-handle?handle="
28
^ Js.Global.encodeURIComponent handle )
29
|> Js.Promise.then_ (fun response ->
30
+
if Fetch.Response.ok response then Fetch.Response.json response
0
31
else Js.Promise.reject (Js.Exn.raiseError "Request failed") )
32
|> Js.Promise.then_ (fun json ->
33
let valid =
···
40
|> Option.map Obj.magic
41
|> Option.value ~default:false
42
in
43
+
let error =
44
+
Option.bind
45
+
( Js.Dict.get (Obj.magic json) "error"
46
+
|> Option.map Obj.magic )
47
+
(fun x ->
48
+
if Js.Nullable.isNullable (Obj.magic x) then None
49
+
else Some x )
50
in
51
+
if valid && available then setHandleStatus (fun _ -> Valid)
52
+
else
53
+
setHandleStatus (fun _ ->
54
+
Invalid (Option.value error ~default:"Invalid handle") ) ;
0
55
Js.Promise.resolve () )
56
|> Js.Promise.catch (fun _ ->
57
setHandleStatus (fun _ ->
···
65
fun e ->
66
let inputValue = (Event.Form.target e)##value in
67
( match onChange with
68
+
| Some f ->
69
+
f e
70
+
| None ->
71
+
setInternalValue (fun _ -> inputValue) ) ;
72
( match checkTimeoutRef.current with
73
| Some id ->
74
Js.Global.clearTimeout id
···
84
let trailing =
85
match hostname with
86
| Some h ->
87
+
Some
88
<span className="font-serif text-mist-80 whitespace-nowrap">
89
(string ("." ^ h))
90
</span>
91
+
| _ ->
92
+
None
93
in
94
<div>
95
<Input
···
110
(string "Checking availability...")
111
</span>
112
| Valid ->
113
+
<span className="inline-flex items-center text-mana-100 text-sm mt-1">
0
114
<CheckmarkIcon className="w-4 h-4 mr-1" />
115
(string "Handle is available")
116
</span>
117
| Invalid msg ->
118
<span
119
className="inline-flex items-center text-phoenix-100 text-sm mt-1">
120
+
<CircleAlertIcon className="w-4 h-4 mr-1" /> (string msg)
0
121
</span>
122
| Idle ->
123
null )
+3
-2
frontend/src/components/Input.mlx
···
4
let req_marker = " *"
5
6
let[@react.component] make ?id ~name ?(className = "") ?(type_ = "text") ?label
7
-
?(sr_only = false) ?value ?defaultValue ?placeholder ?autoComplete ?(required = false)
8
-
?(disabled = false) ?trailing ?(showIndicator = true) ?onChange () =
0
9
let id = Option.value id ~default:name in
10
let placeholder = if label <> None && sr_only then label else placeholder in
11
let input =
···
4
let req_marker = " *"
5
6
let[@react.component] make ?id ~name ?(className = "") ?(type_ = "text") ?label
7
+
?(sr_only = false) ?value ?defaultValue ?placeholder ?autoComplete
8
+
?(required = false) ?(disabled = false) ?trailing ?(showIndicator = true)
9
+
?onChange () =
10
let id = Option.value id ~default:name in
11
let placeholder = if label <> None && sr_only then label else placeholder in
12
let input =
+2
-1
frontend/src/templates/Layout.mlx
···
1
open React
2
3
-
let[@react.component] make ?(title = "Pegasus") ?(children = null) ?(favicon = "/public/favicon.ico") () =
0
4
<html lang="en">
5
<head>
6
<meta charSet="utf-8" />
···
1
open React
2
3
+
let[@react.component] make ?(title = "Pegasus") ?(children = null)
4
+
?(favicon = "/public/favicon.ico") () =
5
<html lang="en">
6
<head>
7
<meta charSet="utf-8" />
+2
-1
frontend/src/templates/OauthAuthorizePage.mlx
···
562
in
563
let add_account_url = "/account/login" ^ query_string in
564
let favicon_url, set_favicon_url =
565
-
useState (fun () -> (Option.value logo_uri ~default:("https://" ^ host ^ "/favicon.ico")) )
0
566
in
567
<form className="w-full h-auto max-w-lg px-4 sm:px-0">
568
<h1 className="text-2xl font-serif text-mana-200 mb-2">
···
562
in
563
let add_account_url = "/account/login" ^ query_string in
564
let favicon_url, set_favicon_url =
565
+
useState (fun () ->
566
+
Option.value logo_uri ~default:("https://" ^ host ^ "/favicon.ico") )
567
in
568
<form className="w-full h-auto max-w-lg px-4 sm:px-0">
569
<h1 className="text-2xl font-serif text-mana-200 mb-2">
+60
frontend/src/templates/RootPage.mlx
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
1
+
open Melange_json.Primitives
2
+
open React
3
+
4
+
let pegasus =
5
+
string
6
+
{js|
7
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⢀⣠⣴⡆⠀⠀⠀⠀⠀⠀
8
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⣤⣤⣶⣶⠿⠿⢛⡛⠉⠀⠀⠀⠀⠀⠀⠀
9
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣤⣤⣤⣴⣶⣿⢻⣿⣿⣶⣶⣢⣆⠀⠀⠀⣀⣀⣤⣤⣶⣶⣾⣿⡿⠿⣛⣛⣭⣵⣶⠾⢟⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀
10
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢿⣧⣾⣿⡟⣻⣿⣿⣿⣿⣿⣿⣭⣭⢠⣾⣿⣯⣭⣤⡬⣩⣭⣽⡶⠿⢟⣛⣯⣭⡶⣶⡛⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
11
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⠋⠉⠁⠿⣿⡿⣿⣿⣿⣿⣿⣯⡄⣿⣿⣿⣿⡟⠃⠈⠱⠶⠾⠛⢋⣭⣿⠶⣟⡛⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
12
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠞⣡⣾⣿⣿⣿⣿⣟⡓⢠⣾⣿⠿⣛⣛⡛⣟⣛⣛⡛⢶⣶⣶⣶⣶⣶⣥⣤⣤⣤⣤⣄⣀⣀⣀⣀⠀⠀⠀
13
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⡞⠁⣴⣿⣿⣿⣿⣿⡿⠯⢁⣿⢟⣴⣿⣿⡿⢳⣿⣿⣿⠏⠰⠶⠶⢾⣽⣭⣭⣭⣙⣛⣛⣛⣛⡛⠿⠿⠿⠿⠋
14
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⡟⢀⣾⣿⣿⣿⣿⣿⣿⣿⠋⣾⡟⣼⣿⣿⢟⣴⣿⡿⢏⣀⣙⣛⣛⣛⡳⠶⠶⠶⣶⣭⣭⣭⣭⣭⣝⠛⠋⠀⠀⠀
15
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⡿⠀⣼⣿⣿⣿⣿⣿⣿⠟⣂⣼⡟⣱⣿⣿⢏⣼⣿⣿⠁⠬⣭⣙⣛⣛⣛⣛⠛⠿⠿⠿⠶⠶⠶⠶⠆⠀⠀⠀⠀⠀⠀
16
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⠟⢁⣤⣾⣿⣿⣿⣷⣶⣶⣾⣟⣫⣾⣿⣿⡏⣾⣿⣿⡅⢶⣶⣦⣤⣤⣬⣭⣭⣉⠛⠛⠛⠒⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀
17
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠿⠟⠛⠱⣿⣛⣋⣤⣴⣶⣶⣦⣭⡉⠉⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
18
+
⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⣄⡀⠀⠀⢰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
19
+
⠀⠀⠀⠀⠀⠀⠀⠀⠻⠿⠿⣿⡿⠶⠜⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣇⢻⣿⠿⢿⣶⣶⣦⣤⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀
20
+
⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⣶⣶⣶⣿⣷⣶⣾⣿⣿⣿⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢸⣿⠀⠀⠉⢿⣿⣿⣿⣿⣿⣷⣦⣄⠀⠀⠀⠀
21
+
⠀⠀⠀⠀⢀⣼⠟⠉⠀⢸⣿⠛⠛⠛⠿⠿⢿⣿⠟⣠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣯⡙⢻⣿⣿⣿⣿⣿⣿⡎⣿⡇⠀⠀⠀⠙⠉⠻⣿⣿⣿⣿⣿⣷⡄⠀⠀
22
+
⠀⠀⠀⣠⣿⠏⠀⠀⠀⠘⣿⣧⡀⠀⠀⠀⠀⠀⠀⠈⠉⠙⠛⠻⠿⠿⠿⠛⠛⠛⠉⠉⠉⠁⠀⠀⠻⣿⣿⣿⣿⣿⣿⣿⣇⠀⠀⠀⠀⠀⠀⠀⠹⣿⣿⣿⣿⡷⠀⠀
23
+
⠀⢀⣾⡿⠁⠀⠀⠀⠀⠀⠈⠻⢿⣶⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⣿⣿⣿⣿⣿⣿⣼⣶⠀⠀⠀⠀⠀⠀⠛⠿⠿⠟⠁⠀⠀
24
+
⣰⣿⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠻⠿⣿⣿⣷⣭⣁⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
25
+
⠿⠥⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠻⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
26
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣿⣇⣤⡀⠀⠀⠀⠀⠀⠀⠀⠀
27
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣿⡿⢿⣶⠀⠀⠀⠀⠀⠀⠀
28
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠹⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀
29
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠿⣷⠄⠀⠀⠀⠀⠀⠀
30
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⠿⠆⠀⠀⠀⠀⠀⠀
31
+
|js}
32
+
33
+
type props = unit [@@deriving json]
34
+
35
+
let[@react.component] make ~props:(_ : props) () =
36
+
<main className="text-mist-100">
37
+
<pre className="text-mana-100">pegasus</pre>
38
+
<p>
39
+
(string "this is ")
40
+
<a
41
+
href="https://tangled.org/futur.blue/pegasus"
42
+
className="text-mana-100 underline hover:text-mana-200">
43
+
(string "pegasus")
44
+
</a>
45
+
(string ", an atproto personal data server")
46
+
</p>
47
+
<p>
48
+
(string "manage your account at ")
49
+
<a
50
+
href="/account" className="text-mana-100 underline hover:text-mana-200">
51
+
(string "/account")
52
+
</a>
53
+
</p>
54
+
<p>
55
+
(string "admin panel at ")
56
+
<a href="/admin" className="text-mana-100 underline hover:text-mana-200">
57
+
(string "/admin")
58
+
</a>
59
+
</p>
60
+
</main>
+1
-5
frontend/src/templates/SignupPage.mlx
···
23
<input type_="hidden" name="dream.csrf" value=csrf_token />
24
( if invite_required then
25
<Input
26
-
sr_only=true
27
-
name="invite_code"
28
-
type_="text"
29
-
label="invite code"
30
-
/>
31
else null )
32
<HandleInput sr_only=true name="handle" hostname />
33
<Input sr_only=true name="email" type_="email" label="email" />
···
23
<input type_="hidden" name="dream.csrf" value=csrf_token />
24
( if invite_required then
25
<Input
26
+
sr_only=true name="invite_code" type_="text" label="invite code" />
0
0
0
0
27
else null )
28
<HandleInput sr_only=true name="handle" hostname />
29
<Input sr_only=true name="email" type_="email" label="email" />
+1
-31
pegasus/lib/api/root.ml
···
1
let handler =
2
Xrpc.handler (fun _ ->
3
-
Dream.respond
4
-
~headers:[("Content-Type", "text/plain; charset=utf-8")]
5
-
{|
6
-
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⢀⣠⣴⡆⠀⠀⠀⠀⠀⠀
7
-
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⣤⣤⣶⣶⠿⠿⢛⡛⠉⠀⠀⠀⠀⠀⠀⠀
8
-
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣤⣤⣤⣴⣶⣿⢻⣿⣿⣶⣶⣢⣆⠀⠀⠀⣀⣀⣤⣤⣶⣶⣾⣿⡿⠿⣛⣛⣭⣵⣶⠾⢟⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀
9
-
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢿⣧⣾⣿⡟⣻⣿⣿⣿⣿⣿⣿⣭⣭⢠⣾⣿⣯⣭⣤⡬⣩⣭⣽⡶⠿⢟⣛⣯⣭⡶⣶⡛⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
10
-
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⠋⠉⠁⠿⣿⡿⣿⣿⣿⣿⣿⣯⡄⣿⣿⣿⣿⡟⠃⠈⠱⠶⠾⠛⢋⣭⣿⠶⣟⡛⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
11
-
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠞⣡⣾⣿⣿⣿⣿⣟⡓⢠⣾⣿⠿⣛⣛⡛⣟⣛⣛⡛⢶⣶⣶⣶⣶⣶⣥⣤⣤⣤⣤⣄⣀⣀⣀⣀⠀⠀⠀
12
-
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⡞⠁⣴⣿⣿⣿⣿⣿⡿⠯⢁⣿⢟⣴⣿⣿⡿⢳⣿⣿⣿⠏⠰⠶⠶⢾⣽⣭⣭⣭⣙⣛⣛⣛⣛⡛⠿⠿⠿⠿⠋
13
-
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⡟⢀⣾⣿⣿⣿⣿⣿⣿⣿⠋⣾⡟⣼⣿⣿⢟⣴⣿⡿⢏⣀⣙⣛⣛⣛⡳⠶⠶⠶⣶⣭⣭⣭⣭⣭⣝⠛⠋⠀⠀⠀
14
-
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⡿⠀⣼⣿⣿⣿⣿⣿⣿⠟⣂⣼⡟⣱⣿⣿⢏⣼⣿⣿⠁⠬⣭⣙⣛⣛⣛⣛⠛⠿⠿⠿⠶⠶⠶⠶⠆⠀⠀⠀⠀⠀⠀
15
-
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⠟⢁⣤⣾⣿⣿⣿⣷⣶⣶⣾⣟⣫⣾⣿⣿⡏⣾⣿⣿⡅⢶⣶⣦⣤⣤⣬⣭⣭⣉⠛⠛⠛⠒⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀
16
-
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠿⠟⠛⠱⣿⣛⣋⣤⣴⣶⣶⣦⣭⡉⠉⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
17
-
⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⣄⡀⠀⠀⢰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
18
-
⠀⠀⠀⠀⠀⠀⠀⠀⠻⠿⠿⣿⡿⠶⠜⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣇⢻⣿⠿⢿⣶⣶⣦⣤⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀
19
-
⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⣶⣶⣶⣿⣷⣶⣾⣿⣿⣿⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢸⣿⠀⠀⠉⢿⣿⣿⣿⣿⣿⣷⣦⣄⠀⠀⠀⠀
20
-
⠀⠀⠀⠀⢀⣼⠟⠉⠀⢸⣿⠛⠛⠛⠿⠿⢿⣿⠟⣠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣯⡙⢻⣿⣿⣿⣿⣿⣿⡎⣿⡇⠀⠀⠀⠙⠉⠻⣿⣿⣿⣿⣿⣷⡄⠀⠀
21
-
⠀⠀⠀⣠⣿⠏⠀⠀⠀⠘⣿⣧⡀⠀⠀⠀⠀⠀⠀⠈⠉⠙⠛⠻⠿⠿⠿⠛⠛⠛⠉⠉⠉⠁⠀⠀⠻⣿⣿⣿⣿⣿⣿⣿⣇⠀⠀⠀⠀⠀⠀⠀⠹⣿⣿⣿⣿⡷⠀⠀
22
-
⠀⢀⣾⡿⠁⠀⠀⠀⠀⠀⠈⠻⢿⣶⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⣿⣿⣿⣿⣿⣿⣼⣶⠀⠀⠀⠀⠀⠀⠛⠿⠿⠟⠁⠀⠀
23
-
⣰⣿⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠻⠿⣿⣿⣷⣭⣁⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
24
-
⠿⠥⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠻⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
25
-
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣿⣇⣤⡀⠀⠀⠀⠀⠀⠀⠀⠀
26
-
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣿⡿⢿⣶⠀⠀⠀⠀⠀⠀⠀
27
-
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠹⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀
28
-
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠿⣷⠄⠀⠀⠀⠀⠀⠀
29
-
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⠿⠆⠀⠀⠀⠀⠀⠀
30
-
31
-
this is [pegasus](https://tangled.org/@futur.blue/pegasus)
32
-
manage your account at /account
33
-
|} )
···
1
let handler =
2
Xrpc.handler (fun _ ->
3
+
Util.render_html ~title:"Pegasus" (module Frontend.RootPage) ~props:() )
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0