This commit is contained in:
FIGBERT 2022-11-19 14:20:17 +08:00 committed by GitHub
commit cebe827e58
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 157 additions and 13 deletions

View File

@ -9,8 +9,8 @@ import jester
import types, config, prefs, formatters, redis_cache, http_pool, tokens
import views/[general, about]
import routes/[
preferences, timeline, status, media, search, rss, list, debug,
unsupported, embed, resolver, router_utils]
home, preferences, timeline, status, media, search, rss, list, debug,
unsupported, embed, resolver, router_utils, follow]
const instancesUrl = "https://github.com/zedeus/nitter/wiki/Instances"
const issuesUrl = "https://github.com/zedeus/nitter/issues"
@ -58,9 +58,6 @@ settings:
bindAddr = cfg.address
routes:
get "/":
resp renderMain(renderSearch(), request, cfg, themePrefs())
get "/about":
resp renderMain(renderAbout(), request, cfg, themePrefs())
@ -89,6 +86,8 @@ routes:
resp Http429, showError(
&"Instance has been rate limited.<br>Use {link} or try again later.", cfg)
extend home, ""
extend follow, ""
extend unsupported, ""
extend preferences, ""
extend resolver, ""

View File

@ -50,6 +50,11 @@ macro genPrefs*(prefDsl: untyped) =
const `name`*: PrefList = toOrderedTable(`table`)
genPrefs:
Timeline:
following(input, ""):
"A comma-separated list of users to follow."
placeholder: "one,two,three"
Display:
theme(select, "Nitter"):
"Theme"

42
src/routes/follow.nim Normal file
View File

@ -0,0 +1,42 @@
import jester, asyncdispatch, strutils, sequtils
import router_utils
import ../types
export follow
proc addUserToFollowing*(following, toAdd: string): string =
var updated = following.split(",")
if updated == @[""]:
return toAdd
elif toAdd in updated:
return following
else:
updated = concat(updated, @[toAdd])
result = updated.join(",")
proc removeUserFromFollowing*(following, remove: string): string =
var updated = following.split(",")
if updated == @[""]:
return ""
else:
updated = filter(updated, proc(x: string): bool = x != remove)
result = updated.join(",")
proc createFollowRouter*(cfg: Config) =
router follow:
post "/follow/@name":
let
following = cookiePrefs().following
toAdd = @"name"
updated = addUserToFollowing(following, toAdd)
setCookie("following", updated, daysForward(360),
httpOnly=true, secure=cfg.useHttps, path="/")
redirect(refPath())
post "/unfollow/@name":
let
following = cookiePrefs().following
remove = @"name"
updated = removeUserFromFollowing(following, remove)
setCookie("following", updated, daysForward(360),
httpOnly=true, secure=cfg.useHttps, path="/")
redirect(refPath())

49
src/routes/home.nim Normal file
View File

@ -0,0 +1,49 @@
import jester
import asyncdispatch, strutils, options, router_utils, timeline
import ".."/[prefs, types, utils, redis_cache]
import ../views/[general, home, search]
export home
proc showHome*(request: Request; query: Query; cfg: Config; prefs: Prefs;
after: string): Future[string] {.async.} =
let
timeline = await getSearch[Tweet](query, after)
html = renderHome(timeline, prefs, getPath())
return renderMain(html, request, cfg, prefs)
proc createHomeRouter*(cfg: Config) =
router home:
get "/":
let
prefs = cookiePrefs()
after = getCursor()
names = getNames(prefs.following)
var query = request.getQuery("", prefs.following)
query.fromUser = names
if @"scroll".len > 0:
var timeline = await getSearch[Tweet](query, after)
if timeline.content.len == 0: resp Http404
timeline.beginning = true
resp $renderHome(timeline, prefs, getPath())
if names.len == 0:
resp renderMain(renderSearch(), request, cfg, themePrefs())
resp (await showHome(request, query, cfg, prefs, after))
get "/following":
let
prefs = cookiePrefs()
names = getNames(prefs.following)
var
profs: seq[User]
query = request.getQuery("", prefs.following)
query.fromUser = names
query.kind = userList
for name in names:
let prof = await getCachedUser(name)
profs &= @[prof]
resp renderMain(renderFollowing(query, profs, prefs), request, cfg, prefs)

