Edit profile / profile pic
This commit is contained in:
parent
0fcda98b9f
commit
f24ef392f7
43 changed files with 611 additions and 338 deletions
|
@ -9,7 +9,7 @@ using System.Text.RegularExpressions;
|
|||
|
||||
namespace Neighbourhood.omg.lol
|
||||
{
|
||||
public class RestService {
|
||||
public class RestService {
|
||||
HttpClient _client;
|
||||
JsonSerializerOptions _serializerOptions;
|
||||
public const string BaseUrl = "https://api.omg.lol";
|
||||
|
@ -25,6 +25,18 @@ namespace Neighbourhood.omg.lol
|
|||
AddToken(token);
|
||||
}
|
||||
|
||||
public T? Deserialize<T>(string str) {
|
||||
T? responseObj = default(T);
|
||||
try {
|
||||
responseObj = JsonSerializer.Deserialize<T>(str, _serializerOptions);
|
||||
}
|
||||
catch (JsonException ex) {
|
||||
Debug.WriteLine(@"\tERROR {0}", ex.Message);
|
||||
Debug.WriteLine(str);
|
||||
}
|
||||
return responseObj;
|
||||
}
|
||||
|
||||
public void AddToken(string? token = null) {
|
||||
if (token == null) token = Task.Run(() => SecureStorage.GetAsync("accounttoken")).GetAwaiter().GetResult();
|
||||
if (token != null) _client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
||||
|
@ -42,8 +54,8 @@ namespace Neighbourhood.omg.lol
|
|||
string str = await response.Content.ReadAsStringAsync();
|
||||
try {
|
||||
OmgLolResponse<T>? responseObj = await response.Content.ReadFromJsonAsync<OmgLolResponse<T>>(_serializerOptions, cancellationToken: cancellationToken);
|
||||
if (responseObj?.Request?.Success ?? false) {
|
||||
responseData = responseObj.Response;
|
||||
if (responseObj?.Request == null || (responseObj?.Request?.Success ?? false)) {
|
||||
responseData = responseObj!.Response;
|
||||
}
|
||||
}
|
||||
catch (JsonException ex) {
|
||||
|
@ -78,6 +90,81 @@ namespace Neighbourhood.omg.lol
|
|||
return responseData;
|
||||
}
|
||||
|
||||
private async Task<TResponse?> PostBinary<TResponse, TData>(string uri, FileResult? fileResult = null, CancellationToken cancellationToken = default) where TResponse : IOmgLolResponseData {
|
||||
TResponse? responseData = default(TResponse);
|
||||
try {
|
||||
if (fileResult != null) using (var fileStream = await fileResult.OpenReadAsync()) {
|
||||
Uri url = new Uri(_client.BaseAddress?.AbsoluteUri + uri);
|
||||
if (string.IsNullOrEmpty(url.Query)) uri += "?binary";
|
||||
else if (!url.Query.Contains("binary")) uri += "&binary";
|
||||
|
||||
HttpContent fileStreamContent = new StreamContent(fileStream);
|
||||
fileStreamContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(fileResult.ContentType ?? "application/octet-stream");
|
||||
fileStreamContent.Headers.ContentLength = fileStream.Length;
|
||||
HttpResponseMessage response = await _client.PostAsync(uri, fileStreamContent, cancellationToken: cancellationToken);
|
||||
string str = await response.Content.ReadAsStringAsync();
|
||||
if (response.IsSuccessStatusCode) {
|
||||
OmgLolResponse<TResponse>? responseObj = await response.Content.ReadFromJsonAsync<OmgLolResponse<TResponse>>(_serializerOptions, cancellationToken: cancellationToken);
|
||||
if (responseObj?.Request?.Success ?? false) {
|
||||
responseData = responseObj.Response;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Debug.WriteLine(@"\tERROR {0}", ex.Message);
|
||||
}
|
||||
|
||||
return responseData;
|
||||
}
|
||||
|
||||
private async Task<TResponse?> PostMultipart<TResponse, TData>(string uri, TData? data = null, FileResult? fileResult = null, CancellationToken cancellationToken = default)
|
||||
where TResponse : IOmgLolResponseData where TData : class
|
||||
{
|
||||
if(fileResult != null) {
|
||||
using (var fileStream = await fileResult.OpenReadAsync())
|
||||
return await PostMultipart<TResponse, TData>(uri, data: data, fileStream: fileStream, fileName: fileResult.FileName, contentType: fileResult.ContentType);
|
||||
}
|
||||
else return await PostMultipart<TResponse, TData>(uri, data, fileStream: null);
|
||||
}
|
||||
|
||||
private async Task<TResponse?> PostMultipart<TResponse, TData>(string uri, TData? data = null, Stream? fileStream = null, string? fileName = null, string? contentType = null, CancellationToken cancellationToken = default)
|
||||
where TResponse : IOmgLolResponseData where TData : class
|
||||
{
|
||||
TResponse? responseData = default;
|
||||
try {
|
||||
using (MultipartFormDataContent formData = new MultipartFormDataContent()) {
|
||||
if(fileStream != null) {
|
||||
HttpContent fileStreamContent = new StreamContent(fileStream);
|
||||
fileStreamContent.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("form-data") {
|
||||
Name = "\"file\"",
|
||||
FileName = $"\"{fileName}\"" ?? "\"unknown\""
|
||||
};
|
||||
fileStreamContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(contentType ?? "application/octet-stream");
|
||||
formData.Add(fileStreamContent);
|
||||
}
|
||||
if (data != null) {
|
||||
HttpContent jsonContent = JsonContent.Create(data, options: _serializerOptions);
|
||||
formData.Add(jsonContent);
|
||||
}
|
||||
|
||||
HttpResponseMessage response = await _client.PostAsync(uri, formData, cancellationToken: cancellationToken);
|
||||
string str = await response.Content.ReadAsStringAsync();
|
||||
if (response.IsSuccessStatusCode) {
|
||||
OmgLolResponse<TResponse>? responseObj = await response.Content.ReadFromJsonAsync<OmgLolResponse<TResponse>>(_serializerOptions, cancellationToken: cancellationToken);
|
||||
if (responseObj?.Request?.Success ?? false) {
|
||||
responseData = responseObj.Response;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Debug.WriteLine(@"\tERROR {0}", ex.Message);
|
||||
}
|
||||
|
||||
return responseData;
|
||||
}
|
||||
|
||||
private async Task<TResponse?> Put<TResponse, TData>(string uri, TData data, CancellationToken cancellationToken = default) where TResponse : IOmgLolResponseData {
|
||||
TResponse? responseData = default(TResponse);
|
||||
try {
|
||||
|
@ -231,24 +318,14 @@ namespace Neighbourhood.omg.lol
|
|||
|
||||
public async Task<BasicResponseData?> PostProfile(string address, PostProfile data) =>
|
||||
await Post<BasicResponseData, PostProfile>($"/address/{address}/web", data);
|
||||
public async Task<Dictionary<string, Theme>?> GetThemes() =>
|
||||
(await Get<ThemeResponseData>($"/theme/list"))?.Themes;
|
||||
|
||||
public async Task<List<MarkupString>> EphemeralScrape() {
|
||||
List<string> notes = new List<string>();
|
||||
Uri Uri = new Uri($"https://eph.emer.al/");
|
||||
try {
|
||||
var response = await _client.GetAsync(Uri);
|
||||
var str = await response.Content.ReadAsStringAsync();
|
||||
string pattern = @"<p class=""post"">(.*?)<\/p>";
|
||||
var matches = Regex.Matches(str, pattern, RegexOptions.IgnoreCase | RegexOptions.Singleline);
|
||||
foreach (Match match in matches) {
|
||||
notes.Add(match.Groups[1].Value);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Debug.WriteLine(ex);
|
||||
}
|
||||
return notes.Select(s => (MarkupString)s).ToList();
|
||||
}
|
||||
public async Task<MarkupString?> GetThemePreview(string theme) =>
|
||||
(MarkupString)((await Get<ThemePreviewResponseData>($"/theme/{theme}/preview"))?.Html ?? string.Empty);
|
||||
|
||||
public async Task<BasicResponseData?> PostProfilePic(string address, FileResult image) =>
|
||||
await PostBinary<BasicResponseData, object>($"/address/{address}/pfp", fileResult: image);
|
||||
|
||||
public async Task<string?> OAuth(string code, string client_id, string client_secret, string redirect_uri) {
|
||||
string? token = null;
|
||||
|
|
|
@ -26,6 +26,8 @@ namespace Neighbourhood.omg.lol {
|
|||
|
||||
public List<StatusOrPic>? Feed { get; set; }
|
||||
|
||||
public Dictionary<string, Theme>? Themes { get; set; }
|
||||
|
||||
// Account data
|
||||
private AccountResponseData? _accountInfo;
|
||||
public AccountResponseData? AccountInfo {
|
||||
|
@ -305,5 +307,12 @@ namespace Neighbourhood.omg.lol {
|
|||
return Feed.OrderByDescending(s => s.CreatedTime);
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, Theme>?> GetThemes(bool forceRefresh = false) {
|
||||
if (forceRefresh || this.Themes == null || this.Themes.Count == 0) {
|
||||
this.Themes = await api.GetThemes();
|
||||
}
|
||||
return this.Themes;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
117
Components/EditProfilePicDialog.razor
Normal file
117
Components/EditProfilePicDialog.razor
Normal file
|
@ -0,0 +1,117 @@
|
|||
@inject IJSRuntime JS
|
||||
@inject State State
|
||||
@inject RestService api
|
||||
@inject NavigationManager navigationManager
|
||||
|
||||
<div class="overlay @(Active ? "active" : string.Empty)" data-ui="#@id"></div>
|
||||
<dialog id="@id" class="@(Active ? "active" : string.Empty)" open="@Active">
|
||||
<h5>Update your profile picture</h5>
|
||||
<div class="padding center-align">
|
||||
<img src="@(Base64Url ?? ExistingUrl)" class="small-height square" />
|
||||
</div>
|
||||
<div class="row">
|
||||
<button @onclick="PicFromMedia"><i class="fa-solid fa-image"></i> Select a picture</button>
|
||||
<button @onclick="PicFromPhoto"><i class="fa-solid fa-camera"></i> Take a photo</button>
|
||||
</div>
|
||||
<nav class="right-align no-space">
|
||||
<button class="transparent link" data-ui="#@id" disabled="@loading">Cancel</button>
|
||||
<button @onclick="PostPic" disabled="@loading">
|
||||
@if (loading) {
|
||||
<span>Uploading...</span>
|
||||
}
|
||||
else {
|
||||
<i class="fa-solid fa-cloud-arrow-up"></i> <span>Upload</span>
|
||||
}
|
||||
</button>
|
||||
</nav>
|
||||
</dialog>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public string? Address { get; set; }
|
||||
public string ExistingUrl { get => $"https://profiles.cache.lol/{Address ?? ""}/picture"; }
|
||||
// private IBrowserFile? File { get; set; }
|
||||
[Parameter]
|
||||
public string? Base64File { get; set; }
|
||||
[Parameter]
|
||||
public long? FileSize { get; set; }
|
||||
[Parameter]
|
||||
public string? FileContentType { get; set; }
|
||||
[Parameter]
|
||||
public string? id { get; set; }
|
||||
[Parameter]
|
||||
public bool Active { get; set; }
|
||||
|
||||
private bool loading = false;
|
||||
|
||||
private FileResult? File { get; set; }
|
||||
private string? Base64Url {
|
||||
get {
|
||||
if (FileContentType == null || Base64File == null) return null;
|
||||
|
||||
return $"data:{FileContentType};base64,{Base64File}";
|
||||
}
|
||||
}
|
||||
|
||||
public async Task PostPic() {
|
||||
loading = true;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
//TODO: upload the profile pic
|
||||
//PutPicResponseData? response = await api.PutPic(State.SelectedAddressName!, Base64File!);
|
||||
if (Base64File != null && File != null)
|
||||
{
|
||||
// using var fileStream = await File.OpenReadAsync();
|
||||
BasicResponseData? response = await api.PostProfilePic(Address!, File);
|
||||
if (response != null)
|
||||
{
|
||||
await JS.InvokeVoidAsync("ui", "#" + id);
|
||||
// clear input
|
||||
File = null;
|
||||
Base64File = null;
|
||||
FileSize = null;
|
||||
FileContentType = null;
|
||||
await JS.InvokeVoidAsync("cacheBust", ExistingUrl);
|
||||
}
|
||||
loading = false;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
|
||||
private string formatSizeUnits(long? bytes) {
|
||||
if (bytes == null) return "?? bytes";
|
||||
string formatted = "0 bytes";
|
||||
if (bytes >= 1073741824) { formatted = $"{(bytes / 1073741824):.##} GB"; }
|
||||
else if (bytes >= 1048576) { formatted = $"{(bytes / 1048576):.##} MB"; }
|
||||
else if (bytes >= 1024) { formatted = $"{(bytes / 1024):.##} KB"; }
|
||||
else if (bytes > 1) { formatted = $"{bytes} bytes"; }
|
||||
else if (bytes == 1) { formatted = $"{bytes} byte"; }
|
||||
|
||||
return formatted;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private async Task PicFromMedia(EventArgs e) {
|
||||
File = await MediaPicker.Default.PickPhotoAsync();
|
||||
await PopulateFileDetails();
|
||||
}
|
||||
|
||||
private async Task PicFromPhoto(EventArgs e) {
|
||||
File = await MediaPicker.Default.CapturePhotoAsync();
|
||||
await PopulateFileDetails();
|
||||
}
|
||||
|
||||
private async Task PopulateFileDetails() {
|
||||
if (File == null) {
|
||||
FileContentType = null;
|
||||
FileSize = null;
|
||||
Base64File = null;
|
||||
}
|
||||
else {
|
||||
FileContentType = File.ContentType;
|
||||
FileSize = await Utilities.FileSize(File);
|
||||
Base64File = await Utilities.Base64FromFile(File);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,6 +10,8 @@
|
|||
public string? Url { get; set; }
|
||||
[Parameter]
|
||||
public string? id { get; set; }
|
||||
[Parameter]
|
||||
public string? SrcString { get; set; }
|
||||
public MarkupString? Html { get; set; }
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender) {
|
||||
|
@ -21,11 +23,18 @@
|
|||
public async Task Reload() {
|
||||
if (Url != null){
|
||||
Html = await api.GetHtml(Url);
|
||||
string? HtmlString = Html?.ToString();
|
||||
HtmlString = HtmlString?.Replace("</head>", "<base target='_blank'></head>");
|
||||
HtmlString = HtmlString?.Replace("</body>", "<script src='https://cdn.jsdelivr.net/npm/@iframe-resizer/child'></script></body>");
|
||||
Html = (MarkupString)(HtmlString ?? string.Empty);
|
||||
SrcString = Html?.ToString();
|
||||
}
|
||||
if(SrcString != null) {
|
||||
SrcString = SrcString?.Replace("</head>", "<base target='_blank'></head>");
|
||||
SrcString = SrcString?.Replace("</body>", "<script src='https://cdn.jsdelivr.net/npm/@iframe-resizer/child'></script></body>");
|
||||
Html = (MarkupString)(SrcString ?? string.Empty);
|
||||
}
|
||||
await InvokeAsync(StateHasChanged);
|
||||
await IframeResize();
|
||||
}
|
||||
|
||||
public async Task IframeResize() {
|
||||
await JS.InvokeVoidAsync("iframeResize", new { license = "GPLv3" });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,12 +9,6 @@
|
|||
<div class="row">
|
||||
<button @onclick="PicFromMedia"><i class="fa-solid fa-image"></i> Select a picture</button>
|
||||
<button @onclick="PicFromPhoto"><i class="fa-solid fa-camera"></i> Take a photo</button>
|
||||
@* <div class="field label prefix border">
|
||||
<i class="fa-solid fa-image"></i>
|
||||
<InputFile OnChange="@ChangeFile" accept="image/gif, image/heic, image/heif, image/jpeg, image/png, image/svg+xml, image/webp"></InputFile>
|
||||
<input type="text">
|
||||
<label>Select a picture</label>
|
||||
</div> *@
|
||||
</div>
|
||||
<div class="row">
|
||||
@if(Base64File != null && FileSize != null){
|
||||
|
|
|
@ -2,14 +2,32 @@
|
|||
@inject NavigationManager Nav
|
||||
@inject RestService api
|
||||
@inject State State
|
||||
|
||||
@inject IJSRuntime JS
|
||||
|
||||
<div class="max markdown-editor">
|
||||
<MarkdownEditor @ref="Editor"
|
||||
@bind-Value="@markdownValue"
|
||||
Theme="material-darker"
|
||||
MaxHeight="100%"
|
||||
/>
|
||||
@if (markdownValue != null)
|
||||
{
|
||||
<MarkdownEditor @ref="Editor"
|
||||
@bind-Value="@markdownValue"
|
||||
Theme="material-darker"
|
||||
MaxHeight="100%"
|
||||
CustomButtonClicked="@OnCustomButtonClicked">
|
||||
<Toolbar>
|
||||
<MarkdownToolbarButton Action="MarkdownAction.Bold" Icon="fa-solid fa-bold" Title="Bold" />
|
||||
<MarkdownToolbarButton Action="MarkdownAction.Italic" Icon="fa-solid fa-italic" Title="Italic" />
|
||||
<MarkdownToolbarButton Action="MarkdownAction.Heading" Icon="fa-solid fa-heading" Title="Heading" />
|
||||
<MarkdownToolbarButton Action="MarkdownAction.Code" Icon="fa-solid fa-code" Title="Code" Separator="true" />
|
||||
<MarkdownToolbarButton Action="MarkdownAction.Quote" Icon="fa-solid fa-quote-left" Title="Quote" />
|
||||
<MarkdownToolbarButton Action="MarkdownAction.UnorderedList" Icon="fa-solid fa-list-ul" Title="Unordered List" />
|
||||
<MarkdownToolbarButton Action="MarkdownAction.OrderedList" Icon="fa-solid fa-list-ol" Title="Ordered List" />
|
||||
<MarkdownToolbarButton Action="MarkdownAction.Link" Icon="fa-solid fa-link" Title="Link" Separator="true" />
|
||||
<MarkdownToolbarButton Action="MarkdownAction.Image" Icon="fa-solid fa-image" Title="Image" />
|
||||
<MarkdownToolbarButton Action="MarkdownAction.HorizontalRule" Icon="fa-solid fa-horizontal-rule" Title="Horizontal Rule" />
|
||||
<MarkdownToolbarButton Action="MarkdownAction.Guide" Icon="fa-solid fa-circle-question" Title="Guide" Separator="true" />
|
||||
<MarkdownToolbarButton Action="MarkdownAction.Custom" Icon="omg-icon omg-prami" Title="Editor Information" Name="Help" />
|
||||
</Toolbar>
|
||||
</MarkdownEditor>
|
||||
}
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
|
@ -34,7 +52,7 @@
|
|||
private bool listed;
|
||||
private string? markdownValue;
|
||||
|
||||
private bool loading = false;
|
||||
private bool loading = true;
|
||||
|
||||
protected override async Task OnInitializedAsync() {
|
||||
await base.OnInitializedAsync();
|
||||
|
@ -43,8 +61,13 @@
|
|||
{
|
||||
listed = data.Listed == 1;
|
||||
markdownValue = data.Content;
|
||||
|
||||
loading = false;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
await Editor!.SetValueAsync(markdownValue);
|
||||
}
|
||||
loading = false;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
|
@ -64,4 +87,10 @@
|
|||
|
||||
loading = false;
|
||||
}
|
||||
|
||||
public async Task OnCustomButtonClicked(MarkdownButtonEventArgs eventArgs) {
|
||||
if (eventArgs.Name == "Help") {
|
||||
await JS.InvokeVoidAsync("open", "https://home.omg.lol/info/editor", "_blank");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,46 +4,59 @@
|
|||
@inject State State
|
||||
@inject IJSRuntime JS
|
||||
|
||||
|
||||
<div class="max markdown-editor">
|
||||
<MarkdownEditor @ref="Editor"
|
||||
@bind-Value="@markdownValue"
|
||||
Theme="material-darker"
|
||||
MaxHeight="100%"
|
||||
CustomButtonClicked="@OnCustomButtonClicked">
|
||||
<Toolbar>
|
||||
<MarkdownToolbarButton Action="MarkdownAction.Bold" Icon="fa-solid fa-bold" Title="Bold" />
|
||||
<MarkdownToolbarButton Action="MarkdownAction.Italic" Icon="fa-solid fa-italic" Title="Italic" />
|
||||
<MarkdownToolbarButton Action="MarkdownAction.Heading" Icon="fa-solid fa-heading" Title="Heading" />
|
||||
<MarkdownToolbarButton Action="MarkdownAction.Code" Icon="fa-solid fa-code" Title="Code" Separator="true" />
|
||||
<MarkdownToolbarButton Action="MarkdownAction.Quote" Icon="fa-solid fa-quote-left" Title="Quote" />
|
||||
<MarkdownToolbarButton Action="MarkdownAction.UnorderedList" Icon="fa-solid fa-list-ul" Title="Unordered List" />
|
||||
<MarkdownToolbarButton Action="MarkdownAction.OrderedList" Icon="fa-solid fa-list-ol" Title="Ordered List" />
|
||||
<MarkdownToolbarButton Action="MarkdownAction.Link" Icon="fa-solid fa-link" Title="Link" Separator="true" />
|
||||
<MarkdownToolbarButton Action="MarkdownAction.Image" Icon="fa-solid fa-image" Title="Image" />
|
||||
<MarkdownToolbarButton Action="MarkdownAction.HorizontalRule" Icon="fa-solid fa-horizontal-rule" Title="Horizontal Rule" />
|
||||
<MarkdownToolbarButton Action="MarkdownAction.Guide" Icon="fa-solid fa-circle-question" Title="Guide" Separator="true" />
|
||||
<MarkdownToolbarButton Name="Help" Action="MarkdownAction.Custom"
|
||||
Icon="omg-icon omg-prami"
|
||||
Title="Editor Information" />
|
||||
</Toolbar>
|
||||
</MarkdownEditor>
|
||||
@if (markdownValue != null)
|
||||
{
|
||||
<MarkdownEditor @ref="Editor"
|
||||
@bind-Value="@markdownValue"
|
||||
Theme="material-darker"
|
||||
MaxHeight="100%"
|
||||
CustomButtonClicked="@OnCustomButtonClicked">
|
||||
<Toolbar>
|
||||
<MarkdownToolbarButton Action="MarkdownAction.Bold" Icon="fa-solid fa-bold" Title="Bold" />
|
||||
<MarkdownToolbarButton Action="MarkdownAction.Italic" Icon="fa-solid fa-italic" Title="Italic" />
|
||||
<MarkdownToolbarButton Action="MarkdownAction.Heading" Icon="fa-solid fa-heading" Title="Heading" />
|
||||
<MarkdownToolbarButton Action="MarkdownAction.Code" Icon="fa-solid fa-code" Title="Code" Separator="true" />
|
||||
<MarkdownToolbarButton Action="MarkdownAction.Quote" Icon="fa-solid fa-quote-left" Title="Quote" />
|
||||
<MarkdownToolbarButton Action="MarkdownAction.UnorderedList" Icon="fa-solid fa-list-ul" Title="Unordered List" />
|
||||
<MarkdownToolbarButton Action="MarkdownAction.OrderedList" Icon="fa-solid fa-list-ol" Title="Ordered List" />
|
||||
<MarkdownToolbarButton Action="MarkdownAction.Link" Icon="fa-solid fa-link" Title="Link" Separator="true" />
|
||||
<MarkdownToolbarButton Action="MarkdownAction.Image" Icon="fa-solid fa-image" Title="Image" />
|
||||
<MarkdownToolbarButton Action="MarkdownAction.HorizontalRule" Icon="fa-solid fa-horizontal-rule" Title="Horizontal Rule" />
|
||||
<MarkdownToolbarButton Action="MarkdownAction.Guide" Icon="fa-solid fa-circle-question" Title="Guide" Separator="true" />
|
||||
<MarkdownToolbarButton Action="MarkdownAction.Custom" Icon="omg-icon omg-prami" Title="Editor Information" Name="Help" />
|
||||
</Toolbar>
|
||||
</MarkdownEditor>
|
||||
}
|
||||
</div>
|
||||
|
||||
<details id="advanced">
|
||||
<summary> Advanced </summary>
|
||||
<small>Style you include here will be places in a <style> element in your page’s <head>.</small>
|
||||
<div class="field textarea label border max">
|
||||
<InputTextArea @bind-Value="css"></InputTextArea>
|
||||
<label>Custom CSS</label>
|
||||
</div>
|
||||
<small>Anything you put here will be included in your page’s <head> element.</small>
|
||||
<div class="field textarea label border max">
|
||||
<InputTextArea @bind-Value="head"></InputTextArea>
|
||||
<label>Additional <head> Content</label>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
@if (markdownValue != null)
|
||||
{
|
||||
<details id="advanced">
|
||||
<summary> Advanced </summary>
|
||||
<h5>Theme:</h5>
|
||||
<div class="row bottom-margin">
|
||||
<ThemeDialog id="theme-modal" onthemechanged="ThemeChanged"></ThemeDialog>
|
||||
<a data-ui="#theme-modal" class="row min" style="text-decoration:none;">
|
||||
@if(selectedTheme != null) {
|
||||
<ThemeCard theme="selectedTheme"></ThemeCard>
|
||||
}
|
||||
else {
|
||||
<button>Choose a theme</button>
|
||||
}
|
||||
</a>
|
||||
</div>
|
||||
<small>Style you include here will be places in a <style> element in your page’s <head>.</small>
|
||||
<div class="field textarea label border max">
|
||||
<InputTextArea @bind-Value="css"></InputTextArea>
|
||||
<label>Custom CSS</label>
|
||||
</div>
|
||||
<small>Anything you put here will be included in your page’s <head> element.</small>
|
||||
<div class="field textarea label border max">
|
||||
<InputTextArea @bind-Value="head"></InputTextArea>
|
||||
<label>Additional <head> Content</label>
|
||||
</div>
|
||||
</details>
|
||||
}
|
||||
<nav>
|
||||
<div class="max"></div>
|
||||
<button class="transparent link" onclick="history.back();" disabled="@loading">Cancel</button>
|
||||
|
@ -62,8 +75,11 @@
|
|||
private string? markdownValue;
|
||||
private string? css;
|
||||
private string? head;
|
||||
private string? theme;
|
||||
private Theme? selectedTheme;
|
||||
private Dictionary<string, Theme>? themes;
|
||||
|
||||
private bool loading = false;
|
||||
private bool loading = true;
|
||||
|
||||
protected override async Task OnInitializedAsync() {
|
||||
await base.OnInitializedAsync();
|
||||
|
@ -72,9 +88,17 @@
|
|||
markdownValue = data.Content;
|
||||
css = data.Css;
|
||||
head = data.Head;
|
||||
theme = data.Theme;
|
||||
|
||||
themes = await State.GetThemes();
|
||||
selectedTheme = themes?[theme];
|
||||
|
||||
loading = false;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
await Editor!.SetValueAsync(markdownValue);
|
||||
}
|
||||
loading = false;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
|
@ -89,7 +113,8 @@
|
|||
new PostProfile() {
|
||||
Content = markdownValue ?? string.Empty,
|
||||
Css = string.IsNullOrEmpty(css) ? null : css,
|
||||
Head = string.IsNullOrEmpty(head) ? null : head
|
||||
Head = string.IsNullOrEmpty(head) ? null : head,
|
||||
Theme = string.IsNullOrEmpty(theme) ? null : theme
|
||||
});
|
||||
if (result != null) {
|
||||
await State.RefreshNow();
|
||||
|
@ -98,6 +123,7 @@
|
|||
}
|
||||
|
||||
loading = false;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
public async Task OnCustomButtonClicked(MarkdownButtonEventArgs eventArgs) {
|
||||
|
@ -105,4 +131,10 @@
|
|||
await JS.InvokeVoidAsync("open", "https://home.omg.lol/info/editor", "_blank");
|
||||
}
|
||||
}
|
||||
|
||||
public void ThemeChanged(Theme? _theme) {
|
||||
theme = _theme?.Id;
|
||||
selectedTheme = _theme;
|
||||
InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,13 @@
|
|||
<h3 class="page-heading"><i class="fa-solid fa-fw fa-at"></i>@Address</h3>
|
||||
</div>
|
||||
<div class="row center-align">
|
||||
<img class="profile avatar" src="https://profiles.cache.lol/@Address/picture" alt="@Address" />
|
||||
<div class="min">
|
||||
@if (IsMe) {
|
||||
<EditProfilePicDialog id="profile-pic" Address="@Address"></EditProfilePicDialog>
|
||||
<button data-ui="#profile-pic" class="small circle small-elevate absolute top right no-margin" style="z-index:1;"><i class="fa-solid fa-pencil"></i></button>
|
||||
}
|
||||
<img class="profile avatar" src="https://profiles.cache.lol/@Address/picture" alt="@Address" />
|
||||
</div>
|
||||
</div>
|
||||
@if (FeatureFlags.Following) {
|
||||
<div class="row center-align">
|
||||
|
@ -76,7 +82,7 @@
|
|||
<StatusList @ref="StatusList" StatusFunc="@(async(refresh) => await State.GetStatuses(Address, refresh))" Editable="@IsMe"></StatusList>
|
||||
@if(IsMe) {
|
||||
<button class="fab circle extra large-elevate" data-ui="#post-modal">
|
||||
<i class="fa-solid fa-pen-to-square"></i>
|
||||
<i class="fa-solid fa-message-plus square"></i>
|
||||
</button>
|
||||
<NewStatusDialog id="post-modal"></NewStatusDialog>
|
||||
}
|
||||
|
@ -131,7 +137,7 @@
|
|||
private PicList? PicList { get; set; }
|
||||
|
||||
private bool IsMe {
|
||||
get => Address == State.SelectedAddressName;
|
||||
get => State.AddressList?.Any(a => a.Address == Address) ?? false;
|
||||
}
|
||||
|
||||
private MarkupString? bio;
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<AuthorizeView>
|
||||
<Authorized>
|
||||
<button class="fab circle extra large-elevate" data-ui="#post-modal">
|
||||
<i class="fa-solid fa-pen-to-square"></i>
|
||||
<i class="fa-solid fa-message-plus square"></i>
|
||||
</button>
|
||||
<NewStatusDialog id="post-modal" Active="true" Content="@Text"></NewStatusDialog>
|
||||
</Authorized>
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<AuthorizeView>
|
||||
<Authorized>
|
||||
<button class="fab circle extra large-elevate" data-ui="#post-modal">
|
||||
<i class="fa-solid fa-pen-to-square"></i>
|
||||
<i class="fa-solid fa-message-plus square"></i>
|
||||
</button>
|
||||
<NewStatusDialog id="post-modal"></NewStatusDialog>
|
||||
</Authorized>
|
||||
|
|
11
Components/ThemeCard.razor
Normal file
11
Components/ThemeCard.razor
Normal file
|
@ -0,0 +1,11 @@
|
|||
<article class="theme" style="@theme?.PreviewCssData?.BackgroundCss ; @theme?.PreviewCssData?.TextCss">
|
||||
<h5 class="address">@theme?.Name</h5>
|
||||
<p class="small theme-author" style="@theme?.PreviewCssData?.LinkCss">
|
||||
<i class="fa-solid fa-palette" style="@theme?.PreviewCssData?.IconCss"></i> by @theme?.Author
|
||||
</p>
|
||||
</article>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public Theme? theme { get; set; }
|
||||
}
|
82
Components/ThemeDialog.razor
Normal file
82
Components/ThemeDialog.razor
Normal file
|
@ -0,0 +1,82 @@
|
|||
@inject IJSRuntime JS
|
||||
@inject State State
|
||||
@inject RestService api
|
||||
@inject NavigationManager navigationManager
|
||||
|
||||
<div class="overlay @(Active ? "active" : string.Empty)" data-ui="#@id"></div>
|
||||
<dialog id="@id" class="@(Active ? "active" : string.Empty)" open="@Active" style="overflow:auto;">
|
||||
<h5>Choose a theme</h5>
|
||||
<nav class="wrap max">
|
||||
@if(themes != null) foreach(Theme theme in themes.Values) {
|
||||
<a onclick="@(() => ClickTheme(theme))" class="min" style="text-decoration:none;">
|
||||
<ThemeCard theme="@theme"></ThemeCard>
|
||||
</a>
|
||||
}
|
||||
</nav>
|
||||
<nav class="right-align no-space">
|
||||
<button class="transparent link" data-ui="#@id">Cancel</button>
|
||||
</nav>
|
||||
</dialog>
|
||||
|
||||
<div class="overlay" data-ui="#@previewId"></div>
|
||||
<dialog id="@previewId" style="overflow:auto;">
|
||||
<h5 class="address">@activeTheme?.Name</h5>
|
||||
<div class="max">
|
||||
<p>@((MarkupString)(activeTheme?.Description ?? string.Empty)) A theme by <a href="@activeTheme?.AuthorUrl" target="_blank">@activeTheme?.Author</a>.</p>
|
||||
@if(themePreview != null) {
|
||||
<ExternalPageComponent id="profile_page" @ref="iframe" SrcString="@themePreview.ToString()"></ExternalPageComponent>
|
||||
}
|
||||
</div>
|
||||
<nav class="right-align no-space">
|
||||
<button class="transparent link" @onclick="CancelPreview">Back</button>
|
||||
<button @onclick=UseTheme><i class="fa-solid fa-palette"></i> Use the @activeTheme?.Name theme</button>
|
||||
</nav>
|
||||
</dialog>
|
||||
|
||||
@code {
|
||||
private Dictionary<string, Theme>? themes;
|
||||
[Parameter]
|
||||
public string? id { get; set; }
|
||||
private string? previewId { get => $"{id}-preview"; }
|
||||
[Parameter]
|
||||
public bool Active { get; set; }
|
||||
[Parameter]
|
||||
public Action<Theme?>? onthemechanged { get; set; }
|
||||
|
||||
private Theme? activeTheme { get; set; }
|
||||
private MarkupString? themePreview { get; set; }
|
||||
private ExternalPageComponent iframe { get; set; }
|
||||
|
||||
protected override async Task OnInitializedAsync() {
|
||||
await base.OnInitializedAsync();
|
||||
activeTheme = null;
|
||||
themes = await State.GetThemes();
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
public async Task ClickTheme(Theme theme) {
|
||||
activeTheme = theme;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
await JS.InvokeVoidAsync("ui", "#" + id);
|
||||
await JS.InvokeVoidAsync("ui", "#" + previewId);
|
||||
themePreview = await api.GetThemePreview(theme.Id);
|
||||
await InvokeAsync(StateHasChanged);
|
||||
@* iframe.SrcString = themePreview.ToString(); *@
|
||||
await iframe.Reload();
|
||||
}
|
||||
|
||||
public async Task CancelPreview() {
|
||||
activeTheme = null;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
await JS.InvokeVoidAsync("ui", "#" + previewId);
|
||||
await JS.InvokeVoidAsync("ui", "#" + id);
|
||||
}
|
||||
|
||||
public async Task UseTheme() {
|
||||
// todo: update theme
|
||||
onthemechanged?.Invoke(activeTheme);
|
||||
activeTheme = null;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
await JS.InvokeVoidAsync("ui", "#" + previewId);
|
||||
}
|
||||
}
|
|
@ -1,10 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
public class AddressResponseData : IOmgLolResponseData {
|
||||
public string Address { get; set; } = string.Empty;
|
||||
public string Message { get; set; } = string.Empty;
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
public class AddressResponseList : List<AddressResponseData>, IOmgLolResponseList<AddressResponseData> {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
public class BasicResponseData : IOmgLolResponseData {
|
||||
public string Message { get; set; } = string.Empty;
|
||||
}
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
public class DirectoryResponseData : IOmgLolResponseData {
|
||||
public string Message { get; set; } = string.Empty;
|
||||
public string Url { get; set; } = string.Empty;
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
public class EphemeralData {
|
||||
public string Content { get; set; } = string.Empty;
|
||||
}
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
public class EphemeralResponseData : IOmgLolResponseData {
|
||||
public string Message { get; set; } = string.Empty;
|
||||
public List<string> Content { get; set; } = new List<string>();
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
public interface IOmgLolResponseList<T> : IList<T>, IOmgLolResponseData where T : IOmgLolResponseData {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
public class NowContentData {
|
||||
public string? Content { get; set; }
|
||||
public long? Updated { get; set; }
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
public class NowPageResponseData : IOmgLolResponseData {
|
||||
public string Message { get; set; } = string.Empty;
|
||||
public NowContentData? Now { get; set; }
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
public class NowResponseData : IOmgLolResponseData {
|
||||
public string Message { get; set; } = string.Empty;
|
||||
public long Count { get; set; }
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
public class PatchStatus {
|
||||
public string Id { get; set; } = string.Empty;
|
||||
public string Content { get; set; } = string.Empty;
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
public class PatchStatusResponseData : IOmgLolResponseData {
|
||||
public string Message { get; set; } = string.Empty;
|
||||
public string Id { get; set; } = string.Empty;
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
public class PostPic {
|
||||
public string? Description { get; set; }
|
||||
}
|
||||
|
|
|
@ -1,13 +1,8 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
public class PostProfile {
|
||||
public string Content { get; set; } = string.Empty;
|
||||
public bool Publish { get; set; } = true;
|
||||
public string? Theme { get; set; }
|
||||
public string? Css { get; set; }
|
||||
public string? Head { get; set; }
|
||||
}
|
||||
|
|
8
Models/API/PreviewCssData.cs
Normal file
8
Models/API/PreviewCssData.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Neighbourhood.omg.lol.Models {
|
||||
public class PreviewCssData {
|
||||
public string BackgroundCss { get; set; } = string.Empty;
|
||||
public string TextCss { get; set; } = string.Empty;
|
||||
public string LinkCss { get; set; } = string.Empty;
|
||||
public string IconCss { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
|
@ -1,21 +1,16 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
public class ProfileResponseData: IOmgLolResponseData {
|
||||
public string Message { get; set; } = string.Empty;
|
||||
public string Content { get; set; } = string.Empty;
|
||||
public string Type { get; set; } = string.Empty;
|
||||
public string Theme { get; set; } = string.Empty;
|
||||
public string Css { get; set; } = string.Empty;
|
||||
public string Head { get; set; } = string.Empty;
|
||||
public string Verified { get; set; } = string.Empty;
|
||||
public string? Css { get; set; }
|
||||
public string? Head { get; set; }
|
||||
public short Verified { get; set; }
|
||||
public string Pfp { get; set; } = string.Empty;
|
||||
public string Metadata { get; set; } = string.Empty;
|
||||
public string Branding { get; set; } = string.Empty;
|
||||
public string? Modified { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
public class PutPic {
|
||||
public string Pic { get; set; } = string.Empty;
|
||||
}
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
public class PutPicResponseData : IOmgLolResponseData {
|
||||
public string Message { get; set; } = string.Empty;
|
||||
public string Id { get; set; } = string.Empty;
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
public class SomePicsResponseData : IOmgLolResponseData {
|
||||
public string Message { get; set; } = string.Empty;
|
||||
public List<Pic>? Pics { get; set; }
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
public class StatusBioResponseData : IOmgLolResponseData {
|
||||
public string Message { get; set; } = string.Empty;
|
||||
public string Bio { get; set; } = string.Empty;
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
public class StatusPost {
|
||||
public string? Emoji { get; set; }
|
||||
public string? Content { get; set; }
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
public class StatusPostResponseData : IOmgLolResponseData {
|
||||
public string? Message { get; set; }
|
||||
public string? Id { get; set; }
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
public class StatusResponseData : IOmgLolResponseData {
|
||||
public string Message { get; set; } = string.Empty;
|
||||
public List<Status> Statuses { get; set; } = new List<Status>();
|
||||
|
|
6
Models/API/ThemePreviewResponseData.cs
Normal file
6
Models/API/ThemePreviewResponseData.cs
Normal file
|
@ -0,0 +1,6 @@
|
|||
namespace Neighbourhood.omg.lol.Models {
|
||||
public class ThemePreviewResponseData : IOmgLolResponseData {
|
||||
public string Message { get; set; } = string.Empty;
|
||||
public string Html { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
6
Models/API/ThemeResponseData.cs
Normal file
6
Models/API/ThemeResponseData.cs
Normal file
|
@ -0,0 +1,6 @@
|
|||
namespace Neighbourhood.omg.lol.Models {
|
||||
public class ThemeResponseData : IOmgLolResponseData {
|
||||
public string Message { get; set; } = string.Empty;
|
||||
public Dictionary<string, Theme> Themes { get; set; } = new Dictionary<string, Theme>();
|
||||
}
|
||||
}
|
|
@ -1,10 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
public class StatusOrPic {
|
||||
public Status? Status { get; set; }
|
||||
public Pic? Pic { get; set; }
|
||||
|
|
19
Models/Theme.cs
Normal file
19
Models/Theme.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
namespace Neighbourhood.omg.lol.Models {
|
||||
public class Theme {
|
||||
public string Id { get; set; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Created { get; set; } = string.Empty;
|
||||
public string Updated { get; set; } = string.Empty;
|
||||
public string Author { get; set; } = string.Empty;
|
||||
public string AuthorUrl { get; set; } = string.Empty;
|
||||
public string Version { get; set; } = string.Empty;
|
||||
public string License { get; set; } = string.Empty;
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public string PreviewCss { get; set; } = string.Empty;
|
||||
public string? SampleProfile { get; set; } = string.Empty;
|
||||
|
||||
public PreviewCssData? PreviewCssData {
|
||||
get => new RestService().Deserialize<PreviewCssData>(this.PreviewCss);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
public class TimeData {
|
||||
public long? UnixEpochTime { get; set; }
|
||||
public string? Iso8601Time { get; set; }
|
||||
|
|
|
@ -103,7 +103,7 @@
|
|||
|
||||
<!-- Images -->
|
||||
<MauiImage Include="Resources\Images\*" />
|
||||
<MauiImage Update="Resources\Images\dotnet_bot.svg" BaseSize="168,208" />
|
||||
<None Update="Resources\Images\dotnet_bot.svg" BaseSize="168,208" />
|
||||
|
||||
<!-- Custom Fonts -->
|
||||
<MauiFont Include="Resources\Fonts\*" />
|
||||
|
@ -117,22 +117,11 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<MauiFont Remove="Resources\Fonts\fa-brands-400.ttf" />
|
||||
<MauiFont Remove="Resources\Fonts\fa-brands-400.woff2" />
|
||||
<MauiFont Remove="Resources\Fonts\fa-duotone-900.ttf" />
|
||||
<MauiFont Remove="Resources\Fonts\fa-duotone-900.woff2" />
|
||||
<MauiFont Remove="Resources\Fonts\fa-light-300.ttf" />
|
||||
<MauiFont Remove="Resources\Fonts\fa-light-300.woff2" />
|
||||
<MauiFont Remove="Resources\Fonts\fa-regular-400.ttf" />
|
||||
<MauiFont Remove="Resources\Fonts\fa-regular-400.woff2" />
|
||||
<MauiFont Remove="Resources\Fonts\fa-solid-900.ttf" />
|
||||
<MauiFont Remove="Resources\Fonts\fa-solid-900.woff2" />
|
||||
<MauiFont Remove="Resources\Fonts\fa-thin-100.ttf" />
|
||||
<MauiFont Remove="Resources\Fonts\fa-thin-100.woff2" />
|
||||
<MauiFont Remove="Resources\Fonts\fa-v4compatibility.ttf" />
|
||||
<MauiFont Remove="Resources\Fonts\fa-v4compatibility.woff2" />
|
||||
<MauiFont Remove="Resources\Fonts\omg.lol-icons.woff2" />
|
||||
<MauiFont Remove="Resources\Fonts\seguiemj.ttf" />
|
||||
<MauiAsset Remove="Resources\Raw\AboutAssets.txt" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<MauiImage Remove="Resources\Images\dotnet_bot.svg" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -154,73 +143,6 @@
|
|||
<None Remove="Resources\Fonts\seguiemj.ttf" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Resources\Fonts\fa-brands-400.ttf">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Include="Resources\Fonts\fa-brands-400.woff2">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Include="Resources\Fonts\fa-duotone-900.ttf">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Include="Resources\Fonts\fa-duotone-900.woff2">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Include="Resources\Fonts\fa-light-300.ttf">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Include="Resources\Fonts\fa-light-300.woff2">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Include="Resources\Fonts\fa-regular-400.ttf">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Include="Resources\Fonts\fa-regular-400.woff2">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Include="Resources\Fonts\fa-solid-900.ttf">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Include="Resources\Fonts\fa-solid-900.woff2">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Include="Resources\Fonts\fa-thin-100.ttf">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Include="Resources\Fonts\fa-thin-100.woff2">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Include="Resources\Fonts\fa-v4compatibility.ttf">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Include="Resources\Fonts\fa-v4compatibility.woff2">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Include="Resources\Fonts\omg.lol-icons.woff2">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Include="Resources\Fonts\seguiemj.ttf">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="appsettings.json" />
|
||||
</ItemGroup>
|
||||
|
@ -240,6 +162,73 @@
|
|||
<PackageReference Include="PSC.Blazor.Components.MarkdownEditor" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<MauiFont Update="Resources\Fonts\fa-brands-400.ttf">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</MauiFont>
|
||||
<MauiFont Update="Resources\Fonts\fa-brands-400.woff2">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</MauiFont>
|
||||
<MauiFont Update="Resources\Fonts\fa-duotone-900.ttf">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</MauiFont>
|
||||
<MauiFont Update="Resources\Fonts\fa-duotone-900.woff2">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</MauiFont>
|
||||
<MauiFont Update="Resources\Fonts\fa-light-300.ttf">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</MauiFont>
|
||||
<MauiFont Update="Resources\Fonts\fa-light-300.woff2">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</MauiFont>
|
||||
<MauiFont Update="Resources\Fonts\fa-regular-400.ttf">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</MauiFont>
|
||||
<MauiFont Update="Resources\Fonts\fa-regular-400.woff2">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</MauiFont>
|
||||
<MauiFont Update="Resources\Fonts\fa-solid-900.ttf">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</MauiFont>
|
||||
<MauiFont Update="Resources\Fonts\fa-solid-900.woff2">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</MauiFont>
|
||||
<MauiFont Update="Resources\Fonts\fa-thin-100.ttf">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</MauiFont>
|
||||
<MauiFont Update="Resources\Fonts\fa-thin-100.woff2">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</MauiFont>
|
||||
<MauiFont Update="Resources\Fonts\fa-v4compatibility.ttf">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</MauiFont>
|
||||
<MauiFont Update="Resources\Fonts\fa-v4compatibility.woff2">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</MauiFont>
|
||||
<MauiFont Update="Resources\Fonts\omg.lol-icons.woff2">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</MauiFont>
|
||||
<MauiFont Update="Resources\Fonts\seguiemj.ttf">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</MauiFont>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<MauiXaml Update="XamlComponents\AppShell.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
|
|
|
@ -319,7 +319,7 @@ article.ephemeral {
|
|||
flex-direction:column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
#now.page > iframe {
|
||||
iframe {
|
||||
width:100%;
|
||||
flex-grow: 1;
|
||||
border: none;
|
||||
|
@ -501,4 +501,9 @@ article.now {
|
|||
background-color: #212121;
|
||||
color: #EEFFFF;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
article.theme {
|
||||
height: 10rem;
|
||||
width: 15rem;
|
||||
}
|
|
@ -28,4 +28,20 @@ function toggleDetails(id) {
|
|||
const element = document.getElementById(id)
|
||||
if (element instanceof HTMLDetailsElement)
|
||||
element.open = !element.open
|
||||
}
|
||||
|
||||
function cacheBust(url) {
|
||||
fetch(new Request(url), {
|
||||
headers: new Headers({
|
||||
"pragma": "no-cache",
|
||||
"cache-control": "no-cache"
|
||||
}),
|
||||
mode: 'no-cors',
|
||||
cache: 'no-cache',
|
||||
})
|
||||
.finally(() => {
|
||||
let els = document.querySelectorAll(`[src="${url}"]`)
|
||||
els.forEach(el => el.removeAttribute('src'))
|
||||
els.forEach(el => el.src = url)
|
||||
})
|
||||
}
|
Loading…
Reference in a new issue