Initial commit
This commit is contained in:
38
src/views/conversation.nim
Normal file
38
src/views/conversation.nim
Normal file
@@ -0,0 +1,38 @@
|
||||
#? stdtmpl(subsChar = '$', metaChar = '#')
|
||||
#import xmltree, strutils, uri
|
||||
#import ../types, ../formatters, ./tweet
|
||||
#
|
||||
#proc renderConversation*(conversation: Conversation): string =
|
||||
<div class="conversation" id="tweets">
|
||||
<div class="main-thread">
|
||||
#if conversation.before.len > 0:
|
||||
<div class="before-tweet">
|
||||
#for tweet in conversation.before:
|
||||
${renderTweet(tweet)}
|
||||
#end for
|
||||
</div>
|
||||
#end if
|
||||
<div class="main-tweet">
|
||||
${renderTweet(conversation.tweet)}
|
||||
</div>
|
||||
#if conversation.after.len > 0:
|
||||
<div class="after-tweet">
|
||||
#for tweet in conversation.after:
|
||||
${renderTweet(tweet)}
|
||||
#end for
|
||||
</div>
|
||||
#end if
|
||||
</div>
|
||||
#if conversation.replies.len > 0:
|
||||
<div class="replies">
|
||||
#for thread in conversation.replies:
|
||||
<div class="thread">
|
||||
#for tweet in thread:
|
||||
${renderTweet(tweet)}
|
||||
#end for
|
||||
</div>
|
||||
#end for
|
||||
</div>
|
||||
#end if
|
||||
</div>
|
||||
#end proc
|
||||
50
src/views/general.nim
Normal file
50
src/views/general.nim
Normal file
@@ -0,0 +1,50 @@
|
||||
#? stdtmpl(subsChar = '$', metaChar = '#')
|
||||
#import user
|
||||
#import xmltree
|
||||
#
|
||||
#proc renderMain*(body: string): string =
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Nitter</title>
|
||||
<link rel="stylesheet" type="text/css" href="/style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<nav id="nav" class="nav-bar container">
|
||||
<div class="inner-nav">
|
||||
<div class="item">
|
||||
<a href="/" class="site-name">twatter</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div id="content" class="container">
|
||||
${body}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
#end proc
|
||||
#
|
||||
#proc renderSearchPanel*(): string =
|
||||
<div class="panel">
|
||||
<div class="search-panel">
|
||||
<form action="search" method="post">
|
||||
<input type="text" name="query" placeholder="Enter username...">
|
||||
<button type="submit" name="button">🔎</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
#end proc
|
||||
#
|
||||
#proc renderError*(error: string): string =
|
||||
<div class="panel">
|
||||
<div class="error-panel">
|
||||
<span>${error}</span>
|
||||
</div>
|
||||
</div>
|
||||
#end proc
|
||||
#
|
||||
#proc showError*(error: string): string =
|
||||
${renderMain(renderError(error))}
|
||||
#end proc
|
||||
99
src/views/tweet.nim
Normal file
99
src/views/tweet.nim
Normal file
@@ -0,0 +1,99 @@
|
||||
#? stdtmpl(subsChar = '$', metaChar = '#')
|
||||
#import xmltree, strutils, times, sequtils, uri
|
||||
#import ../types, ../formatters, ../utils
|
||||
#
|
||||
#proc renderHeading(tweet: Tweet): string =
|
||||
#if tweet.retweetBy != "":
|
||||
<div class="retweet">
|
||||
<span>🔄 ${tweet.retweetBy} retweeted</span>
|
||||
</div>
|
||||
#end if
|
||||
#if tweet.pinned:
|
||||
<div class="pinned">
|
||||
<span>📌 Pinned Tweet</span>
|
||||
</div>
|
||||
#end if
|
||||
<div class="media-heading">
|
||||
<div class="heading-name-row">
|
||||
<img class="avatar" src=${tweet.profile.getUserpic("_bigger").getSigUrl("pic")}>
|
||||
<div class="name-and-account-name">
|
||||
${linkUser(tweet.profile, "h4", class="username", username=false)}
|
||||
${linkUser(tweet.profile, "", class="account-name")}
|
||||
</div>
|
||||
<span class="heading-right">
|
||||
<a href="${tweet.link}" class="timeago faint-link">
|
||||
<time title="${tweet.time.format("d/M/yyyy', ' HH:mm:ss")}">${tweet.shortTime}</time>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
#end proc
|
||||
#
|
||||
#proc renderMediaGroup(tweet: Tweet): string =
|
||||
#let groups = if tweet.photos.len > 2: tweet.photos.distribute(2) else: @[tweet.photos]
|
||||
#let groupStyle = if groups.len == 1 and groups[0].len < 2: "" else: "background-color: #0f0f0f;"
|
||||
#var first = true
|
||||
<div class="attachments media-body" style="${groupStyle}">
|
||||
#for photos in groups:
|
||||
#let style = if first: "" else: "margin-top: .25em;"
|
||||
<div class="gallery-row cover-fit" style="${style}">
|
||||
#for photo in photos:
|
||||
<div class="attachment image">
|
||||
##TODO: why doesn't this work?
|
||||
<a href=${getSigUrl(photo & ":large", "pic")} target="_blank" class="image-attachment">
|
||||
#let style = if photos.len > 1 or groups.len > 1: "display: flex;" else: ""
|
||||
#let istyle = if photos.len > 1 or groups.len > 1: "" else: "border-radius: 7px;"
|
||||
<div class="still-image" style="${style}">
|
||||
<img src=${getSigUrl(photo, "pic")} referrerpolicy="" style="${istyle}">
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
#end for
|
||||
</div>
|
||||
#first = false
|
||||
#end for
|
||||
</div>
|
||||
#end proc
|
||||
#
|
||||
#proc renderGif(tweet: Tweet): string =
|
||||
#let thumbUrl = getGifThumb(tweet).getSigUrl("pic")
|
||||
#let videoUrl = getGifSrc(tweet).getSigUrl("video")
|
||||
<div class="attachments media-body">
|
||||
<div class="gallery-row" style="max-height: unset;">
|
||||
<div class="attachment image">
|
||||
<video poster=${thumbUrl} style="width: 100%; height: 100%;" autoplay muted loop>
|
||||
<source src=${videoUrl} type="video/mp4">
|
||||
</video>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
#end proc
|
||||
#
|
||||
#proc renderStats(tweet: Tweet): string =
|
||||
<div class="tweet-stats">
|
||||
<span class="tweet-stat">💬 ${$tweet.replies}</span>
|
||||
<span class="tweet-stat">🔄 ${$tweet.retweets}</span>
|
||||
<span class="tweet-stat">👍 ${$tweet.likes}</span>
|
||||
</div>
|
||||
#end proc
|
||||
#
|
||||
#proc renderTweet*(tweet: Tweet; class=""): string =
|
||||
<div class="${class}">
|
||||
<div class="status-el">
|
||||
<div class="status-body">
|
||||
${renderHeading(tweet)}
|
||||
<div class="status-content-wrapper">
|
||||
<div class="status-content media-body">
|
||||
${linkifyText(tweet.text)}
|
||||
</div>
|
||||
</div>
|
||||
#if tweet.photos.len > 0:
|
||||
${renderMediaGroup(tweet)}
|
||||
#elif tweet.gif.len > 0:
|
||||
${renderGif(tweet)}
|
||||
#end if
|
||||
${renderStats(tweet)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
#end proc
|
||||
100
src/views/user.nim
Normal file
100
src/views/user.nim
Normal file
@@ -0,0 +1,100 @@
|
||||
#? stdtmpl(subsChar = '$', metaChar = '#')
|
||||
#import xmltree, strutils, uri, htmlgen
|
||||
#import ../types, ../formatters, ../utils
|
||||
#import ./tweet
|
||||
#
|
||||
#proc renderProfileCard*(profile: Profile): string =
|
||||
#let pic = profile.getUserpic().getSigUrl("pic")
|
||||
#let smallPic = profile.getUserpic("_200x200").getSigUrl("pic")
|
||||
<div class="profile-card">
|
||||
<a class="profile-card-avatar" href="${pic}">
|
||||
<img src="${smallPic}">
|
||||
</a>
|
||||
<div class="profile-card-tabs">
|
||||
<div class="profile-card-tabs-name">
|
||||
${linkUser(profile, "h1", class="profile-card-name", username=false)}
|
||||
${linkUser(profile, "h2", class="profile-card-username")}
|
||||
</div>
|
||||
</div>
|
||||
<div class="profile-card-extra">
|
||||
<div class="profile-bio">
|
||||
#if profile.description.len > 0:
|
||||
<div class="profile-description">
|
||||
<p>${linkifyText(xmltree.escape(profile.description))}</p>
|
||||
</div>
|
||||
#end if
|
||||
</div>
|
||||
|
||||
<div class="profile-card-extra-links">
|
||||
<ul class="profile-statlist">
|
||||
<li class="tweets">
|
||||
<span class="profile-stat-header">Tweets</span>
|
||||
<span>${$profile.tweets}</span>
|
||||
</li>
|
||||
<li class="followers">
|
||||
<span class="profile-stat-header">Followers</span>
|
||||
<span>${$profile.followers}</span>
|
||||
</li>
|
||||
<li class="following">
|
||||
<span class="profile-stat-header">Following</span>
|
||||
<span>${$profile.following}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
#end proc
|
||||
#
|
||||
#proc renderBanner(profile: Profile): string =
|
||||
#if "#" in profile.banner:
|
||||
<div style="${profile.banner}" class="profile-banner-color"></div>
|
||||
#else:
|
||||
#let url = getSigUrl(profile.banner, "pic")
|
||||
<a href="${url}">
|
||||
<img src="${url}">
|
||||
</a>
|
||||
#end if
|
||||
#end proc
|
||||
#
|
||||
#proc renderTimeline*(tweets: Tweets; profile: Profile; beginning: bool): string =
|
||||
<div id="tweets">
|
||||
#if profile.protected:
|
||||
<div class="timeline-protected">
|
||||
<h2 class="timeline-protected-header">This account's Tweets are protected.</h2>
|
||||
<p class="timeline-protected-explanation">Only confirmed followers have access to @${profile.username}'s Tweets.
|
||||
</div>
|
||||
#end if
|
||||
#if not beginning:
|
||||
<div class="show-more status-el">
|
||||
<a href="/${profile.username}">Load newest tweets</a>
|
||||
</div>
|
||||
#end if
|
||||
#var retweets: Tweets
|
||||
#for tweet in tweets:
|
||||
#if tweet in retweets: continue
|
||||
#end if
|
||||
#if tweet.retweetBy.len > 0: retweets.add tweet
|
||||
#end if
|
||||
${renderTweet(tweet, "timeline-tweet")}
|
||||
#end for
|
||||
#if tweets.len > 0:
|
||||
<div class="show-more">
|
||||
<a href="/${profile.username}?after=${$tweets[^1].id}">Load older tweets</a>
|
||||
</div>
|
||||
#end if
|
||||
</div>
|
||||
#end proc
|
||||
#
|
||||
#proc renderProfile*(profile: Profile; tweets: Tweets; beginning: bool): string =
|
||||
<div class="profile-tabs">
|
||||
<div class="profile-banner">
|
||||
${renderBanner(profile)}
|
||||
</div>
|
||||
<div class="profile-tab">
|
||||
${renderProfileCard(profile)}
|
||||
</div>
|
||||
<div class="timeline-tab">
|
||||
${renderTimeline(tweets, profile, beginning)}
|
||||
</div>
|
||||
</div>
|
||||
#end proc
|
||||
Reference in New Issue
Block a user