?hlsPlayback=on). These overrides aren't saved to cookies, and links won't retain the parameters. Intended for configuring RSS feeds and other cookieless environments. Hover over a preference to see its name."
-
h4(class="note"):
text "Preferences are stored client-side using cookies without any personal information."
diff --git a/src/views/profile.nim b/src/views/profile.nim
index c9012ed..2b2e410 100644
--- a/src/views/profile.nim
+++ b/src/views/profile.nim
@@ -2,7 +2,7 @@
import strutils, strformat
import karax/[karaxdsl, vdom, vstyles]
-import renderutils, search, timeline
+import renderutils, search
import ".."/[types, utils, formatters]
proc renderStat(num: int; class: string; text=""): VNode =
@@ -12,14 +12,7 @@ proc renderStat(num: int; class: string; text=""): VNode =
span(class="profile-stat-num"):
text insertSep($num, ',')
-proc renderStatLink(num: int; class, href: string): VNode =
- buildHtml(li(class=class)):
- a(href=href):
- span(class="profile-stat-header"): text capitalizeAscii(class)
- span(class="profile-stat-num"):
- text insertSep($num, ',')
-
-proc renderUserCard*(user: User; prefs: Prefs; info: AccountInfo): VNode =
+proc renderUserCard*(user: User; prefs: Prefs): VNode =
buildHtml(tdiv(class="profile-card")):
tdiv(class="profile-card-info"):
let
@@ -33,7 +26,6 @@ proc renderUserCard*(user: User; prefs: Prefs; info: AccountInfo): VNode =
tdiv(class="profile-card-tabs-name"):
linkUser(user, class="profile-card-fullname")
- verifiedIcon(user)
linkUser(user, class="profile-card-username")
tdiv(class="profile-card-extra"):
@@ -53,11 +45,6 @@ proc renderUserCard*(user: User; prefs: Prefs; info: AccountInfo): VNode =
else:
span: text place
- if info.basedIn.len > 0:
- tdiv(class="profile-location"):
- span: icon "location"
- span: text "Based in " & info.basedIn
-
if user.website.len > 0:
tdiv(class="profile-website"):
span:
@@ -66,14 +53,14 @@ proc renderUserCard*(user: User; prefs: Prefs; info: AccountInfo): VNode =
a(href=url): text url.shortLink
tdiv(class="profile-joindate"):
- a(href=(&"/{user.username}/about"), title=getJoinDateFull(user)):
+ span(title=getJoinDateFull(user)):
icon "calendar", getJoinDate(user)
tdiv(class="profile-card-extra-links"):
ul(class="profile-statlist"):
renderStat(user.tweets, "posts", text="Tweets")
- renderStatLink(user.following, "following", &"/{user.username}/following")
- renderStatLink(user.followers, "followers", &"/{user.username}/followers")
+ renderStat(user.following, "following")
+ renderStat(user.followers, "followers")
renderStat(user.likes, "likes")
proc renderPhotoRail(profile: Profile): VNode =
@@ -106,7 +93,7 @@ proc renderBanner(banner: string): VNode =
else:
a(href=getPicUrl(banner), target="_blank"): genImg(banner)
-proc renderProtected*(username: string): VNode =
+proc renderProtected(username: string): VNode =
buildHtml(tdiv(class="timeline-container")):
tdiv(class="timeline-header timeline-protected"):
h2: text "This account's tweets are protected."
@@ -114,52 +101,19 @@ proc renderProtected*(username: string): VNode =
proc renderProfile*(profile: var Profile; prefs: Prefs; path: string): VNode =
profile.tweets.query.fromUser = @[profile.user.username]
- let
- isGalleryView = profile.tweets.query.kind == QueryKind.media and
- profile.tweets.query.view == "gallery"
- viewClass = if isGalleryView: " media-only" else: ""
- buildHtml(tdiv(class=("profile-tabs" & viewClass))):
- if not isGalleryView and not prefs.hideBanner:
+ buildHtml(tdiv(class="profile-tabs")):
+ if not prefs.hideBanner:
tdiv(class="profile-banner"):
renderBanner(profile.user.banner)
- if not isGalleryView:
- let sticky = if prefs.stickyProfile: " sticky" else: ""
- tdiv(class=("profile-tab" & sticky)):
- renderUserCard(profile.user, prefs, profile.accountInfo)
- if profile.photoRail.len > 0:
- renderPhotoRail(profile)
+ let sticky = if prefs.stickyProfile: " sticky" else: ""
+ tdiv(class=("profile-tab" & sticky)):
+ renderUserCard(profile.user, prefs)
+ if profile.photoRail.len > 0:
+ renderPhotoRail(profile)
if profile.user.protected:
renderProtected(profile.user.username)
else:
renderTweetSearch(profile.tweets, prefs, path, profile.pinned)
-
-proc renderFollowTabs(user: User; activeTab: string): VNode =
- buildHtml(ul(class="tab")):
- for tab in ["Following", "Followers"]:
- li(class=(if activeTab == tab: "tab-item active" else: "tab-item")):
- a(href=(&"/{user.username}/{tab.toLowerAscii()}")): text tab
-
-proc renderUserList*(user: User; results: Result[User]; prefs: Prefs;
- path, activeTab: string): VNode =
- # Check if we've loaded all results (for page 1)
- var displayResults = results
- if results.beginning:
- let expectedCount = if activeTab == "Followers": user.followers else: user.following
- if results.content.len >= expectedCount:
- displayResults.bottom = ""
-
- buildHtml(tdiv(class="profile-tabs")):
- if not prefs.hideBanner:
- tdiv(class="profile-banner"):
- renderBanner(user.banner)
-
- let sticky = if prefs.stickyProfile: " sticky" else: ""
- tdiv(class=("profile-tab" & sticky)):
- renderUserCard(user, prefs, AccountInfo())
-
- tdiv(class="timeline-container"):
- renderFollowTabs(user, activeTab)
- renderTimelineUsers(displayResults, prefs, path)
diff --git a/src/views/renderutils.nim b/src/views/renderutils.nim
index af2f05d..331d03e 100644
--- a/src/views/renderutils.nim
+++ b/src/views/renderutils.nim
@@ -4,23 +4,14 @@ import karax/[karaxdsl, vdom, vstyles]
import ".."/[types, utils]
const smallWebp* = "?name=small&format=webp"
-const mediumWebp* = "?name=medium&format=webp"
proc getSmallPic*(url: string): string =
- if url.len == 0: return
result = url
if "?" notin url and not url.endsWith("placeholder.png"):
result &= smallWebp
result = getPicUrl(result)
-proc getMediumPic*(url: string): string =
- if url.len == 0: return
- result = url
- if "?" notin url and not url.endsWith("placeholder.png"):
- result &= mediumWebp
- result = getPicUrl(result)
-
-proc icon*(icon: string; label=""; title=""; class=""; href=""): VNode =
+proc icon*(icon: string; text=""; title=""; class=""; href=""): VNode =
var c = "icon-" & icon
if class.len > 0: c = &"{c} {class}"
buildHtml(tdiv(class="icon-container")):
@@ -29,17 +20,8 @@ proc icon*(icon: string; label=""; title=""; class=""; href=""): VNode =
else:
span(class=c, title=title)
- if label.len > 0:
- text " " & label
-
-template verifiedIcon*(user: User): untyped {.dirty.} =
- if user.verifiedType != VerifiedType.none:
- let lower = ($user.verifiedType).toLowerAscii()
- buildHtml(tdiv(class=(&"verified-icon {lower}"))):
- icon "circle", class="verified-icon-circle", title=(&"Verified {lower} account")
- icon "ok", class="verified-icon-check", title=(&"Verified {lower} account")
- else:
- text ""
+ if text.len > 0:
+ text " " & text
proc linkUser*(user: User, class=""): VNode =
let
@@ -50,10 +32,11 @@ proc linkUser*(user: User, class=""): VNode =
buildHtml(a(href=href, class=class, title=nameText)):
text nameText
- if isName:
- if user.protected:
- text " "
- icon "lock", title="Protected account"
+ if isName and user.verified:
+ icon "ok", class="verified-icon", title="Verified account"
+ if isName and user.protected:
+ text " "
+ icon "lock", title="Protected account"
proc linkText*(text: string; class=""): VNode =
let url = if "http" notin text: https & text else: text
@@ -74,20 +57,20 @@ proc buttonReferer*(action, text, path: string; class=""; `method`="post"): VNod
text text
proc genCheckbox*(pref, label: string; state: bool): VNode =
- buildHtml(label(class="pref-group checkbox-container", title=pref)):
+ buildHtml(label(class="pref-group checkbox-container")):
text label
input(name=pref, `type`="checkbox", checked=state)
span(class="checkbox")
proc genInput*(pref, label, state, placeholder: string; class=""; autofocus=true): VNode =
let p = placeholder
- buildHtml(tdiv(class=("pref-group pref-input " & class), title=pref)):
+ buildHtml(tdiv(class=("pref-group pref-input " & class))):
if label.len > 0:
label(`for`=pref): text label
input(name=pref, `type`="text", placeholder=p, value=state, autofocus=(autofocus and state.len == 0))
proc genSelect*(pref, label, state: string; options: seq[string]): VNode =
- buildHtml(tdiv(class="pref-group pref-input", title=pref)):
+ buildHtml(tdiv(class="pref-group pref-input")):
label(`for`=pref): text label
select(name=pref):
for opt in options:
@@ -99,16 +82,9 @@ proc genDate*(pref, state: string): VNode =
input(name=pref, `type`="date", value=state)
icon "calendar"
-proc genNumberInput*(pref, label, state, placeholder: string; class=""; autofocus=true; min="0"): VNode =
- let p = placeholder
- buildHtml(tdiv(class=("pref-group pref-input " & class))):
- if label.len > 0:
- label(`for`=pref): text label
- input(name=pref, `type`="number", placeholder=p, value=state, autofocus=(autofocus and state.len == 0), min=min, step="1")
-
-proc genImg*(url: string; class=""; alt=""): VNode =
+proc genImg*(url: string; class=""): VNode =
buildHtml():
- img(src=getPicUrl(url), class=class, alt=alt, loading="lazy")
+ img(src=getPicUrl(url), class=class, alt="", loading="lazy", decoding="async")
proc getTabClass*(query: Query; tab: QueryKind): string =
if query.kind == tab: "tab-item active"
diff --git a/src/views/rss.nimf b/src/views/rss.nimf
index 4738705..96f6466 100644
--- a/src/views/rss.nimf
+++ b/src/views/rss.nimf
@@ -1,231 +1,83 @@
#? stdtmpl(subsChar = '$', metaChar = '#')
## SPDX-License-Identifier: AGPL-3.0-only
-#import strutils, sequtils, xmltree, strformat, options, unicode
+#import strutils, xmltree, strformat, options, unicode
#import ../types, ../utils, ../formatters, ../prefs
-## Snowflake ID cutoff for RSS GUID format transition
-## Corresponds to approximately December 14, 2025 UTC
-#const guidCutoff = 2000000000000000000'i64
#
#proc getTitle(tweet: Tweet; retweet: string): string =
-#var prefix = ""
-#if tweet.pinned: prefix = "Pinned: "
-#elif retweet.len > 0: prefix = &"RT by @{retweet}: "
-#elif tweet.reply.len > 0: prefix = &"R to @{tweet.reply[0]}: "
+#if tweet.pinned: result = "Pinned: "
+#elif retweet.len > 0: result = &"RT by @{retweet}: "
+#elif tweet.reply.len > 0: result = &"R to @{tweet.reply[0]}: "
#end if
-#var text = strutils.splitWhitespace(stripHtml(tweet.text)).join(" ")
+#var text = stripHtml(tweet.text)
##if unicode.runeLen(text) > 32:
## text = unicode.runeSubStr(text, 0, 32) & "..."
##end if
-#text = xmltree.escape(text)
-## article tweets' text is just the article link; the title says more
-#if tweet.articlePreview.isSome and tweet.articlePreview.get().title.len > 0:
-# result = prefix & xmltree.escape(tweet.articlePreview.get().title)
-# return
+#result &= xmltree.escape(text)
+#if result.len > 0: return
#end if
-#if text.len > 0:
-# result = prefix & text
-# return
-#end if
-#if tweet.media.len > 0:
-# result = prefix
-# let firstKind = tweet.media[0].kind
-# if tweet.media.anyIt(it.kind != firstKind):
-# result &= "Media"
-# else:
-# case firstKind
-# of photoMedia: result &= "Image"
-# of videoMedia: result &= "Video"
-# of gifMedia: result &= "Gif"
-# end case
-# end if
-#end if
-#if result.len == 0 and tweet.card.isSome:
-# let card = tweet.card.get()
-# if card.kind notin {hidden, unknown} and card.title.len > 0:
-# result = prefix & xmltree.escape(card.title)
-# end if
+#if tweet.photos.len > 0:
+# result &= "Image"
+#elif tweet.video.isSome:
+# result &= "Video"
+#elif tweet.gif.isSome:
+# result &= "Gif"
#end if
#end proc
#
#proc getDescription(desc: string; cfg: Config): string =
-Twitter feed for: ${desc}. Generated by ${getUrlPrefix(cfg)}
+Twitter feed for: ${desc}. Generated by ${cfg.hostname}
#end proc
#
-#proc renderRssMedia(media: Media; tweet: Tweet; urlPrefix: string): string =
-#case media.kind
-#of photoMedia:
-# let photo = media.photo
-${xmltree.escape(card.text)}
-#end if -#if cardLink.len > 0: -# let destText = if card.dest.len > 0: xmltree.escape(card.dest) else: xmltree.escape(cardLink) -${destText} -#elif card.dest.len > 0: -${xmltree.escape(card.dest)} -#end if -#end proc -# -#proc renderRssArticle(article: ArticlePreview; urlPrefix: string): string = -#let link = urlPrefix & "/i/article/" & $article.tweetId -${xmltree.escape(article.previewText)}
-#end if -#end proc -# -#proc renderRssPoll(poll: Poll): string = -
-#for i in 0 ..< poll.options.len:
-# let perc = if poll.votes > 0: poll.values[i] / poll.votes * 100 else: 0.0
-# let pct = (&"{perc:.0f}").strip(chars={'.'})
-# let line = pct & "% — " & xmltree.escape(poll.options[i])
-${line}
-#end for
-#let votesStr = insertSep($poll.votes, ',')
-${votesStr} votes • ${xmltree.escape(poll.status)}
-
${text.replace("\n", "
\n")}
Community note: ${replaceUrls(tweet.note, prefs, absolute=urlPrefix)}
-#end if #if tweet.quote.isSome and get(tweet.quote).available: -# let quoteTweet = get(tweet.quote) -# let quoteLink = urlPrefix & getLink(quoteTweet) --${quoteTweet.user.fullname} (@${quoteTweet.user.username}) -+# let quoteLink = getLink(get(tweet.quote)) + +#end if +#if tweet.photos.len > 0: +# for photo in tweet.photos: +-${renderRssTweet(quoteTweet, cfg, prefs)} -
- -