Skip to content

Commit f9de565

Browse files
committed
Merge branch 'FIGBERT/follows' (zedeus/nitter#363)
2 parents ae185e7 + 1ebfa09 commit f9de565

File tree

9 files changed

+157
-13
lines changed

9 files changed

+157
-13
lines changed

src/nitter.nim

+4-5
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import jester
88
import types, config, prefs, formatters, redis_cache, http_pool, tokens
99
import views/[general, about]
1010
import routes/[
11-
preferences, timeline, status, media, search, rss, list,
12-
unsupported, embed, resolver, router_utils]
11+
home, preferences, timeline, status, media, search, rss, list,
12+
unsupported, embed, resolver, router_utils, follow]
1313

1414
const instancesUrl = "https://github.com/zedeus/nitter/wiki/Instances"
1515

@@ -54,9 +54,6 @@ settings:
5454
bindAddr = cfg.address
5555

5656
routes:
57-
get "/":
58-
resp renderMain(renderSearch(), request, cfg, themePrefs())
59-
6057
get "/about":
6158
resp renderMain(renderAbout(), request, cfg, themePrefs())
6259

@@ -80,6 +77,8 @@ routes:
8077
a("another instance", href = instancesUrl) &
8178
" or try again later.", cfg)
8279

80+
extend home, ""
81+
extend follow, ""
8382
extend unsupported, ""
8483
extend preferences, ""
8584
extend resolver, ""

src/prefs_impl.nim

+5
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ macro genPrefs*(prefDsl: untyped) =
4949
const `name`*: PrefList = toOrderedTable(`table`)
5050

5151
genPrefs:
52+
Timeline:
53+
following(input, ""):
54+
"A comma-separated list of users to follow."
55+
placeholder: "one,two,three"
56+
5257
Privacy:
5358
replaceTwitter(input, "nitter.net"):
5459
"Replace Twitter links with Nitter (blank to disable)"

src/routes/follow.nim

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import jester, asyncdispatch, strutils, sequtils
2+
import router_utils
3+
import ../types
4+
5+
export follow
6+
7+
proc addUserToFollowing*(following, toAdd: string): string =
8+
var updated = following.split(",")
9+
if updated == @[""]:
10+
return toAdd
11+
elif toAdd in updated:
12+
return following
13+
else:
14+
updated = concat(updated, @[toAdd])
15+
result = updated.join(",")
16+
17+
proc removeUserFromFollowing*(following, remove: string): string =
18+
var updated = following.split(",")
19+
if updated == @[""]:
20+
return ""
21+
else:
22+
updated = filter(updated, proc(x: string): bool = x != remove)
23+
result = updated.join(",")
24+
25+
proc createFollowRouter*(cfg: Config) =
26+
router follow:
27+
post "/follow/@name":
28+
let
29+
following = cookiePrefs().following
30+
toAdd = @"name"
31+
updated = addUserToFollowing(following, toAdd)
32+
setCookie("following", updated, daysForward(360),
33+
httpOnly=true, secure=cfg.useHttps, path="/")
34+
redirect(refPath())
35+
post "/unfollow/@name":
36+
let
37+
following = cookiePrefs().following
38+
remove = @"name"
39+
updated = removeUserFromFollowing(following, remove)
40+
setCookie("following", updated, daysForward(360),
41+
httpOnly=true, secure=cfg.useHttps, path="/")
42+
redirect(refPath())

src/routes/home.nim

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import jester
2+
import asyncdispatch, strutils, options, router_utils, timeline
3+
import ".."/[prefs, types, utils, redis_cache]
4+
import ../views/[general, home, search]
5+
6+
export home
7+
8+
proc showHome*(request: Request; query: Query; cfg: Config; prefs: Prefs;
9+
after: string): Future[string] {.async.} =
10+
let
11+
timeline = await getSearch[Tweet](query, after)
12+
html = renderHome(timeline, prefs, getPath())
13+
return renderMain(html, request, cfg, prefs)
14+
15+
proc createHomeRouter*(cfg: Config) =
16+
router home:
17+
get "/":
18+
let
19+
prefs = cookiePrefs()
20+
after = getCursor()
21+
names = getNames(prefs.following)
22+
23+
var query = request.getQuery("", prefs.following)
24+
query.fromUser = names
25+
26+
if @"scroll".len > 0:
27+
var timeline = await getSearch[Tweet](query, after)
28+
if timeline.content.len == 0: resp Http404
29+
timeline.beginning = true
30+
resp $renderHome(timeline, prefs, getPath())
31+
32+
if names.len == 0:
33+
resp renderMain(renderSearch(), request, cfg, themePrefs())
34+
resp (await showHome(request, query, cfg, prefs, after))
35+
get "/following":
36+
let
37+
prefs = cookiePrefs()
38+
names = getNames(prefs.following)
39+
var
40+
profs: seq[Profile]
41+
query = request.getQuery("", prefs.following)
42+
query.fromUser = names
43+
query.kind = userList
44+
45+
for name in names:
46+
let prof = await getCachedProfile(name)
47+
profs &= @[prof]
48+
49+
resp renderMain(renderFollowing(query, profs, prefs), request, cfg, prefs)

