Added in pastes (from people)
might still need some work/testing Also, should I add pastes in the feed?
This commit is contained in:
parent
278811c2c2
commit
a02b14782b
15 changed files with 342 additions and 5 deletions
|
@ -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<BasicResponseData?> PostProfilePic(string address, FileResult image) =>
|
||||
await PostBinary<BasicResponseData>($"/address/{address}/pfp", fileResult: image);
|
||||
|
||||
public async Task<List<Paste>> GetPastes(string address) =>
|
||||
(await Get<PastesResponseData>($"/address/{address}/pastebin"))?.Pastebin ?? new List<Paste>();
|
||||
|
||||
public async Task<BasicResponseData?> DeletePaste(string address, string title) =>
|
||||
await Delete<BasicResponseData>($"/address/{address}/pastebin/{title}");
|
||||
|
||||
public async Task<PostPasteResponseData?> PostPaste(string address, string title, string content, bool listed) =>
|
||||
await Post<PostPasteResponseData, Paste>($"/address/{address}/pastebin/", new Paste() { Title = title, Content = content, IsListed = listed });
|
||||
|
||||
#endregion
|
||||
|
||||
#region Auth
|
||||
|
|
|
@ -73,6 +73,7 @@ namespace Neighbourhood.omg.lol {
|
|||
// data for selected address
|
||||
public List<Status>? CachedAddressStatuses { get; set; }
|
||||
public List<Pic>? CachedAddressPics { get; set; }
|
||||
public List<Paste>? 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<Status>();
|
||||
CachedAddressPics = new List<Pic>();
|
||||
CachedAddressPastes = new List<Paste>();
|
||||
CachedAddressBio = null;
|
||||
}
|
||||
}
|
||||
|
@ -284,6 +286,14 @@ namespace Neighbourhood.omg.lol {
|
|||
return CachedAddressPics;
|
||||
}
|
||||
|
||||
public async Task<List<Paste>?> GetPastes(string address, bool forceRefresh = false) {
|
||||
CachedAddress = address;
|
||||
if (forceRefresh || this.CachedAddressPastes == null || this.CachedAddressPastes.Count == 0) {
|
||||
CachedAddressPastes = (await api.GetPastes(address)) ?? new List<Paste>();
|
||||
}
|
||||
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<IOrderedEnumerable<StatusOrPic>> GetFeed(bool forceRefresh = false) {
|
||||
if(forceRefresh || Feed == null || Feed.Count == 0) {
|
||||
Feed = new List<StatusOrPic>();
|
||||
|
|
|
@ -11,7 +11,9 @@
|
|||
<MarkdownEditor @ref="Editor"
|
||||
@bind-Value="@Bio"
|
||||
Theme="material-darker"
|
||||
MaxHeight="100%">
|
||||
MaxHeight="100%"
|
||||
AutoDownloadFontAwesome="false"
|
||||
>
|
||||
<Toolbar>
|
||||
<MarkdownToolbarButton Action="MarkdownAction.Bold" Icon="fa-solid fa-bold" Title="Bold" />
|
||||
<MarkdownToolbarButton Action="MarkdownAction.Italic" Icon="fa-solid fa-italic" Title="Italic" />
|
||||
|
|
130
Components/EditPasteDialog.razor
Normal file
130
Components/EditPasteDialog.razor
Normal file
|
@ -0,0 +1,130 @@
|
|||
@inject IJSRuntime JS
|
||||
@inject State State
|
||||
@inject ApiService api
|
||||
|
||||
<div class="overlay" data-ui="#@id"></div>
|
||||
<dialog id="@id">
|
||||
<div class="row">
|
||||
<div class="field text label border max">
|
||||
<InputText @bind-Value="Title"></InputText>
|
||||
<label>Content</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="field textarea label border max">
|
||||
<InputTextArea @bind-Value="Content"></InputTextArea>
|
||||
<label>Content</label>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="no-space">
|
||||
@if (Paste != null)
|
||||
{
|
||||
if (confirmDelete)
|
||||
{
|
||||
<button @onclick="ConfirmDeletePaste" disabled="@loading" class="red-7-bg white-fg">
|
||||
<i class="fa-solid fa-exclamation-triangle"></i> <span>Are you sure?</span>
|
||||
</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button @onclick="DeletePaste" disabled="@loading" class="red-7-bg white-fg">
|
||||
<i class="fa-solid fa-trash"></i> <span>Delete</span>
|
||||
</button>
|
||||
}
|
||||
}
|
||||
<div class="max"></div>
|
||||
<label class="checkbox">
|
||||
<InputCheckbox @bind-Value="Listed"></InputCheckbox>
|
||||
<span>Listed?</span>
|
||||
</label>
|
||||
<button class="transparent link" data-ui="#@id" disabled="@loading">Cancel</button>
|
||||
<button @onclick="PostPaste" disabled="@loading">
|
||||
@if (loading) {
|
||||
<span>Saving...</span>
|
||||
}
|
||||
else {
|
||||
<i class="fa-solid fa-floppy-disk"></i> <span>Save</span>
|
||||
}
|
||||
</button>
|
||||
</nav>
|
||||
</dialog>
|
||||
|
||||
@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);
|
||||
|
||||
}
|
||||
}
|
|
@ -11,7 +11,9 @@
|
|||
@bind-Value="@markdownValue"
|
||||
Theme="material-darker"
|
||||
MaxHeight="100%"
|
||||
CustomButtonClicked="@OnCustomButtonClicked">
|
||||
CustomButtonClicked="@OnCustomButtonClicked"
|
||||
AutoDownloadFontAwesome="false"
|
||||
>
|
||||
<Toolbar>
|
||||
<MarkdownToolbarButton Action="MarkdownAction.Bold" Icon="fa-solid fa-bold" Title="Bold" />
|
||||
<MarkdownToolbarButton Action="MarkdownAction.Italic" Icon="fa-solid fa-italic" Title="Italic" />
|
||||
|
|
|
@ -11,7 +11,9 @@
|
|||
@bind-Value="@markdownValue"
|
||||
Theme="material-darker"
|
||||
MaxHeight="100%"
|
||||
CustomButtonClicked="@OnCustomButtonClicked">
|
||||
CustomButtonClicked="@OnCustomButtonClicked"
|
||||
AutoDownloadFontAwesome="false"
|
||||
>
|
||||
<Toolbar>
|
||||
<MarkdownToolbarButton Action="MarkdownAction.Bold" Icon="fa-solid fa-bold" Title="Bold" />
|
||||
<MarkdownToolbarButton Action="MarkdownAction.Italic" Icon="fa-solid fa-italic" Title="Italic" />
|
||||
|
|
|
@ -51,6 +51,10 @@
|
|||
<span>/Now</span>
|
||||
</a>
|
||||
}
|
||||
<a data-ui="#pastebin">
|
||||
<i class="fa-solid fa-clipboard"></i>
|
||||
<span>Paste.lol</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -115,6 +119,15 @@
|
|||
}
|
||||
</div>
|
||||
}
|
||||
<div id="pastebin" class="page padding">
|
||||
<PasteList @ref="PasteList" PastesFunc="@(async(refresh) => await State.GetPastes(Address, refresh))" Editable="@IsMe"></PasteList>
|
||||
@if (IsMe) {
|
||||
<button class="fab circle extra large-elevate" data-ui="#paste-modal">
|
||||
<i class="fa-solid fa-clipboard"></i>
|
||||
</button>
|
||||
<EditPasteDialog id="paste-modal"></EditPasteDialog>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@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;
|
||||
|
|
62
Components/PasteCard.razor
Normal file
62
Components/PasteCard.razor
Normal file
|
@ -0,0 +1,62 @@
|
|||
@inject IJSRuntime JS
|
||||
|
||||
<article class="paste">
|
||||
@* TODO: link to paste view *@
|
||||
<nav>
|
||||
<h5 class="mono"><a href="/pastes/tbc">@Paste.Title</a></h5>
|
||||
<div class="max"></div>
|
||||
@if (MarkupView)
|
||||
{
|
||||
<button class="transparent circle" title="View Original" @onclick="() => { MarkupView = false; InvokeAsync(StateHasChanged); }"><i class="fa-solid fa-code"></i></button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button class="transparent circle" title="View Markup" @onclick="() => { MarkupView = true; InvokeAsync(StateHasChanged); }"><i class="fa-solid fa-browser"></i></button>
|
||||
}
|
||||
<button class="transparent circle" title="Copy to Clipboard" @onclick="() => Clipboard.Default.SetTextAsync(Paste.Content)"><i class="fa-solid fa-copy"></i></button>
|
||||
<button class="transparent circle" @onclick="ShareClick">
|
||||
<i class="fa-solid fa-share-nodes"></i>
|
||||
</button>
|
||||
</nav>
|
||||
<small class="nowrap gray-5-fg"><i class="fa-solid fa-clock tiny"></i> @Paste.RelativeTime</small>
|
||||
@if(MarkupView){
|
||||
<div class="padding">
|
||||
@Utilities.MdToHtmlMarkup(Paste.Content)
|
||||
</div>
|
||||
}
|
||||
else {
|
||||
<pre><code class="padding margin">@((MarkupString)Paste.Content)</code></pre>
|
||||
}
|
||||
<nav>
|
||||
<div class="max"></div>
|
||||
@if (Editable) {
|
||||
<button @onclick="EditPaste"><i class="fa-solid fa-pencil"></i> Edit</button>
|
||||
}
|
||||
</nav>
|
||||
</article>
|
||||
|
||||
@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
|
||||
});
|
||||
}
|
||||
}
|
51
Components/PasteList.razor
Normal file
51
Components/PasteList.razor
Normal file
|
@ -0,0 +1,51 @@
|
|||
@implements IDisposable
|
||||
@inject IJSRuntime JS
|
||||
@inject State State
|
||||
|
||||
@if (Editable) {
|
||||
<EditPasteDialog @ref="Dialog" id="EditPasteModal"></EditPasteDialog>
|
||||
}
|
||||
|
||||
@if (pastes != null) foreach (Paste paste in pastes) {
|
||||
<PasteCard Paste="paste" Editable="Editable" Dialog="Dialog"></PasteCard>
|
||||
}
|
||||
|
||||
<LoadingCard id="pastes-loading" icon="fa-solid fa-clipboard"></LoadingCard>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public Func<bool, Task<List<Paste>?>>? PastesFunc { get; set; }
|
||||
[Parameter]
|
||||
public bool Editable { get; set; } = false;
|
||||
|
||||
public EditPasteDialog? Dialog { get; set; }
|
||||
|
||||
private List<Paste>? 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;
|
||||
}
|
||||
}
|
6
Models/API/PastesResponseData.cs
Normal file
6
Models/API/PastesResponseData.cs
Normal file
|
@ -0,0 +1,6 @@
|
|||
namespace Neighbourhood.omg.lol.Models {
|
||||
public class PastesResponseData : IOmgLolResponseData {
|
||||
public string Message { get; set; } = string.Empty;
|
||||
public List<Paste> Pastebin { get; set; } = new List<Paste>();
|
||||
}
|
||||
}
|
6
Models/API/PostPasteResponseData.cs
Normal file
6
Models/API/PostPasteResponseData.cs
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
29
Models/Paste.cs
Normal file
29
Models/Paste.cs
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -145,6 +145,11 @@ article.pic nav { flex-wrap: wrap }
|
|||
}
|
||||
}
|
||||
|
||||
article.paste code {
|
||||
overflow-x: scroll;
|
||||
display: block;
|
||||
}
|
||||
|
||||
iframe {
|
||||
width: 100%;
|
||||
flex-grow: 1;
|
||||
|
|
|
@ -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 }
|
||||
|
|
Loading…
Reference in a new issue