From c7cd016baf963978cc6ec5d4746c469f7f238f33 Mon Sep 17 00:00:00 2001 From: Oliver Booth Date: Wed, 21 Feb 2024 18:27:32 +0000 Subject: [PATCH 1/5] style: improve appearance of the american countdown --- OliverBooth/Pages/Shared/_Layout.cshtml | 7 ++++++- src/scss/app.scss | 28 +++++++++++++------------ src/ts/UI.ts | 27 ++++++++++++++++++++++++ src/ts/app.ts | 26 ++++------------------- 4 files changed, 52 insertions(+), 36 deletions(-) diff --git a/OliverBooth/Pages/Shared/_Layout.cshtml b/OliverBooth/Pages/Shared/_Layout.cshtml index a2908c3..0b9c4d0 100644 --- a/OliverBooth/Pages/Shared/_Layout.cshtml +++ b/OliverBooth/Pages/Shared/_Layout.cshtml @@ -95,7 +95,12 @@
-

00 : 00 : 00 : 00

+
+
00
+
00
+
00
+
00
+
diff --git a/src/scss/app.scss b/src/scss/app.scss index 34872a3..7e0c9aa 100644 --- a/src/scss/app.scss +++ b/src/scss/app.scss @@ -365,25 +365,27 @@ td.trim-p p:last-child { background-position: center; background-repeat: no-repeat; background-size: cover; - - p { + border-radius: 10px; + cursor: pointer; + * { + cursor: pointer; + } + + .usa-countdown-element { + margin: 10px 0; + padding: 5px; font-family: "Gabarito", sans-serif; font-weight: 500; text-align: center; font-size: 3em; - margin: 0; - padding: 0; - - a { - transition: color 250ms; - } + border-right: 2px solid #fff; + border-left: 2px solid #fff; - a:link, a:visited, a:active { - color: #fff; + &:first-child { + border-left: none; } - - a:hover { - color: #03A9F4; + &:last-child { + border-right: none; } } } diff --git a/src/ts/UI.ts b/src/ts/UI.ts index 969fde3..34310f8 100644 --- a/src/ts/UI.ts +++ b/src/ts/UI.ts @@ -82,6 +82,33 @@ class UI { UI.updateProjectCards(element); } + public static updateUsaCountdown(element?: Element){ + element = element || document.getElementById("usa-countdown"); + + const daysElement = element.querySelector("#usa-countdown-days"); + const hoursElement = element.querySelector("#usa-countdown-hours"); + const minutesElement = element.querySelector("#usa-countdown-minutes"); + const secondsElement = element.querySelector("#usa-countdown-seconds"); + + const start = new Date().getTime(); + const end = Date.UTC(2024, 2, 7, 13, 20); + const diff = end - start; + let days = Math.floor(diff / (1000 * 60 * 60 * 24)); + let hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); + let minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)); + let seconds = Math.floor((diff % (1000 * 60)) / 1000); + + if (days < 0) days = 0 + if (hours < 0) hours = 0; + if (minutes < 0) minutes = 0; + if (seconds < 0) seconds = 0; + + daysElement.innerHTML = days.toString().padStart(2, '0'); + hoursElement.innerHTML = hours.toString().padStart(2, '0'); + minutesElement.innerHTML = minutes.toString().padStart(2, '0'); + secondsElement.innerHTML = seconds.toString().padStart(2, '0'); + } + /** * Adds Bootstrap tooltips to all elements with a title attribute. * @param element The element to search for elements with a title attribute in. diff --git a/src/ts/app.ts b/src/ts/app.ts index 75782ac..33d0cab 100644 --- a/src/ts/app.ts +++ b/src/ts/app.ts @@ -97,27 +97,9 @@ declare const Prism: any; } UI.updateUI(); - - setInterval(() => { - const countdown = document.querySelector("#usa-countdown p"); - const start = new Date().getTime(); - const end = Date.UTC(2024, 2, 7, 13, 20); - const diff = end - start; - let days = Math.floor(diff / (1000 * 60 * 60 * 24)); - let hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); - let minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)); - let seconds = Math.floor((diff % (1000 * 60)) / 1000); - if (days < 0) days = 0 - if (hours < 0) hours = 0; - if (minutes < 0) minutes = 0; - if (seconds < 0) seconds = 0; - - const blogUrl = '/blog/2024/02/19/the-american'; - const dayStr = days.toString().padStart(2, '0'); - const hourStr = hours.toString().padStart(2, '0'); - const minuteStr = minutes.toString().padStart(2, '0'); - const secondStr = seconds.toString().padStart(2, '0'); - countdown.innerHTML = `${dayStr} : ${hourStr} : ${minuteStr} : ${secondStr}`; - }, 1000); + const usaCountdown = document.getElementById("usa-countdown"); + usaCountdown.addEventListener("click", () => window.location.href = "/blog/2024/02/19/the-american"); + UI.updateUsaCountdown(usaCountdown); + setInterval(() => UI.updateUsaCountdown(usaCountdown), 1000); })(); From 279d8247724cec556f9d4d267220a46c54b2ff6c Mon Sep 17 00:00:00 2001 From: Oliver Booth Date: Fri, 23 Feb 2024 03:23:57 +0000 Subject: [PATCH 2/5] feat: add mastodon status card --- OliverBooth/Data/IMastodonStatus.cs | 22 ++++++++++++++++++ OliverBooth/Data/MastodonStatus.cs | 18 +++++++++++++++ OliverBooth/Pages/Blog/Index.cshtml | 16 +++++++++++++ OliverBooth/Program.cs | 2 ++ OliverBooth/Services/IMastodonService.cs | 12 ++++++++++ OliverBooth/Services/MastodonService.cs | 29 ++++++++++++++++++++++++ docker-compose.yml | 2 ++ src/scss/app.scss | 13 +++++++++++ 8 files changed, 114 insertions(+) create mode 100644 OliverBooth/Data/IMastodonStatus.cs create mode 100644 OliverBooth/Data/MastodonStatus.cs create mode 100644 OliverBooth/Services/IMastodonService.cs create mode 100644 OliverBooth/Services/MastodonService.cs diff --git a/OliverBooth/Data/IMastodonStatus.cs b/OliverBooth/Data/IMastodonStatus.cs new file mode 100644 index 0000000..0ef875d --- /dev/null +++ b/OliverBooth/Data/IMastodonStatus.cs @@ -0,0 +1,22 @@ +namespace OliverBooth.Data; + +public interface IMastodonStatus +{ + /// + /// Gets the content of the status. + /// + /// The content. + string Content { get; } + + /// + /// Gets the date and time at which this status was posted. + /// + /// The post timestamp. + DateTimeOffset CreatedAt { get; } + + /// + /// Gets the original URI of the status. + /// + /// The original URI. + Uri OriginalUri { get; } +} \ No newline at end of file diff --git a/OliverBooth/Data/MastodonStatus.cs b/OliverBooth/Data/MastodonStatus.cs new file mode 100644 index 0000000..bb02662 --- /dev/null +++ b/OliverBooth/Data/MastodonStatus.cs @@ -0,0 +1,18 @@ +using System.Text.Json.Serialization; + +namespace OliverBooth.Data; + +internal sealed class MastodonStatus : IMastodonStatus +{ + /// + [JsonPropertyName("content")] + public string Content { get; set; } = string.Empty; + + /// + [JsonPropertyName("created_at")] + public DateTimeOffset CreatedAt { get; set; } + + /// + [JsonPropertyName("url")] + public Uri OriginalUri { get; set; } = null!; +} diff --git a/OliverBooth/Pages/Blog/Index.cshtml b/OliverBooth/Pages/Blog/Index.cshtml index d558cdf..6d7d37b 100644 --- a/OliverBooth/Pages/Blog/Index.cshtml +++ b/OliverBooth/Pages/Blog/Index.cshtml @@ -1,10 +1,26 @@ @page +@using Humanizer +@using OliverBooth.Data +@using OliverBooth.Services @model Index +@inject IMastodonService MastodonService @{ ViewData["Title"] = "Blog"; + IMastodonStatus latestStatus = MastodonService.GetLatestStatus(); } +
+
+ @Html.Raw(latestStatus.Content) +
+ +
+
@await Html.PartialAsync("_LoadingSpinner")
diff --git a/OliverBooth/Program.cs b/OliverBooth/Program.cs index b471977..afef81e 100644 --- a/OliverBooth/Program.cs +++ b/OliverBooth/Program.cs @@ -32,11 +32,13 @@ builder.Services.AddSingleton(provider => new MarkdownPipelineBuilder() builder.Services.AddDbContextFactory(); builder.Services.AddDbContextFactory(); +builder.Services.AddHttpClient(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); +builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddRazorPages().AddRazorRuntimeCompilation(); builder.Services.AddControllersWithViews(); diff --git a/OliverBooth/Services/IMastodonService.cs b/OliverBooth/Services/IMastodonService.cs new file mode 100644 index 0000000..57dc9d3 --- /dev/null +++ b/OliverBooth/Services/IMastodonService.cs @@ -0,0 +1,12 @@ +using OliverBooth.Data; + +namespace OliverBooth.Services; + +public interface IMastodonService +{ + /// + /// Gets the latest status posted to Mastodon. + /// + /// The latest status. + IMastodonStatus GetLatestStatus(); +} \ No newline at end of file diff --git a/OliverBooth/Services/MastodonService.cs b/OliverBooth/Services/MastodonService.cs new file mode 100644 index 0000000..3a561a8 --- /dev/null +++ b/OliverBooth/Services/MastodonService.cs @@ -0,0 +1,29 @@ +using System.Text.Json; +using OliverBooth.Data; + +namespace OliverBooth.Services; + +internal sealed class MastodonService : IMastodonService +{ + private readonly HttpClient _httpClient; + + public MastodonService(HttpClient httpClient) + { + _httpClient = httpClient; + } + + /// + public IMastodonStatus GetLatestStatus() + { + string token = Environment.GetEnvironmentVariable("MASTODON_TOKEN") ?? string.Empty; + string account = Environment.GetEnvironmentVariable("MASTODON_ACCOUNT") ?? string.Empty; + using var request = new HttpRequestMessage(); + request.Headers.Add("Authorization", $"Bearer {token}"); + request.RequestUri = new Uri($"https://mastodon.olivr.me/api/v1/accounts/{account}/statuses"); + + using HttpResponseMessage response = _httpClient.Send(request); + using var stream = response.Content.ReadAsStream(); + var statuses = JsonSerializer.Deserialize(stream) ?? Array.Empty(); + return statuses[0]; + } +} diff --git a/docker-compose.yml b/docker-compose.yml index 9668736..3a9ace9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,3 +19,5 @@ services: environment: - SSL_CERT_PATH=${SSL_CERT_PATH} - SSL_KEY_PATH=${SSL_KEY_PATH} + - MASTODON_TOKEN=${MASTODON_TOKEN} + - MASTODON_ACCOUNT=${MASTODON_ACCOUNT} diff --git a/src/scss/app.scss b/src/scss/app.scss index 7e0c9aa..19e3129 100644 --- a/src/scss/app.scss +++ b/src/scss/app.scss @@ -402,4 +402,17 @@ td.trim-p p:last-child { color: #03A9F4; background-color: #1E1E1E !important; } +} + +.mastodon-update-card.card { + background-color: desaturate(darken(#6364FF, 50%), 50%); + margin-bottom: 50px; + + p:last-child { + margin-bottom: 0; + } + + button.btn.btn-mastodon { + background-color: #6364FF; + } } \ No newline at end of file From 21be5e96221ef9a2627f7e700e66378b7d8edf52 Mon Sep 17 00:00:00 2001 From: Oliver Booth Date: Fri, 23 Feb 2024 05:04:50 +0000 Subject: [PATCH 3/5] refactor: hide countdown after deadline --- OliverBooth/Pages/Shared/_Layout.cshtml | 17 ++++++++++------- src/ts/app.ts | 8 +++++--- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/OliverBooth/Pages/Shared/_Layout.cshtml b/OliverBooth/Pages/Shared/_Layout.cshtml index 0b9c4d0..b1d9a88 100644 --- a/OliverBooth/Pages/Shared/_Layout.cshtml +++ b/OliverBooth/Pages/Shared/_Layout.cshtml @@ -94,14 +94,17 @@
-
-
-
00
-
00
-
00
-
00
+@if (DateTimeOffset.UtcNow < new DateTime(2024, 03, 08)) +{ +
+
+
00
+
00
+
00
+
00
+
-
+}
diff --git a/src/ts/app.ts b/src/ts/app.ts index 33d0cab..9d0723e 100644 --- a/src/ts/app.ts +++ b/src/ts/app.ts @@ -99,7 +99,9 @@ declare const Prism: any; UI.updateUI(); const usaCountdown = document.getElementById("usa-countdown"); - usaCountdown.addEventListener("click", () => window.location.href = "/blog/2024/02/19/the-american"); - UI.updateUsaCountdown(usaCountdown); - setInterval(() => UI.updateUsaCountdown(usaCountdown), 1000); + if (usaCountdown) { + usaCountdown.addEventListener("click", () => window.location.href = "/blog/2024/02/19/the-american"); + UI.updateUsaCountdown(usaCountdown); + setInterval(() => UI.updateUsaCountdown(usaCountdown), 1000); + } })(); From 70f167c9c3876347076f4d27a01b2aafafb3350b Mon Sep 17 00:00:00 2001 From: Oliver Booth Date: Fri, 23 Feb 2024 05:36:31 +0000 Subject: [PATCH 4/5] feat: add support for mastodon media --- OliverBooth/Data/IMastodonStatus.cs | 22 ------------- OliverBooth/Data/Mastodon/AttachmentType.cs | 10 ++++++ OliverBooth/Data/Mastodon/MastodonStatus.cs | 34 ++++++++++++++++++++ OliverBooth/Data/Mastodon/MediaAttachment.cs | 22 +++++++++++++ OliverBooth/Data/MastodonStatus.cs | 18 ----------- OliverBooth/Pages/Blog/Index.cshtml | 22 +++++++++++-- OliverBooth/Services/IMastodonService.cs | 4 +-- OliverBooth/Services/MastodonService.cs | 13 ++++++-- 8 files changed, 98 insertions(+), 47 deletions(-) delete mode 100644 OliverBooth/Data/IMastodonStatus.cs create mode 100644 OliverBooth/Data/Mastodon/AttachmentType.cs create mode 100644 OliverBooth/Data/Mastodon/MastodonStatus.cs create mode 100644 OliverBooth/Data/Mastodon/MediaAttachment.cs delete mode 100644 OliverBooth/Data/MastodonStatus.cs diff --git a/OliverBooth/Data/IMastodonStatus.cs b/OliverBooth/Data/IMastodonStatus.cs deleted file mode 100644 index 0ef875d..0000000 --- a/OliverBooth/Data/IMastodonStatus.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace OliverBooth.Data; - -public interface IMastodonStatus -{ - /// - /// Gets the content of the status. - /// - /// The content. - string Content { get; } - - /// - /// Gets the date and time at which this status was posted. - /// - /// The post timestamp. - DateTimeOffset CreatedAt { get; } - - /// - /// Gets the original URI of the status. - /// - /// The original URI. - Uri OriginalUri { get; } -} \ No newline at end of file diff --git a/OliverBooth/Data/Mastodon/AttachmentType.cs b/OliverBooth/Data/Mastodon/AttachmentType.cs new file mode 100644 index 0000000..6d0b5e1 --- /dev/null +++ b/OliverBooth/Data/Mastodon/AttachmentType.cs @@ -0,0 +1,10 @@ +namespace OliverBooth.Data.Mastodon; + +public enum AttachmentType +{ + Unknown, + Image, + GifV, + Video, + Audio +} diff --git a/OliverBooth/Data/Mastodon/MastodonStatus.cs b/OliverBooth/Data/Mastodon/MastodonStatus.cs new file mode 100644 index 0000000..a4d3ebf --- /dev/null +++ b/OliverBooth/Data/Mastodon/MastodonStatus.cs @@ -0,0 +1,34 @@ +using System.Text.Json.Serialization; + +namespace OliverBooth.Data.Mastodon; + +public sealed class MastodonStatus +{ + /// + /// Gets the content of the status. + /// + /// The content. + [JsonPropertyName("content")] + public string Content { get; set; } = string.Empty; + + /// + /// Gets the date and time at which this status was posted. + /// + /// The post timestamp. + [JsonPropertyName("created_at")] + public DateTimeOffset CreatedAt { get; set; } + + /// + /// Gets the media attachments for this status. + /// + /// The media attachments. + [JsonPropertyName("media_attachments")] + public IReadOnlyList MediaAttachments { get; set; } = ArraySegment.Empty; + + /// + /// Gets the original URI of the status. + /// + /// The original URI. + [JsonPropertyName("url")] + public Uri OriginalUri { get; set; } = null!; +} diff --git a/OliverBooth/Data/Mastodon/MediaAttachment.cs b/OliverBooth/Data/Mastodon/MediaAttachment.cs new file mode 100644 index 0000000..ddc3307 --- /dev/null +++ b/OliverBooth/Data/Mastodon/MediaAttachment.cs @@ -0,0 +1,22 @@ +namespace OliverBooth.Data.Mastodon; + +public sealed class MediaAttachment +{ + /// + /// Gets the preview URL of the attachment. + /// + /// The preview URL. + public Uri PreviewUrl { get; set; } = null!; + + /// + /// Gets the type of this attachment. + /// + /// The attachment type. + public AttachmentType Type { get; set; } = AttachmentType.Unknown; + + /// + /// Gets the URL of the attachment. + /// + /// The URL. + public Uri Url { get; set; } = null!; +} \ No newline at end of file diff --git a/OliverBooth/Data/MastodonStatus.cs b/OliverBooth/Data/MastodonStatus.cs deleted file mode 100644 index bb02662..0000000 --- a/OliverBooth/Data/MastodonStatus.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Text.Json.Serialization; - -namespace OliverBooth.Data; - -internal sealed class MastodonStatus : IMastodonStatus -{ - /// - [JsonPropertyName("content")] - public string Content { get; set; } = string.Empty; - - /// - [JsonPropertyName("created_at")] - public DateTimeOffset CreatedAt { get; set; } - - /// - [JsonPropertyName("url")] - public Uri OriginalUri { get; set; } = null!; -} diff --git a/OliverBooth/Pages/Blog/Index.cshtml b/OliverBooth/Pages/Blog/Index.cshtml index 6d7d37b..1109038 100644 --- a/OliverBooth/Pages/Blog/Index.cshtml +++ b/OliverBooth/Pages/Blog/Index.cshtml @@ -1,18 +1,36 @@ @page @using Humanizer -@using OliverBooth.Data +@using OliverBooth.Data.Mastodon @using OliverBooth.Services @model Index @inject IMastodonService MastodonService @{ ViewData["Title"] = "Blog"; - IMastodonStatus latestStatus = MastodonService.GetLatestStatus(); + MastodonStatus latestStatus = MastodonService.GetLatestStatus(); }
@Html.Raw(latestStatus.Content) + @foreach (MediaAttachment attachment in latestStatus.MediaAttachments) + { + switch (attachment.Type) + { + case AttachmentType.Audio: +

+ break; + + case AttachmentType.Video: +

+ break; + + case AttachmentType.Image: + case AttachmentType.GifV: +

+ break; + } + }