mirror of
https://github.com/zedeus/nitter.git
synced 2026-04-03 20:32:10 -04:00
@@ -27,6 +27,13 @@ const GAP = 10;
|
|||||||
class Masonry {
|
class Masonry {
|
||||||
constructor(container) {
|
constructor(container) {
|
||||||
this.container = container;
|
this.container = container;
|
||||||
|
const colSizes = {
|
||||||
|
small: w => Math.max(130, w * 0.11),
|
||||||
|
medium: w => Math.max(190, Math.min(350, w * 0.22)),
|
||||||
|
large: w => Math.max(350, Math.min(480, w * 0.22)),
|
||||||
|
};
|
||||||
|
const size = container.dataset.colSize || "medium";
|
||||||
|
this._targetWidth = colSizes[size] || colSizes.medium;
|
||||||
this.colHeights = [];
|
this.colHeights = [];
|
||||||
this.colCounts = [];
|
this.colCounts = [];
|
||||||
this.colCount = 0;
|
this.colCount = 0;
|
||||||
@@ -90,8 +97,8 @@ class Masonry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_rebuild() {
|
_rebuild() {
|
||||||
const n = Math.max(1, Math.floor(this.container.clientWidth / 350));
|
|
||||||
const w = this.container.clientWidth;
|
const w = this.container.clientWidth;
|
||||||
|
const n = Math.max(1, Math.floor(w / this._targetWidth(w)));
|
||||||
if (n === this.colCount && w === this._lastWidth) return;
|
if (n === this.colCount && w === this._lastWidth) return;
|
||||||
|
|
||||||
const isFirst = this.colCount === 0;
|
const isFirst = this.colCount === 0;
|
||||||
|
|||||||
@@ -103,6 +103,10 @@ genPrefs:
|
|||||||
compactGallery(checkbox, false):
|
compactGallery(checkbox, false):
|
||||||
"Compact media gallery (no profile info or text)"
|
"Compact media gallery (no profile info or text)"
|
||||||
|
|
||||||
|
gallerySize(select, "Medium"):
|
||||||
|
"Gallery column size"
|
||||||
|
options: @["Small", "Medium", "Large"]
|
||||||
|
|
||||||
mediaView(select, "Timeline"):
|
mediaView(select, "Timeline"):
|
||||||
"Default media view"
|
"Default media view"
|
||||||
options: @["Timeline", "Grid", "Gallery"]
|
options: @["Timeline", "Grid", "Gallery"]
|
||||||
|
|||||||
@@ -293,7 +293,15 @@
|
|||||||
.gallery-masonry {
|
.gallery-masonry {
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
column-gap: 10px;
|
column-gap: 10px;
|
||||||
column-width: 350px;
|
column-width: unquote("clamp(190px, 22vw, 350px)");
|
||||||
|
|
||||||
|
&[data-col-size="small"] {
|
||||||
|
column-width: unquote("max(130px, 11vw)");
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-col-size="large"] {
|
||||||
|
column-width: unquote("clamp(350px, 22vw, 480px)");
|
||||||
|
}
|
||||||
|
|
||||||
&.masonry-active {
|
&.masonry-active {
|
||||||
column-width: unset;
|
column-width: unset;
|
||||||
@@ -470,27 +478,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 900px) {
|
|
||||||
.timeline.media-grid-view {
|
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 520px) {
|
@media (max-width: 520px) {
|
||||||
.timeline.media-grid-view {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timeline.media-gallery-view {
|
.timeline.media-gallery-view {
|
||||||
padding: 8px 0;
|
padding: 8px 0;
|
||||||
|
|
||||||
.gallery-masonry {
|
|
||||||
columns: 1;
|
|
||||||
column-gap: 0;
|
|
||||||
|
|
||||||
&.masonry-active {
|
|
||||||
columns: unset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ proc renderHead*(prefs: Prefs; cfg: Config; req: Request; titleText=""; desc="";
|
|||||||
let opensearchUrl = getUrlPrefix(cfg) & "/opensearch"
|
let opensearchUrl = getUrlPrefix(cfg) & "/opensearch"
|
||||||
|
|
||||||
buildHtml(head):
|
buildHtml(head):
|
||||||
link(rel="stylesheet", type="text/css", href="/css/style.css?v=31")
|
link(rel="stylesheet", type="text/css", href="/css/style.css?v=32")
|
||||||
link(rel="stylesheet", type="text/css", href="/css/fontello.css?v=5")
|
link(rel="stylesheet", type="text/css", href="/css/fontello.css?v=5")
|
||||||
|
|
||||||
if theme.len > 0:
|
if theme.len > 0:
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import karax/[karaxdsl, vdom, vstyles]
|
|||||||
import ".."/[types, utils]
|
import ".."/[types, utils]
|
||||||
|
|
||||||
const smallWebp* = "?name=small&format=webp"
|
const smallWebp* = "?name=small&format=webp"
|
||||||
|
const mediumWebp* = "?name=medium&format=webp"
|
||||||
|
|
||||||
proc getSmallPic*(url: string): string =
|
proc getSmallPic*(url: string): string =
|
||||||
result = url
|
result = url
|
||||||
@@ -11,6 +12,12 @@ proc getSmallPic*(url: string): string =
|
|||||||
result &= smallWebp
|
result &= smallWebp
|
||||||
result = getPicUrl(result)
|
result = getPicUrl(result)
|
||||||
|
|
||||||
|
proc getMediumPic*(url: string): string =
|
||||||
|
result = url
|
||||||
|
if "?" notin url and not url.endsWith("placeholder.png"):
|
||||||
|
result &= mediumWebp
|
||||||
|
result = getPicUrl(result)
|
||||||
|
|
||||||
proc icon*(icon: string; text=""; title=""; class=""; href=""): VNode =
|
proc icon*(icon: string; text=""; title=""; class=""; href=""): VNode =
|
||||||
var c = "icon-" & icon
|
var c = "icon-" & icon
|
||||||
if class.len > 0: c = &"{c} {class}"
|
if class.len > 0: c = &"{c} {class}"
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ proc renderNoneFound(): VNode =
|
|||||||
h2(class="timeline-none"):
|
h2(class="timeline-none"):
|
||||||
text "No items found"
|
text "No items found"
|
||||||
|
|
||||||
proc renderThread(thread: Tweets; prefs: Prefs; path: string): VNode =
|
proc renderThread(thread: Tweets; prefs: Prefs; path: string; bigThumb=false): VNode =
|
||||||
buildHtml(tdiv(class="thread-line")):
|
buildHtml(tdiv(class="thread-line")):
|
||||||
let sortedThread = thread.sortedByIt(it.id)
|
let sortedThread = thread.sortedByIt(it.id)
|
||||||
for i, tweet in sortedThread:
|
for i, tweet in sortedThread:
|
||||||
@@ -79,7 +79,7 @@ proc renderThread(thread: Tweets; prefs: Prefs; path: string): VNode =
|
|||||||
let show = i == thread.high and sortedThread[0].id != tweet.threadId
|
let show = i == thread.high and sortedThread[0].id != tweet.threadId
|
||||||
let header = if tweet.pinned or tweet.retweet.isSome: "with-header " else: ""
|
let header = if tweet.pinned or tweet.retweet.isSome: "with-header " else: ""
|
||||||
renderTweet(tweet, prefs, path, class=(header & "thread"),
|
renderTweet(tweet, prefs, path, class=(header & "thread"),
|
||||||
index=i, last=(i == thread.high))
|
index=i, last=(i == thread.high), bigThumb=bigThumb)
|
||||||
|
|
||||||
proc renderUser(user: User; prefs: Prefs): VNode =
|
proc renderUser(user: User; prefs: Prefs): VNode =
|
||||||
buildHtml(tdiv(class="timeline-item", data-username=user.username)):
|
buildHtml(tdiv(class="timeline-item", data-username=user.username)):
|
||||||
@@ -146,10 +146,12 @@ proc renderTimelineTweets*(results: Timeline; prefs: Prefs; path: string;
|
|||||||
let filtered = filterThreads(results.content, prefs)
|
let filtered = filterThreads(results.content, prefs)
|
||||||
|
|
||||||
if results.query.view == "gallery":
|
if results.query.view == "gallery":
|
||||||
tdiv(class=if prefs.compactGallery: "gallery-masonry compact" else: "gallery-masonry"):
|
let bigThumb = prefs.gallerySize == "Large"
|
||||||
|
let galClass = if prefs.compactGallery: "gallery-masonry compact" else: "gallery-masonry"
|
||||||
|
tdiv(class=galClass, `data-col-size`=prefs.gallerySize.toLowerAscii):
|
||||||
for thread in filtered:
|
for thread in filtered:
|
||||||
if thread.len == 1: renderTweet(thread[0], prefs, path)
|
if thread.len == 1: renderTweet(thread[0], prefs, path, bigThumb=bigThumb)
|
||||||
else: renderThread(thread, prefs, path)
|
else: renderThread(thread, prefs, path, bigThumb)
|
||||||
else:
|
else:
|
||||||
for thread in filtered:
|
for thread in filtered:
|
||||||
if thread.len == 1: renderTweet(thread[0], prefs, path)
|
if thread.len == 1: renderTweet(thread[0], prefs, path)
|
||||||
|
|||||||
@@ -42,13 +42,15 @@ proc renderAltText(altText: string): VNode =
|
|||||||
buildHtml(p(class="alt-text")):
|
buildHtml(p(class="alt-text")):
|
||||||
text "ALT " & altText
|
text "ALT " & altText
|
||||||
|
|
||||||
proc renderPhotoAttachment(photo: Photo): VNode =
|
proc renderPhotoAttachment(photo: Photo; bigThumb=false): VNode =
|
||||||
buildHtml(tdiv(class="attachment")):
|
buildHtml(tdiv(class="attachment")):
|
||||||
let
|
let
|
||||||
named = "name=" in photo.url
|
named = "name=" in photo.url
|
||||||
small = if named: photo.url else: photo.url & smallWebp
|
thumb = if named: photo.url
|
||||||
|
elif bigThumb: photo.url & mediumWebp
|
||||||
|
else: photo.url & smallWebp
|
||||||
a(href=getOrigPicUrl(photo.url), class="still-image", target="_blank"):
|
a(href=getOrigPicUrl(photo.url), class="still-image", target="_blank"):
|
||||||
genImg(small, alt=photo.altText)
|
genImg(thumb, alt=photo.altText)
|
||||||
if photo.altText.len > 0:
|
if photo.altText.len > 0:
|
||||||
renderAltText(photo.altText)
|
renderAltText(photo.altText)
|
||||||
|
|
||||||
@@ -76,11 +78,11 @@ proc renderVideoUnavailable(video: Video): VNode =
|
|||||||
else:
|
else:
|
||||||
p: text "This media is unavailable"
|
p: text "This media is unavailable"
|
||||||
|
|
||||||
proc renderVideoAttachment(videoData: Video; prefs: Prefs; path=""): VNode =
|
proc renderVideoAttachment(videoData: Video; prefs: Prefs; path=""; bigThumb=false): VNode =
|
||||||
let
|
let
|
||||||
playbackType = if not prefs.proxyVideos and videoData.hasMp4Url: mp4
|
playbackType = if not prefs.proxyVideos and videoData.hasMp4Url: mp4
|
||||||
else: videoData.playbackType
|
else: videoData.playbackType
|
||||||
thumb = getSmallPic(videoData.thumb)
|
thumb = if bigThumb: getMediumPic(videoData.thumb) else: getSmallPic(videoData.thumb)
|
||||||
|
|
||||||
buildHtml(tdiv(class="attachment")):
|
buildHtml(tdiv(class="attachment")):
|
||||||
if not videoData.available:
|
if not videoData.available:
|
||||||
@@ -106,12 +108,12 @@ proc renderVideoAttachment(videoData: Video; prefs: Prefs; path=""): VNode =
|
|||||||
tdiv(class="overlay-duration"): text getDuration(videoData)
|
tdiv(class="overlay-duration"): text getDuration(videoData)
|
||||||
verbatim "</div>"
|
verbatim "</div>"
|
||||||
|
|
||||||
proc renderVideo*(video: Video; prefs: Prefs; path: string): VNode =
|
proc renderVideo*(video: Video; prefs: Prefs; path: string; bigThumb=false): VNode =
|
||||||
let hasCardContent = video.description.len > 0 or video.title.len > 0
|
let hasCardContent = video.description.len > 0 or video.title.len > 0
|
||||||
|
|
||||||
buildHtml(tdiv(class="attachments card")):
|
buildHtml(tdiv(class="attachments card")):
|
||||||
tdiv(class=("gallery-video" & (if hasCardContent: " card-container" else: ""))):
|
tdiv(class=("gallery-video" & (if hasCardContent: " card-container" else: ""))):
|
||||||
renderVideoAttachment(video, prefs, path)
|
renderVideoAttachment(video, prefs, path, bigThumb)
|
||||||
if hasCardContent:
|
if hasCardContent:
|
||||||
tdiv(class="card-content"):
|
tdiv(class="card-content"):
|
||||||
h2(class="card-title"): text video.title
|
h2(class="card-title"): text video.title
|
||||||
@@ -138,14 +140,14 @@ proc renderGif(gif: Gif; prefs: Prefs): VNode =
|
|||||||
buildHtml(tdiv(class="attachments media-gif")):
|
buildHtml(tdiv(class="attachments media-gif")):
|
||||||
renderGifAttachment(gif, prefs)
|
renderGifAttachment(gif, prefs)
|
||||||
|
|
||||||
proc renderMedia(media: seq[Media]; prefs: Prefs; path: string): VNode =
|
proc renderMedia(media: seq[Media]; prefs: Prefs; path: string; bigThumb=false): VNode =
|
||||||
if media.len == 0:
|
if media.len == 0:
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
if media.len == 1:
|
if media.len == 1:
|
||||||
let item = media[0]
|
let item = media[0]
|
||||||
if item.kind == videoMedia:
|
if item.kind == videoMedia:
|
||||||
return renderVideo(item.video, prefs, path)
|
return renderVideo(item.video, prefs, path, bigThumb)
|
||||||
if item.kind == gifMedia:
|
if item.kind == gifMedia:
|
||||||
return renderGif(item.gif, prefs)
|
return renderGif(item.gif, prefs)
|
||||||
|
|
||||||
@@ -162,9 +164,9 @@ proc renderMedia(media: seq[Media]; prefs: Prefs; path: string): VNode =
|
|||||||
for mediaItem in mediaGroup:
|
for mediaItem in mediaGroup:
|
||||||
case mediaItem.kind
|
case mediaItem.kind
|
||||||
of photoMedia:
|
of photoMedia:
|
||||||
renderPhotoAttachment(mediaItem.photo)
|
renderPhotoAttachment(mediaItem.photo, bigThumb)
|
||||||
of videoMedia:
|
of videoMedia:
|
||||||
renderVideoAttachment(mediaItem.video, prefs, path)
|
renderVideoAttachment(mediaItem.video, prefs, path, bigThumb)
|
||||||
of gifMedia:
|
of gifMedia:
|
||||||
renderGifAttachment(mediaItem.gif, prefs)
|
renderGifAttachment(mediaItem.gif, prefs)
|
||||||
|
|
||||||
@@ -336,7 +338,7 @@ proc renderLocation*(tweet: Tweet): string =
|
|||||||
return $node
|
return $node
|
||||||
|
|
||||||
proc renderTweet*(tweet: Tweet; prefs: Prefs; path: string; class=""; index=0;
|
proc renderTweet*(tweet: Tweet; prefs: Prefs; path: string; class=""; index=0;
|
||||||
last=false; mainTweet=false; afterTweet=false): VNode =
|
last=false; mainTweet=false; afterTweet=false; bigThumb=false): VNode =
|
||||||
var divClass = class
|
var divClass = class
|
||||||
if index == -1 or last:
|
if index == -1 or last:
|
||||||
divClass = "thread-last " & class
|
divClass = "thread-last " & class
|
||||||
@@ -389,7 +391,7 @@ proc renderTweet*(tweet: Tweet; prefs: Prefs; path: string; class=""; index=0;
|
|||||||
renderCard(tweet.card.get(), prefs, path)
|
renderCard(tweet.card.get(), prefs, path)
|
||||||
|
|
||||||
if tweet.media.len > 0:
|
if tweet.media.len > 0:
|
||||||
renderMedia(tweet.media, prefs, path)
|
renderMedia(tweet.media, prefs, path, bigThumb)
|
||||||
|
|
||||||
if tweet.poll.isSome:
|
if tweet.poll.isSome:
|
||||||
renderPoll(tweet.poll.get())
|
renderPoll(tweet.poll.get())
|
||||||
|
|||||||
Reference in New Issue
Block a user