Compare commits

...

2 Commits

9 changed files with 85 additions and 28 deletions

View File

@@ -27,6 +27,7 @@ enableDebug = false # enable request logs and debug endpoints (/.sessions)
logLevel = "info" # log level (debug, info, warn, error, fatal) logLevel = "info" # log level (debug, info, warn, error, fatal)
proxy = "" # http/https url, SOCKS proxies are not supported proxy = "" # http/https url, SOCKS proxies are not supported
proxyAuth = "" proxyAuth = ""
defaultFollowedAccounts = "eff,fsf" # default accounts to show when user follows none
# Change default preferences here, see src/prefs_impl.nim for a complete list # Change default preferences here, see src/prefs_impl.nim for a complete list
[Preferences] [Preferences]

View File

@@ -1,6 +1,6 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
import parsecfg except Config import parsecfg except Config
import types, strutils import types, strutils, sequtils
proc get*[T](config: parseCfg.Config; section, key: string; default: T): T = proc get*[T](config: parseCfg.Config; section, key: string; default: T): T =
let val = config.getSectionValue(section, key) let val = config.getSectionValue(section, key)
@@ -9,6 +9,7 @@ proc get*[T](config: parseCfg.Config; section, key: string; default: T): T =
when T is int: parseInt(val) when T is int: parseInt(val)
elif T is bool: parseBool(val) elif T is bool: parseBool(val)
elif T is string: val elif T is string: val
elif T is seq[string]: val.split(',').mapIt(it.strip())
proc getConfig*(path: string): (Config, parseCfg.Config) = proc getConfig*(path: string): (Config, parseCfg.Config) =
var cfg = loadConfig(path) var cfg = loadConfig(path)
@@ -41,7 +42,8 @@ proc getConfig*(path: string): (Config, parseCfg.Config) =
enableDebug: cfg.get("Config", "enableDebug", false), enableDebug: cfg.get("Config", "enableDebug", false),
logLevel: cfg.get("Config", "logLevel", ""), logLevel: cfg.get("Config", "logLevel", ""),
proxy: cfg.get("Config", "proxy", ""), proxy: cfg.get("Config", "proxy", ""),
proxyAuth: cfg.get("Config", "proxyAuth", "") proxyAuth: cfg.get("Config", "proxyAuth", ""),
defaultFollowedAccounts: cfg.get("Config", "defaultFollowedAccounts", @["eff", "fsf"])
) )
return (conf, cfg) return (conf, cfg)

View File

@@ -7,7 +7,7 @@ from os import getEnv
import jester import jester
import types, config, prefs, formatters, redis_cache, http_pool, auth, query import types, config, prefs, formatters, redis_cache, http_pool, auth, query
import views/[general, about, search, profile] import views/[general, about, search, profile, homepage]
import karax/[karaxdsl, vdom] import karax/[karaxdsl, vdom]
import routes/[ import routes/[
preferences, timeline, status, media, search, rss, list, debug, preferences, timeline, status, media, search, rss, list, debug,
@@ -101,37 +101,35 @@ routes:
get "/": get "/":
let prefs = cookiePrefs() let prefs = cookiePrefs()
if prefs.following.len > 0: if prefs.following.len > 0 or cfg.defaultFollowedAccounts.len > 0:
let let
cursor = getCursor() cursor = getCursor()
accounts = if prefs.following.len > 0: prefs.following else: cfg.defaultFollowedAccounts
isDefault = prefs.following.len == 0
currentPath = if @"f".len > 0: "/?f=" & @"f" else: "/"
var homepageQuery = initQuery(params(request)) var homepageQuery = initQuery(params(request))
if @"f".len == 0: if @"f".len == 0:
homepageQuery.kind = tweets homepageQuery.kind = tweets
homepageQuery.fromUser = prefs.following homepageQuery.fromUser = accounts
let let
timeline = await getGraphTweetSearch(homepageQuery, cursor) timeline = await getGraphTweetSearch(homepageQuery, cursor)
html = renderHomepageTimeline(timeline, prefs, getPath()) html = if isDefault:
renderDefaultTimeline(timeline, prefs, getPath())
else:
renderHomepageTimeline(timeline, prefs, getPath())
var users: seq[User] var users: seq[User]
for username in prefs.following: for username in accounts:
try: try:
let user = await getCachedUser(username) let user = await getCachedUser(username)
users.add(user) users.add(user)
except: except:
continue continue
let homepageHtml = buildHtml(tdiv(class="homepage-container")): let homepageHtml = renderHomepage(users, html, prefs, currentPath)
tdiv(class="following-column"):
tdiv(class="profile-cards"):
for user in users:
let currentPath = if @"f".len > 0: "/?f=" & @"f" else: "/"
renderUserCard(user, prefs, currentPath)
tdiv(class="timeline"):
html
resp renderMain(homepageHtml, request, cfg, prefs, "Following") resp renderMain(homepageHtml, request, cfg, prefs, "Following")
else: else:

View File

@@ -6,29 +6,48 @@
gap: 5px; gap: 5px;
max-width: 1200px; max-width: 1200px;
margin: 0 auto; margin: 0 auto;
justify-content: center;
} }
.following-column { .following-column,
.timeline-users-column {
width: 280px; width: 280px;
flex-shrink: 0; flex-shrink: 0;
} }
.timeline-users-column {
.timeline-item {
flex-direction: column;
}
}
.profile-cards { .profile-cards {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 5px; gap: 5px;
} }
.timeline-users-column > :first-child,
.homepage-container .timeline-container > .timeline > :first-child { .homepage-container .timeline-container > .timeline > :first-child {
margin-top: 0; margin-top: 0;
} }
@media (max-width: 887px) { @media (max-width: 1200px) {
.homepage-container { .homepage-container {
max-width: 100%; max-width: 100%;
padding: 0 10px;
}
} }
@media (max-width: 1100px) {
.following-column { .following-column {
display: none; display: none;
} }
} }
@media (max-width: 800px) {
.timeline-users-column {
display: none;
}
}