src/sass/profile/card.scss

+9-2
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@
1313
width: 100%;
1414
}
1515

16-
.profile-card-tabs-name {
16+
.profile-card-tabs-name-and-follow {
1717
@include breakable;
18-
max-width: 100%;
18+
width: 100%;
19+
display: flex;
20+
flex-wrap: wrap;
21+
justify-content: space-between;
1922
}
2023

2124
.profile-card-username {
@@ -34,6 +37,10 @@
3437
max-width: 100%;
3538
}
3639

40+
.profile-card-follow-button {
41+
float: none;
42+
}
43+
3744
.profile-card-avatar {
3845
display: block;
3946
width: 100%;

src/views/home.nim

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import karax/[karaxdsl, vdom]
2+
import search, timeline, renderutils
3+
import ../types
4+
5+
proc renderFollowingUsers*(results: seq[Profile]; prefs: Prefs): VNode =
6+
buildHtml(tdiv(class="timeline")):
7+
for user in results:
8+
renderUser(user, prefs)
9+
10+
proc renderHomeTabs*(query: Query): VNode =
11+
buildHtml(ul(class="tab")):
12+
li(class=query.getTabClass(posts)):
13+
a(href="/"): text "Tweets"
14+
li(class=query.getTabClass(userList)):
15+
a(href=("/following")): text "Following"
16+
17+
proc renderHome*(results: Result[Tweet]; prefs: Prefs; path: string): VNode =
18+
let query = results.query
19+
buildHtml(tdiv(class="timeline-container")):
20+
if query.fromUser.len > 0:
21+
renderHomeTabs(query)
22+
23+
if query.fromUser.len == 0 or query.kind == tweets:
24+
tdiv(class="timeline-header"):
25+
renderSearchPanel(query)
26+
27+
renderTimelineTweets(results, prefs, path)
28+
29+
proc renderFollowing*(query: Query; following: seq[Profile]; prefs: Prefs): VNode =
30+
buildHtml(tdiv(class="timeline-container")):
31+
renderHomeTabs(query)
32+
renderFollowingUsers(following, prefs)

src/views/profile.nim

+11-5
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ proc renderStat(num, class: string; text=""): VNode =
1111
span(class="profile-stat-num"):
1212
text if num.len == 0: "?" else: insertSep(num, ',')
1313

14-
proc renderProfileCard*(profile: Profile; prefs: Prefs): VNode =
14+
proc renderProfileCard*(profile: Profile; prefs: Prefs, path: string): VNode =
1515
buildHtml(tdiv(class="profile-card")):
1616
tdiv(class="profile-card-info"):
1717
let url = getPicUrl(profile.getUserPic())
@@ -21,9 +21,15 @@ proc renderProfileCard*(profile: Profile; prefs: Prefs): VNode =
2121
a(class="profile-card-avatar", href=url, target="_blank"):
2222
genImg(profile.getUserpic(size))
2323

24-
tdiv(class="profile-card-tabs-name"):
25-
linkUser(profile, class="profile-card-fullname")
26-
linkUser(profile, class="profile-card-username")
24+
tdiv(class="profile-card-tabs-name-and-follow"):
25+
tdiv():
26+
linkUser(profile, class="profile-card-fullname")
27+
linkUser(profile, class="profile-card-username")
28+
let following = isFollowing(profile.username, prefs.following)
29+
if not following:
30+
buttonReferer "/follow/" & profile.username, "Follow", path, "profile-card-follow-button"
31+
else:
32+
buttonReferer "/unfollow/" & profile.username, "Unfollow", path, "profile-card-follow-button"
2733

2834
tdiv(class="profile-card-extra"):
2935
if profile.bio.len > 0:
@@ -104,7 +110,7 @@ proc renderProfile*(profile: Profile; timeline: var Timeline;
104110

105111
let sticky = if prefs.stickyProfile: "sticky" else: "unset"
106112
tdiv(class="profile-tab", style={position: sticky}):
107-
renderProfileCard(profile, prefs)
113+
renderProfileCard(profile, prefs, path)
108114
if photoRail.len > 0:
109115
renderPhotoRail(profile, photoRail)
110116

src/views/renderutils.nim

+4
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,7 @@ proc getTabClass*(query: Query; tab: QueryKind): string =
9393
result = "tab-item"
9494
if query.kind == tab:
9595
result &= " active"
96+
97+
proc isFollowing*(name, following: string): bool =
98+
let following = following.split(",")
99+
return name in following

src/views/timeline.nim

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ proc threadFilter(tweets: openArray[Tweet]; threads: openArray[int64]; it: Tweet
5656
elif t.replyId == result[0].id:
5757
result.add t
5858

59-
proc renderUser(user: Profile; prefs: Prefs): VNode =
59+
proc renderUser*(user: Profile; prefs: Prefs): VNode =
6060
buildHtml(tdiv(class="timeline-item")):
6161
a(class="tweet-link", href=("/" & user.username))
6262
tdiv(class="tweet-body profile-result"):

0 commit comments

Comments
 (0)