Add client preferences
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import karax/[karaxdsl, vdom]
|
||||
|
||||
import ../utils
|
||||
import ../utils, ../types
|
||||
|
||||
const doctype = "<!DOCTYPE html>\n"
|
||||
|
||||
@@ -14,9 +14,9 @@ proc renderNavbar*(title: string): VNode =
|
||||
|
||||
tdiv(class="item right"):
|
||||
a(class="site-about", href="/about"): text "🛈"
|
||||
a(class="site-settings", href="/settings"): text "⚙"
|
||||
a(class="site-prefs", href="/settings"): text "⚙"
|
||||
|
||||
proc renderMain*(body: VNode; title="Nitter"; titleText=""; desc="";
|
||||
proc renderMain*(body: VNode; prefs: Prefs; title="Nitter"; titleText=""; desc="";
|
||||
`type`="article"; video=""; images: seq[string] = @[]): string =
|
||||
let node = buildHtml(html(lang="en")):
|
||||
head:
|
||||
@@ -60,5 +60,5 @@ proc renderError*(error: string): VNode =
|
||||
tdiv(class="error-panel"):
|
||||
span: text error
|
||||
|
||||
proc showError*(error: string; title: string): string =
|
||||
renderMain(renderError(error), title=title, titleText="Error")
|
||||
proc showError*(error: string; title: string; prefs: Prefs): string =
|
||||
renderMain(renderError(error), prefs, title=title, titleText="Error")
|
||||
|
||||
69
src/views/preferences.nim
Normal file
69
src/views/preferences.nim
Normal file
@@ -0,0 +1,69 @@
|
||||
import tables, macros
|
||||
import karax/[karaxdsl, vdom, vstyles]
|
||||
|
||||
import ../types, ../prefs
|
||||
|
||||
proc genCheckbox(pref: string; label: string; state: bool): VNode =
|
||||
buildHtml(tdiv(class="pref-group")):
|
||||
if state:
|
||||
input(name=pref, `type`="checkbox", checked="")
|
||||
else:
|
||||
input(name=pref, `type`="checkbox")
|
||||
label(`for`=pref): text label
|
||||
|
||||
proc genSelect(pref: string; label: string; options: seq[string]; state: string): VNode =
|
||||
buildHtml(tdiv(class="pref-group")):
|
||||
select(name=pref):
|
||||
for opt in options:
|
||||
if opt == state:
|
||||
option(value=opt, selected=""): text opt
|
||||
else:
|
||||
option(value=opt): text opt
|
||||
label(`for`=pref): text label
|
||||
|
||||
proc genInput(pref: string; label: string; placeholder, state: string): VNode =
|
||||
buildHtml(tdiv(class="pref-group")):
|
||||
input(name=pref, `type`="text", placeholder=placeholder, value=state)
|
||||
label(`for`=pref): text label
|
||||
|
||||
macro renderPrefs*(): untyped =
|
||||
result = nnkCall.newTree(
|
||||
ident("buildHtml"), ident("tdiv"), nnkStmtList.newTree())
|
||||
|
||||
for header, options in prefList:
|
||||
result[2].add nnkCall.newTree(
|
||||
ident("legend"),
|
||||
nnkStmtList.newTree(
|
||||
nnkCommand.newTree(ident("text"), newLit(header))))
|
||||
|
||||
for pref in options:
|
||||
let name = newLit(pref.name)
|
||||
let label = newLit(pref.label)
|
||||
let field = ident(pref.name)
|
||||
case pref.kind
|
||||
of checkbox:
|
||||
result[2].add nnkStmtList.newTree(
|
||||
nnkCall.newTree(
|
||||
ident("genCheckbox"), name, label,
|
||||
nnkDotExpr.newTree(ident("prefs"), field)))
|
||||
of select:
|
||||
let options = newLit(pref.options)
|
||||
result[2].add nnkStmtList.newTree(
|
||||
nnkCall.newTree(
|
||||
ident("genSelect"), name, label, options,
|
||||
nnkDotExpr.newTree(ident("prefs"), field)))
|
||||
of input:
|
||||
let placeholder = newLit(pref.placeholder)
|
||||
result[2].add nnkStmtList.newTree(
|
||||
nnkCall.newTree(
|
||||
ident("genInput"), name, label, placeholder,
|
||||
nnkDotExpr.newTree(ident("prefs"), field)))
|
||||
|
||||
proc renderPreferences*(prefs: Prefs): VNode =
|
||||
buildHtml(tdiv(class="preferences-container")):
|
||||
form(class="preferences", `method`="post", action="saveprefs"):
|
||||
fieldset:
|
||||
renderPrefs()
|
||||
|
||||
button(`type`="submit", class="pref-submit"):
|
||||
text "Save preferences"
|
||||
@@ -68,7 +68,7 @@ proc renderBanner(profile: Profile): VNode =
|
||||
genImg(profile.banner)
|
||||
|
||||
proc renderProfile*(profile: Profile; timeline: Timeline;
|
||||
photoRail: seq[GalleryPhoto]): VNode =
|
||||
photoRail: seq[GalleryPhoto]; prefs: Prefs): VNode =
|
||||
buildHtml(tdiv(class="profile-tabs")):
|
||||
tdiv(class="profile-banner"):
|
||||
renderBanner(profile)
|
||||
@@ -79,9 +79,9 @@ proc renderProfile*(profile: Profile; timeline: Timeline;
|
||||
renderPhotoRail(profile, photoRail)
|
||||
|
||||
tdiv(class="timeline-tab"):
|
||||
renderTimeline(timeline, profile.username, profile.protected)
|
||||
renderTimeline(timeline, profile.username, profile.protected, prefs)
|
||||
|
||||
proc renderMulti*(timeline: Timeline; usernames: string): VNode =
|
||||
proc renderMulti*(timeline: Timeline; usernames: string; prefs: Prefs): VNode =
|
||||
buildHtml(tdiv(class="multi-timeline")):
|
||||
tdiv(class="timeline-tab"):
|
||||
renderTimeline(timeline, usernames, false, multi=true)
|
||||
renderTimeline(timeline, usernames, false, prefs, multi=true)
|
||||
|
||||
@@ -4,11 +4,11 @@ import karax/[karaxdsl, vdom]
|
||||
import ../types
|
||||
import tweet, renderutils
|
||||
|
||||
proc renderReplyThread(thread: Thread): VNode =
|
||||
proc renderReplyThread(thread: Thread; prefs: Prefs): VNode =
|
||||
buildHtml(tdiv(class="reply thread thread-line")):
|
||||
for i, tweet in thread.tweets:
|
||||
let last = (i == thread.tweets.high and thread.more == 0)
|
||||
renderTweet(tweet, index=i, last=last)
|
||||
renderTweet(tweet, prefs, index=i, last=last)
|
||||
|
||||
if thread.more != 0:
|
||||
let num = if thread.more != -1: $thread.more & " " else: ""
|
||||
@@ -17,26 +17,26 @@ proc renderReplyThread(thread: Thread): VNode =
|
||||
a(class="more-replies-text", title="Not implemented yet"):
|
||||
text $num & "more " & reply
|
||||
|
||||
proc renderConversation*(conversation: Conversation): VNode =
|
||||
proc renderConversation*(conversation: Conversation; prefs: Prefs): VNode =
|
||||
let hasAfter = conversation.after != nil
|
||||
buildHtml(tdiv(class="conversation", id="posts")):
|
||||
tdiv(class="main-thread"):
|
||||
if conversation.before != nil:
|
||||
tdiv(class="before-tweet thread-line"):
|
||||
for i, tweet in conversation.before.tweets:
|
||||
renderTweet(tweet, index=i)
|
||||
renderTweet(tweet, prefs, index=i)
|
||||
|
||||
tdiv(class="main-tweet"):
|
||||
let afterClass = if hasAfter: "thread thread-line" else: ""
|
||||
renderTweet(conversation.tweet, class=afterClass)
|
||||
renderTweet(conversation.tweet, prefs, class=afterClass)
|
||||
|
||||
if hasAfter:
|
||||
tdiv(class="after-tweet thread-line"):
|
||||
let total = conversation.after.tweets.high
|
||||
for i, tweet in conversation.after.tweets:
|
||||
renderTweet(tweet, index=i, total=total)
|
||||
renderTweet(tweet, prefs, index=i, total=total)
|
||||
|
||||
if conversation.replies.len > 0:
|
||||
tdiv(class="replies"):
|
||||
for thread in conversation.replies:
|
||||
renderReplyThread(thread)
|
||||
renderReplyThread(thread, prefs)
|
||||
|
||||
@@ -54,28 +54,28 @@ proc renderProtected(username: string): VNode =
|
||||
h2: text "This account's tweets are protected."
|
||||
p: text &"Only confirmed followers have access to @{username}'s tweets."
|
||||
|
||||
proc renderThread(thread: seq[Tweet]): VNode =
|
||||
proc renderThread(thread: seq[Tweet]; prefs: Prefs): VNode =
|
||||
buildHtml(tdiv(class="timeline-tweet thread-line")):
|
||||
for i, threadTweet in thread.sortedByIt(it.time):
|
||||
renderTweet(threadTweet, "thread", index=i, total=thread.high)
|
||||
renderTweet(threadTweet, prefs, class="thread", index=i, total=thread.high)
|
||||
|
||||
proc threadFilter(it: Tweet; tweetThread: string): bool =
|
||||
it.retweet.isNone and it.reply.len == 0 and it.threadId == tweetThread
|
||||
|
||||
proc renderTweets(timeline: Timeline): VNode =
|
||||
proc renderTweets(timeline: Timeline; prefs: Prefs): VNode =
|
||||
buildHtml(tdiv(id="posts")):
|
||||
var threads: seq[string]
|
||||
for tweet in timeline.tweets:
|
||||
if tweet.threadId in threads: continue
|
||||
let thread = timeline.tweets.filterIt(threadFilter(it, tweet.threadId))
|
||||
if thread.len < 2:
|
||||
renderTweet(tweet, "timeline-tweet")
|
||||
renderTweet(tweet, prefs, class="timeline-tweet")
|
||||
else:
|
||||
renderThread(thread)
|
||||
renderThread(thread, prefs)
|
||||
threads &= tweet.threadId
|
||||
|
||||
proc renderTimeline*(timeline: Timeline; username: string;
|
||||
protected: bool; multi=false): VNode =
|
||||
proc renderTimeline*(timeline: Timeline; username: string; protected: bool;
|
||||
prefs: Prefs; multi=false): VNode =
|
||||
buildHtml(tdiv):
|
||||
if multi:
|
||||
tdiv(class="multi-header"):
|
||||
@@ -91,7 +91,7 @@ proc renderTimeline*(timeline: Timeline; username: string;
|
||||
elif timeline.tweets.len == 0:
|
||||
renderNoneFound()
|
||||
else:
|
||||
renderTweets(timeline)
|
||||
renderTweets(timeline, prefs)
|
||||
if timeline.hasMore or timeline.query.isSome:
|
||||
renderOlder(timeline, username)
|
||||
else:
|
||||
|
||||
@@ -44,26 +44,38 @@ proc renderAlbum(tweet: Tweet): VNode =
|
||||
target="_blank", style={display: flex}):
|
||||
genImg(photo)
|
||||
|
||||
proc renderVideo(video: Video): VNode =
|
||||
proc renderVideo(video: Video; prefs: Prefs): VNode =
|
||||
buildHtml(tdiv(class="attachments")):
|
||||
tdiv(class="gallery-video"):
|
||||
tdiv(class="attachment video-container"):
|
||||
let thumb = video.thumb.getSigUrl("pic")
|
||||
case video.playbackType
|
||||
of mp4:
|
||||
video(poster=video.thumb.getSigUrl("pic"), controls=""):
|
||||
video(poster=thumb, controls=""):
|
||||
source(src=video.url.getSigUrl("video"), `type`="video/mp4")
|
||||
of m3u8, vmap:
|
||||
video(poster=video.thumb.getSigUrl("pic"))
|
||||
tdiv(class="video-overlay"):
|
||||
p: text "Video playback not supported"
|
||||
if prefs.videoPlayback:
|
||||
video(poster=thumb)
|
||||
tdiv(class="video-overlay"):
|
||||
p: text "Video playback not supported yet"
|
||||
else:
|
||||
img(src=thumb)
|
||||
tdiv(class="video-overlay"):
|
||||
p: text "Video playback disabled"
|
||||
|
||||
proc renderGif(gif: Gif): VNode =
|
||||
proc renderGif(gif: Gif; prefs: Prefs): VNode =
|
||||
buildHtml(tdiv(class="attachments media-gif")):
|
||||
tdiv(class="gallery-gif", style=style(maxHeight, "unset")):
|
||||
tdiv(class="attachment"):
|
||||
video(class="gif", poster=gif.thumb.getSigUrl("pic"),
|
||||
autoplay="", muted="", loop=""):
|
||||
source(src=gif.url.getSigUrl("video"), `type`="video/mp4")
|
||||
let thumb = gif.thumb.getSigUrl("pic")
|
||||
let url = gif.url.getSigUrl("video")
|
||||
if prefs.autoplayGifs:
|
||||
|
||||
video(class="gif", poster=thumb, autoplay="", muted="", loop=""):
|
||||
source(src=url, `type`="video/mp4")
|
||||
else:
|
||||
video(class="gif", poster=thumb, controls="", muted="", loop=""):
|
||||
source(src=url, `type`="video/mp4")
|
||||
|
||||
proc renderPoll(poll: Poll): VNode =
|
||||
buildHtml(tdiv(class="poll")):
|
||||
@@ -86,7 +98,7 @@ proc renderCardImage(card: Card): VNode =
|
||||
tdiv(class="card-overlay-circle"):
|
||||
span(class="card-overlay-triangle")
|
||||
|
||||
proc renderCard(card: Card): VNode =
|
||||
proc renderCard(card: Card; prefs: Prefs): VNode =
|
||||
const largeCards = {summaryLarge, liveEvent, promoWebsite, promoVideo}
|
||||
let large = if card.kind in largeCards: " large" else: ""
|
||||
|
||||
@@ -95,7 +107,7 @@ proc renderCard(card: Card): VNode =
|
||||
if card.image.isSome:
|
||||
renderCardImage(card)
|
||||
elif card.video.isSome:
|
||||
renderVideo(get(card.video))
|
||||
renderVideo(get(card.video), prefs)
|
||||
|
||||
tdiv(class="card-content-container"):
|
||||
tdiv(class="card-content"):
|
||||
@@ -161,7 +173,8 @@ proc renderQuote(quote: Quote): VNode =
|
||||
a(href=getLink(quote)):
|
||||
text "Show this thread"
|
||||
|
||||
proc renderTweet*(tweet: Tweet; class=""; index=0; total=(-1); last=false): VNode =
|
||||
proc renderTweet*(tweet: Tweet; prefs: Prefs; class="";
|
||||
index=0; total=(-1); last=false): VNode =
|
||||
var divClass = class
|
||||
if index == total or last:
|
||||
divClass = "thread-last " & class
|
||||
@@ -187,13 +200,13 @@ proc renderTweet*(tweet: Tweet; class=""; index=0; total=(-1); last=false): VNod
|
||||
renderQuote(tweet.quote.get())
|
||||
|
||||
if tweet.card.isSome:
|
||||
renderCard(tweet.card.get())
|
||||
renderCard(tweet.card.get(), prefs)
|
||||
elif tweet.photos.len > 0:
|
||||
renderAlbum(tweet)
|
||||
elif tweet.video.isSome:
|
||||
renderVideo(tweet.video.get())
|
||||
renderVideo(tweet.video.get(), prefs)
|
||||
elif tweet.gif.isSome:
|
||||
renderGif(tweet.gif.get())
|
||||
renderGif(tweet.gif.get(), prefs)
|
||||
elif tweet.poll.isSome:
|
||||
renderPoll(tweet.poll.get())
|
||||
|
||||
|
||||
Reference in New Issue
Block a user