View File

@ -13,9 +13,12 @@
width: 100%;
}
.profile-card-tabs-name {
.profile-card-tabs-name-and-follow {
@include breakable;
max-width: 100%;
width: 100%;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.profile-card-username {
@ -34,6 +37,10 @@
max-width: 100%;
}
.profile-card-follow-button {
float: none;
}
.profile-card-avatar {
display: inline-block;
position: relative;

32
src/views/home.nim Normal file
View File

@ -0,0 +1,32 @@
import karax/[karaxdsl, vdom]
import search, timeline, renderutils
import ../types
proc renderFollowingUsers*(results: seq[User]; prefs: Prefs): VNode =
buildHtml(tdiv(class="timeline")):
for user in results:
renderUser(user, prefs)
proc renderHomeTabs*(query: Query): VNode =
buildHtml(ul(class="tab")):
li(class=query.getTabClass(posts)):
a(href="/"): text "Tweets"
li(class=query.getTabClass(userList)):
a(href=("/following")): text "Following"
proc renderHome*(results: Result[Tweet]; prefs: Prefs; path: string): VNode =
let query = results.query
buildHtml(tdiv(class="timeline-container")):
if query.fromUser.len > 0:
renderHomeTabs(query)
if query.fromUser.len == 0 or query.kind == tweets:
tdiv(class="timeline-header"):
renderSearchPanel(query)
renderTimelineTweets(results, prefs, path)
proc renderFollowing*(query: Query; following: seq[User]; prefs: Prefs): VNode =
buildHtml(tdiv(class="timeline-container")):
renderHomeTabs(query)
renderFollowingUsers(following, prefs)

View File

@ -12,7 +12,7 @@ proc renderStat(num: int; class: string; text=""): VNode =
span(class="profile-stat-num"):
text insertSep($num, ',')
proc renderUserCard*(user: User; prefs: Prefs): VNode =
proc renderUserCard*(user: User; prefs: Prefs; path: string): VNode =
buildHtml(tdiv(class="profile-card")):
tdiv(class="profile-card-info"):
let
@ -24,9 +24,15 @@ proc renderUserCard*(user: User; prefs: Prefs): VNode =
a(class="profile-card-avatar", href=url, target="_blank"):
genImg(user.getUserPic(size))
tdiv(class="profile-card-tabs-name"):
linkUser(user, class="profile-card-fullname")
linkUser(user, class="profile-card-username")
tdiv(class="profile-card-tabs-name-and-follow"):
tdiv():
linkUser(user, class="profile-card-fullname")
linkUser(user, class="profile-card-username")
let following = isFollowing(user.username, prefs.following)
if not following:
buttonReferer "/follow/" & user.username, "Follow", path, "profile-card-follow-button"
else:
buttonReferer "/unfollow/" & user.username, "Unfollow", path, "profile-card-follow-button"
tdiv(class="profile-card-extra"):
if user.bio.len > 0:
@ -109,7 +115,7 @@ proc renderProfile*(profile: var Profile; prefs: Prefs; path: string): VNode =
let sticky = if prefs.stickyProfile: " sticky" else: ""
tdiv(class=(&"profile-tab{sticky}")):
renderUserCard(profile.user, prefs)
renderUserCard(profile.user, prefs, path)
if profile.photoRail.len > 0:
renderPhotoRail(profile)

View File

@ -94,3 +94,7 @@ proc getAvatarClass*(prefs: Prefs): string =
"avatar"
else:
"avatar round"
proc isFollowing*(name, following: string): bool =
let following = following.split(",")
return name in following

View File

@ -57,7 +57,7 @@ proc threadFilter(tweets: openArray[Tweet]; threads: openArray[int64]; it: Tweet
elif t.replyId == result[0].id:
result.add t
proc renderUser(user: User; prefs: Prefs): VNode =
proc renderUser*(user: User; prefs: Prefs): VNode =
buildHtml(tdiv(class="timeline-item")):
a(class="tweet-link", href=("/" & user.username))
tdiv(class="tweet-body profile-result"):