View File

@@ -26,6 +26,25 @@
button { button {
float: unset; float: unset;
} }
&.timeline-default {
background-color: var(--bg_panel);
border-bottom: 1px solid var(--bg_elements);
margin-bottom: 5px;
.timeline-default-message {
color: var(--fg_color);
font-size: 16px;
font-weight: normal;
margin: 0;
padding: 5px;
strong {
font-weight: bold;
color: var(--accent);
}
}
}
} }
.timeline-banner img { .timeline-banner img {

View File

@@ -290,6 +290,7 @@ type
logLevel*: string logLevel*: string
proxy*: string proxy*: string
proxyAuth*: string proxyAuth*: string
defaultFollowedAccounts*: seq[string]
rssCacheTime*: int rssCacheTime*: int
listCacheTime*: int listCacheTime*: int

View File

@@ -1,15 +1,19 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
import karax/[karaxdsl, vdom] import karax/[karaxdsl, vdom]
import profile import timeline, profile
import ".."/[types] import ".."/[types]
proc renderHomepage*(users: seq[User]; prefs: Prefs; path: string): VNode = proc renderHomepage*(users: seq[User]; timeline: VNode; prefs: Prefs; path: string): VNode =
buildHtml(tdiv(class="homepage-container")): buildHtml(tdiv(class="homepage-container")):
tdiv(class="timeline-users-column"):
for user in users:
renderUser(user, prefs, path)
tdiv(class="timeline"):
timeline
tdiv(class="following-column"): tdiv(class="following-column"):
tdiv(class="profile-cards"): tdiv(class="profile-cards"):
for user in users: for user in users:
renderUserCard(user, prefs, path) renderUserCard(user, prefs, path)
tdiv(class="timeline"):
tdiv(id="timeline-container")

View File

@@ -65,6 +65,19 @@ proc renderHomepageTimeline*(results: Timeline; prefs: Prefs; path: string): VNo
renderTimelineTweets(results, prefs, path) renderTimelineTweets(results, prefs, path)
proc renderDefaultTimeline*(results: Timeline; prefs: Prefs; path: string): VNode =
let query = results.query
buildHtml(tdiv(class="timeline-container")):
tdiv(class="timeline-header timeline-default"):
h2(class="timeline-default-message"):
text "Follow people to populate your "
strong: text "own"
text " feed"
renderHomepageTabs(query)
renderTimelineTweets(results, prefs, path)
proc renderUserSearch*(results: Result[User]; prefs: Prefs; path: string): VNode = proc renderUserSearch*(results: Result[User]; prefs: Prefs; path: string): VNode =
buildHtml(tdiv(class="timeline-container")): buildHtml(tdiv(class="timeline-container")):
renderSearchTabs(results.query) renderSearchTabs(results.query)

View File

@@ -55,7 +55,7 @@ proc renderThread(thread: Tweets; prefs: Prefs; path: string): VNode =
renderTweet(tweet, prefs, path, class=(header & "thread"), renderTweet(tweet, prefs, path, class=(header & "thread"),
index=i, last=(i == thread.high), showThread=show) index=i, last=(i == thread.high), showThread=show)
proc renderUser(user: User; prefs: Prefs; path: string): VNode = proc renderUser*(user: User; prefs: Prefs; path: string): VNode =
let class = if user.sensitive: "timeline-item nsfw" else: "timeline-item" let class = if user.sensitive: "timeline-item nsfw" else: "timeline-item"
buildHtml(tdiv(class=class)): buildHtml(tdiv(class=class)):
a(class="tweet-link", href=("/" & user.username)) a(class="tweet-link", href=("/" & user.username))