diff --git a/Classes/ApiService.cs b/Classes/ApiService.cs index 1f9b904..60ccc54 100644 --- a/Classes/ApiService.cs +++ b/Classes/ApiService.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Components.Forms; using Neighbourhood.omg.lol.Models; using System; +using System.Collections.Generic; using System.Diagnostics; using System.Net.Http.Json; using System.Text; @@ -240,6 +241,15 @@ namespace Neighbourhood.omg.lol public async Task PostProfilePic(string address, FileResult image) => await PostBinary($"/address/{address}/pfp", fileResult: image); + public async Task> GetPastes(string address) => + (await Get($"/address/{address}/pastebin"))?.Pastebin ?? new List(); + + public async Task DeletePaste(string address, string title) => + await Delete($"/address/{address}/pastebin/{title}"); + + public async Task PostPaste(string address, string title, string content, bool listed) => + await Post($"/address/{address}/pastebin/", new Paste() { Title = title, Content = content, IsListed = listed }); + #endregion #region Auth diff --git a/Classes/State.cs b/Classes/State.cs index baa8b26..c47f4c8 100644 --- a/Classes/State.cs +++ b/Classes/State.cs @@ -73,6 +73,7 @@ namespace Neighbourhood.omg.lol { // data for selected address public List? CachedAddressStatuses { get; set; } public List? CachedAddressPics { get; set; } + public List? CachedAddressPastes { get; set; } public MarkupString? CachedAddressBio { get; set; } private string? _cachedAddress; public string? CachedAddress { @@ -82,6 +83,7 @@ namespace Neighbourhood.omg.lol { _cachedAddress = value; CachedAddressStatuses = new List(); CachedAddressPics = new List(); + CachedAddressPastes = new List(); CachedAddressBio = null; } } @@ -284,6 +286,14 @@ namespace Neighbourhood.omg.lol { return CachedAddressPics; } + public async Task?> GetPastes(string address, bool forceRefresh = false) { + CachedAddress = address; + if (forceRefresh || this.CachedAddressPastes == null || this.CachedAddressPastes.Count == 0) { + CachedAddressPastes = (await api.GetPastes(address)) ?? new List(); + } + return CachedAddressPastes; + } + public async Task RefreshStatuses() { await GetStatuses(forceRefresh: true); if(SelectedAddressName != null) @@ -296,6 +306,11 @@ namespace Neighbourhood.omg.lol { } public async Task RefreshNow() => await GetNowGarden(forceRefresh: true); + public async Task RefreshPastes() { + if (SelectedAddressName != null) + await GetPastes(SelectedAddressName, forceRefresh: true); + } + public async Task> GetFeed(bool forceRefresh = false) { if(forceRefresh || Feed == null || Feed.Count == 0) { Feed = new List(); diff --git a/Components/EditBioDialog.razor b/Components/EditBioDialog.razor index 5d218f1..0449181 100644 --- a/Components/EditBioDialog.razor +++ b/Components/EditBioDialog.razor @@ -11,7 +11,9 @@ + MaxHeight="100%" + AutoDownloadFontAwesome="false" + > diff --git a/Components/EditPasteDialog.razor b/Components/EditPasteDialog.razor new file mode 100644 index 0000000..0467e79 --- /dev/null +++ b/Components/EditPasteDialog.razor @@ -0,0 +1,130 @@ +@inject IJSRuntime JS +@inject State State +@inject ApiService api + +
+ +
+
+ + +
+
+
+
+ + +
+
+ +
+ +@code { + private Paste? _paste; + + public Paste? Paste { + get => _paste; + set { + _paste = value; + Title = _paste?.Title; + Content = _paste?.Content; + Listed = _paste?.IsListed ?? false; + InvokeAsync(StateHasChanged); + } + } + + public string? Title { get; set; } + public string? Content { get; set; } + public bool Listed { get; set; } + private bool loading = false; + [Parameter] + public string? id { get; set; } + private bool confirmDelete { get; set; } + + protected override async Task OnInitializedAsync() { + await base.OnInitializedAsync(); + Title = Paste?.Title; + Content = Paste?.Content; + Listed = Paste?.IsListed ?? false; + } + + public async Task DeletePaste() { + if (!confirmDelete) confirmDelete = true; + await InvokeAsync(StateHasChanged); + } + + public async Task ConfirmDeletePaste() { + if (confirmDelete) { + loading = true; + await InvokeAsync(StateHasChanged); + + if (!string.IsNullOrEmpty(Paste?.Title)) { + await api.DeletePaste(State.SelectedAddressName!, Paste.Title); + await State.RefreshPastes(); + State.SendRefresh(); + await InvokeAsync(StateHasChanged); + } + + await JS.InvokeVoidAsync("ui", "#" + id); + // clear input + Title = string.Empty; + Content = string.Empty; + Listed = false; + loading = false; + confirmDelete = false; + await InvokeAsync(StateHasChanged); + } + } + + public async Task PostPaste() { + loading = true; + await InvokeAsync(StateHasChanged); + + if (!string.IsNullOrEmpty(Paste?.Title)) { + await api.PostPaste(State.SelectedAddressName!, Title, Content, Listed); + await State.RefreshPastes(); + State.SendRefresh(); + await InvokeAsync(StateHasChanged); + } + + await JS.InvokeVoidAsync("ui", "#" + id); + // clear input + Paste = null; + Title = string.Empty; + Content = string.Empty; + Listed = false; + confirmDelete = false; + await InvokeAsync(StateHasChanged); + + } +} diff --git a/Components/Pages/EditNow.razor b/Components/Pages/EditNow.razor index 9507b7f..a7294c8 100644 --- a/Components/Pages/EditNow.razor +++ b/Components/Pages/EditNow.razor @@ -11,7 +11,9 @@ @bind-Value="@markdownValue" Theme="material-darker" MaxHeight="100%" - CustomButtonClicked="@OnCustomButtonClicked"> + CustomButtonClicked="@OnCustomButtonClicked" + AutoDownloadFontAwesome="false" + > diff --git a/Components/Pages/EditProfile.razor b/Components/Pages/EditProfile.razor index b6a1493..358cbe7 100644 --- a/Components/Pages/EditProfile.razor +++ b/Components/Pages/EditProfile.razor @@ -11,7 +11,9 @@ @bind-Value="@markdownValue" Theme="material-darker" MaxHeight="100%" - CustomButtonClicked="@OnCustomButtonClicked"> + CustomButtonClicked="@OnCustomButtonClicked" + AutoDownloadFontAwesome="false" + > diff --git a/Components/Pages/Person.razor b/Components/Pages/Person.razor index 6936a98..02b0d3b 100644 --- a/Components/Pages/Person.razor +++ b/Components/Pages/Person.razor @@ -51,6 +51,10 @@ /Now } + + + Paste.lol + @@ -115,6 +119,15 @@ } } +
+ + @if (IsMe) { + + + } +
@code { @@ -126,6 +139,7 @@ _address = value; if (StatusList != null) StatusList.StatusFunc = async (refresh) => await State.GetStatuses(_address, refresh); if (PicList != null) PicList.PicsFunc = async (refresh) => await State.GetPics(_address, refresh); + if (PasteList != null) PasteList.PastesFunc = async (refresh) => await State.GetPastes(_address, refresh); } } public string ProfileUrl { @@ -141,6 +155,7 @@ private StatusList? StatusList { get; set; } private PicList? PicList { get; set; } + private PasteList? PasteList { get; set; } private bool IsMe { get => State.AddressList?.Any(a => a.Address == Address) ?? false; diff --git a/Components/PasteCard.razor b/Components/PasteCard.razor new file mode 100644 index 0000000..7c26111 --- /dev/null +++ b/Components/PasteCard.razor @@ -0,0 +1,62 @@ +@inject IJSRuntime JS + +
+ @* TODO: link to paste view *@ + + @Paste.RelativeTime + @if(MarkupView){ +
+ @Utilities.MdToHtmlMarkup(Paste.Content) +
+ } + else { +
@((MarkupString)Paste.Content)
+ } + +
+ +@code { + [Parameter] + public Paste? Paste { get; set; } + [Parameter] + public bool Editable { get; set; } = false; + [Parameter] + public EditPasteDialog? Dialog { get; set; } + + private bool MarkupView = false; + + private async Task EditPaste(EventArgs e) { + Dialog!.Paste = Paste; + await InvokeAsync(StateHasChanged); + await JS.InvokeVoidAsync("ui", "#" + Dialog?.id); + } + + public async Task ShareClick(EventArgs e) { + await Share.Default.RequestAsync(new ShareTextRequest { + Uri = Paste!.Url, + Text = Paste!.Content, + Title = Paste!.Title, + Subject = Paste!.Title + }); + } +} diff --git a/Components/PasteList.razor b/Components/PasteList.razor new file mode 100644 index 0000000..7af9842 --- /dev/null +++ b/Components/PasteList.razor @@ -0,0 +1,51 @@ +@implements IDisposable +@inject IJSRuntime JS +@inject State State + +@if (Editable) { + +} + +@if (pastes != null) foreach (Paste paste in pastes) { + + } + + + +@code { + [Parameter] + public Func?>>? PastesFunc { get; set; } + [Parameter] + public bool Editable { get; set; } = false; + + public EditPasteDialog? Dialog { get; set; } + + private List? pastes; + + protected override async Task OnInitializedAsync() { + await base.OnInitializedAsync(); + if (PastesFunc == null) return; + + if (pastes == null || pastes.Count == 0) pastes = await PastesFunc(false); + State.PropertyChanged += StateChanged; + State.CanRefresh = true; + await InvokeAsync(StateHasChanged); + await JS.InvokeVoidAsync("removeElementById", "pastes-loading"); + } + + private async void StateChanged(object? sender, PropertyChangedEventArgs e) { + if (PastesFunc == null) return; + + if (e.PropertyName == nameof(State.IsRefreshing) && State.IsRefreshing) { + using (State.GetRefreshToken()) { + pastes = await PastesFunc(true); + await InvokeAsync(StateHasChanged); + } + } + } + + public void Dispose() { + State.PropertyChanged -= StateChanged; + State.CanRefresh = false; + } +} \ No newline at end of file diff --git a/Models/API/PastesResponseData.cs b/Models/API/PastesResponseData.cs new file mode 100644 index 0000000..14cf5e7 --- /dev/null +++ b/Models/API/PastesResponseData.cs @@ -0,0 +1,6 @@ +namespace Neighbourhood.omg.lol.Models { + public class PastesResponseData : IOmgLolResponseData { + public string Message { get; set; } = string.Empty; + public List Pastebin { get; set; } = new List(); + } +} diff --git a/Models/API/PostPasteResponseData.cs b/Models/API/PostPasteResponseData.cs new file mode 100644 index 0000000..376c672 --- /dev/null +++ b/Models/API/PostPasteResponseData.cs @@ -0,0 +1,6 @@ +namespace Neighbourhood.omg.lol.Models { + public class PostPasteResponseData : IOmgLolResponseData { + public string Message { get; set; } = string.Empty; + public string Title { get; set; } = string.Empty; + } +} diff --git a/Models/Paste.cs b/Models/Paste.cs new file mode 100644 index 0000000..1208cb5 --- /dev/null +++ b/Models/Paste.cs @@ -0,0 +1,29 @@ +namespace Neighbourhood.omg.lol.Models { + public class Paste { + public string? Url; + public string Title { get; set; } = string.Empty; + public string Content { get; set; } = string.Empty; + public long? ModifiedOn { get; set; } + public int Listed { get; set; } + + public bool IsListed { + get => Listed != 0; + set => Listed = value ? 1 : 0; + } + + public DateTimeOffset ModifiedTime { get => DateTimeOffset.UnixEpoch.AddSeconds(ModifiedOn ?? 0); } + public string RelativeTime { + get { + TimeSpan offset = DateTimeOffset.UtcNow - ModifiedTime; + + var offsetString = string.Empty; + if (offset.TotalDays >= 1) offsetString = $"{Math.Floor(offset.TotalDays)} days ago"; + else if (offset.TotalHours >= 1) offsetString = $"{Math.Floor(offset.TotalHours)} hours, {offset.Minutes} minutes ago"; + else if (offset.TotalMinutes >= 1) offsetString = $"{Math.Floor(offset.TotalMinutes)} minutes ago"; + else offsetString = $"{Math.Floor(offset.TotalSeconds)} seconds ago"; + + return offsetString; + } + } + } +} diff --git a/wwwroot/css/color.css b/wwwroot/css/color.css index d7fe048..86cc057 100644 --- a/wwwroot/css/color.css +++ b/wwwroot/css/color.css @@ -35,7 +35,7 @@ menu > details > a:is(:hover,:focus,.active), menu > details > summary:is(:hover background-color: var(--active) } -#advanced :is(.field.textarea, textarea), .EasyMDEContainer { +#advanced :is(.field.textarea, textarea), .EasyMDEContainer, article.paste code { background-color: #212121; color: #eff } \ No newline at end of file diff --git a/wwwroot/css/style.css b/wwwroot/css/style.css index 101edaa..295ad4e 100644 --- a/wwwroot/css/style.css +++ b/wwwroot/css/style.css @@ -145,6 +145,11 @@ article.pic nav { flex-wrap: wrap } } } +article.paste code { + overflow-x: scroll; + display: block; +} + iframe { width: 100%; flex-grow: 1; diff --git a/wwwroot/css/type.css b/wwwroot/css/type.css index d869f38..b0a3ecf 100644 --- a/wwwroot/css/type.css +++ b/wwwroot/css/type.css @@ -7,7 +7,9 @@ body { font-size: 1.2em } .address, .author, .honey, .page-heading { font-family: "VC Honey Deck",var(--font) } -#advanced :is(.field.textarea, textarea), .EasyMDEContainer { font-family: 'MD IO 0.4', monospace, var(--emoji-font) } +.mono, #advanced :is(.field.textarea, textarea), .EasyMDEContainer, article.paste code { + font-family: 'MD IO 0.4', monospace, var(--emoji-font) +} li, p { line-height: 160% } .author { font-size: 1.2em }