diff --git a/public/css/fontello.css b/public/css/fontello.css index eb41de7..8f9abad 100644 --- a/public/css/fontello.css +++ b/public/css/fontello.css @@ -1,12 +1,12 @@ @font-face { font-family: "fontello"; - src: url("/fonts/fontello.eot?49059696"); + src: url("/fonts/fontello.eot?59696369"); src: - url("/fonts/fontello.eot?49059696#iefix") format("embedded-opentype"), - url("/fonts/fontello.woff2?49059696") format("woff2"), - url("/fonts/fontello.woff?49059696") format("woff"), - url("/fonts/fontello.ttf?49059696") format("truetype"), - url("/fonts/fontello.svg?49059696#fontello") format("svg"); + url("/fonts/fontello.eot?59696369#iefix") format("embedded-opentype"), + url("/fonts/fontello.woff2?59696369") format("woff2"), + url("/fonts/fontello.woff?59696369") format("woff"), + url("/fonts/fontello.ttf?59696369") format("truetype"), + url("/fonts/fontello.svg?59696369#fontello") format("svg"); font-weight: normal; font-style: normal; } @@ -36,113 +36,118 @@ -moz-osx-font-smoothing: grayscale; } +.icon-group:before { + content: "\e0c3"; +} + +/* '' */ .icon-views:before { content: "\e800"; } -/* '' */ +/* '' */ .icon-heart:before { content: "\e801"; } -/* '' */ +/* '' */ .icon-quote:before { content: "\e802"; } -/* '' */ +/* '' */ .icon-comment:before { content: "\e803"; } -/* '' */ -.icon-group:before { - content: "\e804"; -} - -/* '' */ +/* '' */ .icon-play:before { content: "\e805"; } -/* '' */ +/* '' */ .icon-link:before { content: "\e806"; } -/* '' */ +/* '' */ .icon-calendar:before { content: "\e807"; } -/* '' */ +/* '' */ .icon-location:before { content: "\e808"; } -/* '' */ +/* '' */ .icon-picture:before { content: "\e809"; } -/* '' */ +/* '' */ .icon-lock:before { content: "\e80a"; } -/* '' */ +/* '' */ .icon-down:before { content: "\e80b"; } -/* '' */ +/* '' */ .icon-retweet:before { content: "\e80c"; } -/* '' */ +/* '' */ .icon-search:before { content: "\e80d"; } -/* '' */ +/* '' */ .icon-pin:before { content: "\e80e"; } -/* '' */ +/* '' */ .icon-cog:before { content: "\e80f"; } -/* '' */ +/* '' */ .icon-rss:before { content: "\e810"; } -/* '' */ +/* '' */ .icon-ok:before { content: "\e811"; } -/* '' */ -.icon-attention:before { +/* '' */ +.icon-attention-circled:before { content: "\e812"; } -/* '' */ +/* '' */ +.icon-download-alt:before { + content: "\e813"; +} + +/* '' */ .icon-circle:before { content: "\f111"; } -/* '' */ +/* '' */ .icon-info:before { content: "\f128"; } -/* '' */ +/* '' */ .icon-bird:before { content: "\f309"; } -/* '' */ +/* '' */ diff --git a/public/fonts/fontello.eot b/public/fonts/fontello.eot index ed1dba8..a5b8b1c 100644 Binary files a/public/fonts/fontello.eot and b/public/fonts/fontello.eot differ diff --git a/public/fonts/fontello.svg b/public/fonts/fontello.svg index 19db3bd..5dc65a8 100644 --- a/public/fonts/fontello.svg +++ b/public/fonts/fontello.svg @@ -6,6 +6,8 @@ + + @@ -14,8 +16,6 @@ - - @@ -44,6 +44,8 @@ + + diff --git a/public/fonts/fontello.ttf b/public/fonts/fontello.ttf index be34c4e..a2b972e 100644 Binary files a/public/fonts/fontello.ttf and b/public/fonts/fontello.ttf differ diff --git a/public/fonts/fontello.woff b/public/fonts/fontello.woff index 699b2ba..65508ca 100644 Binary files a/public/fonts/fontello.woff and b/public/fonts/fontello.woff differ diff --git a/public/fonts/fontello.woff2 b/public/fonts/fontello.woff2 index cfe8dfb..a8d96da 100644 Binary files a/public/fonts/fontello.woff2 and b/public/fonts/fontello.woff2 differ diff --git a/src/sass/tweet/_base.scss b/src/sass/tweet/_base.scss index 7606c31..93d2086 100644 --- a/src/sass/tweet/_base.scss +++ b/src/sass/tweet/_base.scss @@ -305,7 +305,7 @@ margin-top: 4px; margin-bottom: -2px; - .icon-attention { + .icon-attention-circled { margin-right: -3px; } } diff --git a/src/sass/tweet/video.scss b/src/sass/tweet/video.scss index c20d348..28fc125 100644 --- a/src/sass/tweet/video.scss +++ b/src/sass/tweet/video.scss @@ -28,6 +28,40 @@ video { } } +.video-download { + position: absolute; + top: 8px; + right: 8px; + z-index: 2; + color: var(--accent); + text-decoration: none; + opacity: 0; + transition: opacity 0.2s; + filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.7)); + + .icon-container { + margin: 0; + } + + .icon-download-alt { + font-size: 18px; + } +} + +.attachment:hover .video-download { + opacity: 1; + + &:hover { + color: var(--fg_color); + } +} + +@media (hover: none) { + .video-download { + opacity: 1; + } +} + .video-overlay { @include play-button; background-color: $shadow; diff --git a/src/views/general.nim b/src/views/general.nim index 9a5a7eb..d49804e 100644 --- a/src/views/general.nim +++ b/src/views/general.nim @@ -50,8 +50,8 @@ proc renderHead*(prefs: Prefs; cfg: Config; req: Request; titleText=""; desc=""; let opensearchUrl = getUrlPrefix(cfg) & "/opensearch" buildHtml(head): - link(rel="stylesheet", type="text/css", href="/css/style.css?v=43") - link(rel="stylesheet", type="text/css", href="/css/fontello.css?v=5") + link(rel="stylesheet", type="text/css", href="/css/style.css?v=44") + link(rel="stylesheet", type="text/css", href="/css/fontello.css?v=7") if theme.len > 0: link(rel="stylesheet", type="text/css", href=(&"/css/themes/{theme}.css")) @@ -119,7 +119,7 @@ proc renderHead*(prefs: Prefs; cfg: Config; req: Request; titleText=""; desc=""; # this is last so images are also preloaded # if this is done earlier, Chrome only preloads one image for some reason link(rel="preload", type="font/woff2", `as`="font", - href="/fonts/fontello.woff2?61663884", crossorigin="anonymous") + href="/fonts/fontello.woff2?59696369", crossorigin="anonymous") proc renderMain*(body: VNode; req: Request; cfg: Config; prefs=defaultPrefs; titleText=""; desc=""; ogTitle=""; rss=""; video=""; diff --git a/src/views/tweet.nim b/src/views/tweet.nim index 3d785ae..b66269a 100644 --- a/src/views/tweet.nim +++ b/src/views/tweet.nim @@ -97,6 +97,12 @@ proc renderVideoUnavailable(video: Video): VNode = else: p: text "This media is unavailable" +proc getVideoDownloadUrl(videoData: Video): string = + let mp4Vars = videoData.variants.filterIt(it.contentType == mp4) + if mp4Vars.len == 0: return "" + let best = mp4Vars.sortedByIt(it.bitrate)[^1].url + if best.startsWith("http"): getVidUrl(best) else: best + proc renderVideoAttachment(videoData: Video; prefs: Prefs; path=""; bigThumb=false): VNode = let playbackType = if not prefs.proxyVideos and videoData.hasMp4Url: mp4 @@ -127,6 +133,11 @@ proc renderVideoAttachment(videoData: Video; prefs: Prefs; path=""; bigThumb=fal if videoData.durationMs > 0: tdiv(class="overlay-duration"): text getDuration(videoData) verbatim "" + if videoData.available: + let dlUrl = getVideoDownloadUrl(videoData) + if dlUrl.len > 0: + a(class="video-download", href=dlUrl, download="", + title="Download video"): icon "download-alt" proc renderVideo*(video: Video; prefs: Prefs; path: string; bigThumb=false): VNode = let hasCardContent = video.description.len > 0 or video.title.len > 0 @@ -345,10 +356,10 @@ proc renderDisclosures*(tweet: Tweet): VNode = buildHtml(tdiv(class="disclosures")): if tweet.isAI: span(data-disclosure="ai"): - icon "attention", "Made with AI" + icon "attention-circled", "Made with AI" if tweet.isAd: span(data-disclosure="ad"): - icon "attention", "Paid partnership (ad)" + icon "attention-circled", "Paid partnership (ad)" proc renderLocation*(tweet: Tweet): string = let (place, url) = tweet.getLocation()