/
forum.nim
162 lines (150 loc) · 4.34 KB
/
forum.nim
1
2
3
4
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
import options, tables, sugar, httpcore
from dom import window, Location, document, decodeURI
include karax/prelude
import karax/[kdom]
import jester/[patterns]
import threadlist, postlist, header, profile, newthread, error, about
import categorylist
import resetpassword, activateemail, search
import karaxutils
type
State = ref object
originalTitle: cstring
url: Location
profile: ProfileState
newThread: NewThread
about: About
resetPassword: ResetPassword
activateEmail: ActivateEmail
search: Search
proc copyLocation(loc: Location): Location =
# TODO: It sucks that I had to do this. We need a nice way to deep copy in JS.
Location(
hash: loc.hash,
host: loc.host,
hostname: loc.hostname,
href: loc.href,
pathname: loc.pathname,
port: loc.port,
protocol: loc.protocol,
search: loc.search
)
proc newState(): State =
State(
originalTitle: document.title,
url: copyLocation(window.location),
profile: newProfileState(),
newThread: newNewThread(),
about: newAbout(),
resetPassword: newResetPassword(),
activateEmail: newActivateEmail(),
search: newSearch()
)
var state = newState()
proc onPopState(event: dom.Event) =
# This event is usually only called when the user moves back in their
# history. I fire it in karaxutils.anchorCB as well to ensure the URL is
# always updated. This should be moved into Karax in the future.
echo "New URL: ", window.location.href, " ", state.url.href
document.title = state.originalTitle
if state.url.href != window.location.href:
state = newState() # Reload the state to remove stale data.
state.url = copyLocation(window.location)
redraw()
type Params = Table[string, string]
type
Route = object
n: string
p: proc (params: Params): VNode
proc r(n: string, p: proc (params: Params): VNode): Route = Route(n: n, p: p)
proc route(routes: openarray[Route]): VNode =
let path =
if state.url.pathname.len == 0: "/" else: $state.url.pathname
let prefix = if appName == "/": "" else: appName
for route in routes:
let pattern = (prefix & route.n).parsePattern()
var (matched, params) = pattern.match(path)
parseUrlQuery($state.url.search, params)
if matched:
return route.p(params)
return renderError("Unmatched route: " & path, Http500)
proc render(): VNode =
result = buildHtml(tdiv()):
renderHeader()
route([
r("/categories",
(params: Params) =>
(renderCategoryList(getLoggedInUser()))
),
r("/c/@id",
(params: Params) =>
(renderThreadList(getLoggedInUser(), some(params["id"].parseInt)))
),
r("/newthread",
(params: Params) =>
(render(state.newThread, getLoggedInUser()))
),
r("/profile/@username",
(params: Params) =>
(
render(
state.profile,
decodeURI(params["username"]),
getLoggedInUser()
)
)
),
r("/t/@id",
(params: Params) =>
(
let postId = getInt(($state.url.hash).substr(1), 0);
renderPostList(
params["id"].parseInt(),
if postId == 0: none[int]() else: some[int](postId),
getLoggedInUser()
)
)
),
r("/about/?@page?",
(params: Params) => (render(state.about, params["page"]))
),
r("/activateEmail/success",
(params: Params) => (
renderMessage(
"Email activated",
"You can now create new posts!",
"fa-check"
)
)
),
r("/activateEmail",
(params: Params) => (
render(state.activateEmail)
)
),
r("/resetPassword/success",
(params: Params) => (
renderMessage(
"Password changed",
"You can now login using your new password!",
"fa-check"
)
)
),
r("/resetPassword",
(params: Params) => (
render(state.resetPassword)
)
),
r("/search",
(params: Params) => (
render(state.search, params["q"], getLoggedInUser())
)
),
r("/404",
(params: Params) => render404()
),
r("/", (params: Params) => renderThreadList(getLoggedInUser()))
])
window.onPopState = onPopState
setRenderer render