Compare commits

...

14 commits
0.9.8 ... main

36 changed files with 640 additions and 81 deletions

View file

@ -0,0 +1,11 @@
#DONE update theme
<!--
order:-10
completed:2025-02-10T17:02:46+11:00
archived:true
archivedAt:2025-02-10T17:02:46+11:00
originalPath:Components\ThemeDialog.razor
originalLine:76
-->

View file

@ -0,0 +1,11 @@
#DONE upload the profile pic
<!--
order:0
completed:2025-02-10T16:51:21+11:00
archived:true
archivedAt:2025-02-10T16:51:21+11:00
originalPath:Components\EditProfilePicDialog.razor
originalLine:60
-->

61
.imdone/DONE/backlog.md Normal file
View file

@ -0,0 +1,61 @@
## Must Haves
- {check} View [latest statuslog entries](https://api.omg.lol/statuslog/latest)
- {check} View [all statuses of a single person](https://api.omg.lol/address/adam/statuses) (get [profile picture](https://profiles.cache.lol/adam/picture) and [statuslog bio](https://api.omg.lol/address/adam/statuses/bio)) Note: I'm calling this the profile page (even though omg.lol profile is a different thing)
- {check} [Log in](https://home.omg.lol/oauth/authorize?client_id=ea14dafd3e92cbcf93750c35cd81a031&scope=everything&redirect_uri=https://neatnik.net/adam/bucket/omgloloauth/&response_type=code) and [Authenticate](https://api.omg.lol/#token-get-oauth-exchange-an-authorization-code-for-an-access-token) (then [get all addresses](https://api.omg.lol/account/application/addresses) so we can pick one for other interactions)
- {check} Post a [new status](https://api.omg.lol/#token-post-statuslog-share-a-new-status) (checkbox for posting to mastodon)
- {check} Log out
- {check} Light/Dark themes (based on system theme)
## Should Haves
- {check} Share statuses, etc.
- {check} Have a character counter on statuses and a warning if going over length for posting to Mastodon.
- {check} Be a share target for creating statuses
- {check} View the [address directory](https://api.omg.lol/directory) (showing profile pics and linking to profile page)
- {check} Link to it via the account menu (There's not a lot of room in the nav)
- {check} View the [now garden](https://api.omg.lol/now/garden) (also, perhaps cache the now garden and link to the now page on a person's profile)
- {check} Updated profile page. Shows:
- {check} [profile picture](https://profiles.cache.lol/adam/picture)
- {check} [statuslog bio](https://api.omg.lol/address/adam/statuses/bio) text
- {check} [all statuses](https://api.omg.lol/address/adam/statuses)
- {check} Link to now page (if present in [now garden](https://api.omg.lol/now/garden))
- {check} Link to profile page (aka web page)
- {check} Link to person's some.pics
- {check} Link to person's pastebin
## Want to Haves
- {check} [Some.pics feed](https://api.omg.lol/pics) (plus seeing the some.pics of individuals, link on profile)
- {check} Be a share target for pictures
- {check} [Ephemeral feed](https://eph.emer.al/)
- {check} plus posting - ~~if/when an API becomes available~~ (Thanks Adam 😁)
- {check} Upload pics
- {check} Edit some.pics
- {check} delete pics
- {check} Edit statuses
- {check} delete statuses
- {check} Update / manage [now page](https://api.omg.lol/#now-page)
- {square} pull to refresh
- {check} Follow people (i.e. locally bookmark their statuslog profile)
- {check} A combined feed of all statuses and pics of everyone you're following
## Nice to Haves
- {check} Update profile picture
- {check} Update / manage statuslog bio
- {check} Update / manage [profile/web page](https://api.omg.lol/#web)
- {check} including [themes](https://api.omg.lol/#theme)
- {check} Update / manage [pastebin](https://api.omg.lol/#pastebin)
- {check} share and copy items
- {check} view as markup
- {check} visible in profile page
- {check} visible in feed
## Current Bugs
- {check} ~~Sharing to app multiple times throws an exception~~
- {check} ~~Need to update "Loading", "Logging in" and "nothing here" pages to match the splash screen (ish)~~
- {check} ~~Empty bio on person/statuses (just remove the div if the bio is empty)~~
- {check} ~~Need warnings on pics with no description~~
- {check} ~~respond appears on statuses with no external link~~
- {check} ~~statuses / pics don't refresh on update/delete~~
- {check} ~~own now page isn't showing properly in profile~~
- {check} ~~statuses with long words or urls won't wrap.~~
- {check} ~~Ephemeral scraping doesn't send a user agent string, so no longer works.~~

12
.imdone/DONE/test.md Normal file
View file

@ -0,0 +1,12 @@
#DONE test
<!--
created:2025-02-10T20:44:27+11:00
order:-20
completed:2025-02-10T20:44:57+11:00
archived:true
archivedAt:2025-02-10T20:44:57+11:00
originalPath:backlog.md
originalLine:64
-->

16
.imdone/actions/board.js Normal file
View file

@ -0,0 +1,16 @@
const path = require('path')
module.exports = function () {
const project = this.project
return [
{
title: "Open in vscode", // This is what displays in the main menu
keys: ['alt+o'], // This is the keyboard shortcut
icon: "code", // This is the font awesome icon that displays in the main menu
action (task) {
const url = `vscode://file/${path.join(project.path, task.path)}:${task.line}`
project.openUrl(url)
}
}
]
}

4
.imdone/actions/card.js Normal file
View file

@ -0,0 +1,4 @@
module.exports = function (task) {
const project = this.project
return []
}

101
.imdone/config.yml Normal file
View file

@ -0,0 +1,101 @@
keepEmptyPriority: false
languages:
.razor:
name: razor
symbol: "//"
block:
start: "@*"
end: "*@"
ignore: "*"
code:
include_lists:
- TODO
- DOING
- DONE
- PLANNING
- FIXME
- ARCHIVE
- HACK
- CHANGED
- XXX
- IDEA
- NOTE
- REVIEW
- WAITING
lists:
- name: NOTE
hidden: false
id: 9886o1muwm6yiizyq
- name: Past Due Reminders
hidden: true
ignore: false
filter: 'remind = /./ and remind < "${now}" and list != DONE -remind'
id: 9886o1muwm6yiizyr
- name: What's Due?
hidden: true
ignore: false
filter: 'dueDate < "${in 15 days}" AND list != DONE +dueDate +order'
id: 9886o1muwm6yiizys
- name: WAITING
hidden: false
ignore: false
id: 9886o10uwm6yovnxl
- name: TODO
hidden: false
id: 9886o1muwm6yiizyt
- name: DOING
hidden: false
id: 9886o1muwm6yiizyu
- name: DONE
hidden: false
ignore: true
id: 9886o1muwm6yiizyv
- name: Recently Completed
filter: 'completedDate > "${14 days ago}" -completed'
hidden: false
id: 9886o1muwm6yiizyw
settings:
'0': object Object
openIn: default
openCodeIn: default
journalType: Single File
journalPath: null
appendNewCardsTo: backlog.md
newCardSyntax: MARKDOWN
replaceSpacesWith: '-'
plugins: {}
journalTemplate: null
markdownOnly: false
kudosProbability: 0.33
views: []
name: Neighbourhood.omg.lol
cards:
colors:
- color: red
filter: tags = "BUG"
- color: black
filter: tags = "Someday"
- color: green
filter: tags = "WantToHave"
template: |
<!--
created:${timestamp}
-->
trackChanges: false
metaNewLine: true
addCompletedMeta: true
addCheckBoxTasks: false
doneList: DONE
tokenPrefix: '#'
taskPrefix: ''
tagPrefix: '#'
metaSep: ':'
orderMeta: true
maxLines: 6
addNewCardsToTop: true
showTagsAndMeta: false
defaultList: TODO
computed: !<tag:yaml.org,2002:js/undefined> ''
archiveCompleted: true
archiveFolder: .imdone/DONE

124
.imdone/properties/card.js Normal file
View file

@ -0,0 +1,124 @@
let updatedAt = new Date()
module.exports = function ({ line, source, totals }) {
const project = this.project
const emoji = {
due: dueEmoji(totals),
recent: recentEmoji(totals),
wip: wipEmoji(totals),
chart: EMOJI.CHART
}
// These are the properties that are available to use in your cards
// Use ${property_name} to permanently insert the value of the property
// Use {{property_name}} to insert the value of the property at runtime
return {
date: `${new Date().toISOString().substring(0, 10)}`,
sourceLink: `[${source.path}:${line}](${source.path}:${line})`,
cardTotal: cardTotal(totals),
allTopics: project.allTopics, // This is an array of all the topics in the project
topicTable: getTopicTable(project), // This is a markdown table with the count of tasks for each topic/list intersection
emoji,
icons
}
}
const icons = {
filter: `<span class="icon is-small fa-xs"><svg aria-hidden="true" focusable="false" data-prefix="fa" data-icon="search" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" class="svg-inline--fa fa-search fa-w-16"><path fill="currentColor" d="M505 442.7L405.3 343c-4.5-4.5-10.6-7-17-7H372c27.6-35.3 44-79.7 44-128C416 93.1 322.9 0 208 0S0 93.1 0 208s93.1 208 208 208c48.3 0 92.7-16.4 128-44v16.3c0 6.4 2.5 12.5 7 17l99.7 99.7c9.4 9.4 24.6 9.4 33.9 0l28.3-28.3c9.4-9.4 9.4-24.6.1-34zM208 336c-70.7 0-128-57.2-128-128 0-70.7 57.2-128 128-128 70.7 0 128 57.2 128 128 0 70.7-57.2 128-128 128z" class=""></path></svg></span><span data-v-fd981bec="" class="icon is-small fa-xs"><svg aria-hidden="true" focusable="false" data-prefix="fa" data-icon="chevron-down" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" class="svg-inline--fa fa-chevron-down fa-w-14"><path fill="currentColor" d="M207.029 381.476L12.686 187.132c-9.373-9.373-9.373-24.569 0-33.941l22.667-22.667c9.357-9.357 24.522-9.375 33.901-.04L224 284.505l154.745-154.021c9.379-9.335 24.544-9.317 33.901.04l22.667 22.667c9.373 9.373 9.373 24.569 0 33.941L240.971 381.476c-9.373 9.372-24.569 9.372-33.942 0z" class=""></path></svg></span>`
,openFile: `<span class="icon is-medium"><svg version="1.1" width="16" height="16" viewBox="0 0 16 16" aria-hidden="true" class="octicon octicon-link"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></span>`
,kebab: `<span class="icon is-medium"><svg version="1.1" width="3" height="16" viewBox="0 0 3 16" aria-hidden="true" class="octicon octicon-kebab-vertical"><path data-v-5bf4cb66="" fill-rule="evenodd" d="M0 2.5a1.5 1.5 0 1 0 3 0 1.5 1.5 0 0 0-3 0zm0 5a1.5 1.5 0 1 0 3 0 1.5 1.5 0 0 0-3 0zM1.5 14a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3z"></path></svg></span>`
,clone: `<span class="icon copy-button is-medium" style=""><svg aria-hidden="true" focusable="false" data-prefix="fa" data-icon="clone" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" class="svg-inline--fa fa-clone fa-w-16 fa-lg"><path fill="currentColor" d="M464 0c26.51 0 48 21.49 48 48v288c0 26.51-21.49 48-48 48H176c-26.51 0-48-21.49-48-48V48c0-26.51 21.49-48 48-48h288M176 416c-44.112 0-80-35.888-80-80V128H48c-26.51 0-48 21.49-48 48v288c0 26.51 21.49 48 48 48h288c26.51 0 48-21.49 48-48v-48H176z" class=""></path></svg></span>`
,editCard: `<span class="icon is-medium"><svg version="1.1" width="14" height="16" viewBox="0 0 14 16" aria-hidden="true" class="octicon octicon-pencil"><path fill-rule="evenodd" d="M0 12v3h3l8-8-3-3-8 8zm3 2H1v-2h1v1h1v1zm10.3-9.3L12 6 9 3l1.3-1.3a.996.996 0 0 1 1.41 0l1.59 1.59c.39.39.39 1.02 0 1.41z"></path></svg></span>`
}
const EMOJI = {
BAD: ':rotating_light:',
GREAT: ':rocket:',
SLEEP: ':sleeping:',
GOOD: ':2nd_place_medal:',
CHART: '<span style="font-size: 1.5em;">:chart:</span>'
}
function formatEmoji(emoji) {
return `<span style="font-size: 1.5em;">${emoji}</span>`
}
function dueEmoji(totals) {
const due = totals["What's Due?"]
let emoji = EMOJI.GOOD
if (due >= 3) {
emoji = EMOJI.BAD
} else if (due === 0) {
emoji = EMOJI.GREAT
}
return formatEmoji(emoji)
}
function recentEmoji(totals) {
const recentlyCompleted = totals['Recently Completed']
let emoji = EMOJI.GOOD
if (recentlyCompleted >= 3) {
emoji = EMOJI.GREAT
} else if (recentlyCompleted === 0) {
emoji = EMOJI.BAD
}
return formatEmoji(emoji)
}
function wipEmoji(totals) {
const doing = totals['DOING']
let emoji = EMOJI.GOOD
if (doing >= 3) {
emoji = EMOJI.BAD
} else if (doing === 0) {
emoji = EMOJI.SLEEP
} else if (doing === 1) {
emoji = EMOJI.GREAT
}
return formatEmoji(emoji)
}
function cardTotal(totals) {
let count = 0
Object.keys(totals).forEach((list) => {
count += totals[list]
})
return count
}
function getTopicTable(project) {
console.log('project.updatedAt', project.updatedAt)
console.log('updatedAt', updatedAt)
if (project.updatedAt < updatedAt) return ''
updatedAt = project.updatedAt
const lists = project.allLists.filter(list => !list.filter)
const topicTable = project.allTopics.map((topic) => {
return {
name: topic,
lists: [
...lists.map((list) => {
return {
name: list.name,
count: list.tasks.filter((task) => task.topics.includes(topic)).length
}
})
]
}
});
//convert topic table into a markdown table with topic name on the left and list names on the top and the count for each topic/list intersection
const table = `
| Topic | ${lists.map((list) => list.name).join(' | ')} |
| --- | ${lists.map(() => ' --- ').join(' | ')} |
${topicTable.map((topic) => {
const topicLink = `imdone://${project.path}?filter=topics="${encodeURIComponent(topic.name)}"`;
return `| [[${topic.name}]] | ${topic.lists.map((list) => `[${list.count}](${topicLink})`).join(' | ')} |`;
}).join('\n')}
`;
console.log(table);
return table
}

0
.imdone/style.css Normal file
View file

4
.imdone/tags.yml Normal file
View file

@ -0,0 +1,4 @@
tags:
- BUG
- Someday
- WantToHave

8
.imdoneignore Normal file
View file

@ -0,0 +1,8 @@
.vs
bin
obj
*.user
.imdone
Resources
.git
.vscode

View file

@ -1,13 +1,9 @@
using Microsoft.AspNetCore.Components;
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;
using System.Text.Json;
using System.Threading;
namespace Neighbourhood.omg.lol
{
@ -15,6 +11,7 @@ namespace Neighbourhood.omg.lol
HttpClient _client;
JsonSerializerOptions _serializerOptions;
public const string BaseUrl = "https://api.omg.lol";
private string? apiToken = null;
public ApiService(string? token = null) {
_client = new HttpClient();
@ -94,7 +91,7 @@ namespace Neighbourhood.omg.lol
/// <param name="file">A FileResult for the file to send in the body of the request as binary data</param>
/// <param name="cancellationToken">A cancellation token</param>
/// <returns>The returned data if successful, otherwise default</returns>
private async Task<TResponse?> Request<TResponse, TData>(string uri, HttpMethod method, TData? data = default, FileResult? file = null, CancellationToken cancellationToken = default)
private async Task<TResponse?> Request<TResponse, TData>(string uri, HttpMethod method, TData? data = default, FileResult? file = null, bool useAuthToken = true, CancellationToken cancellationToken = default)
where TResponse : IOmgLolResponseData
{
TResponse? responseData = default;
@ -118,6 +115,12 @@ namespace Neighbourhood.omg.lol
string json = JsonSerializer.Serialize(data, _serializerOptions);
request.Content = new StringContent(json, Encoding.UTF8, "application/json");
}
if(useAuthToken) {
if (apiToken == null) apiToken = Task.Run(() => SecureStorage.GetAsync("accounttoken")).GetAwaiter().GetResult();
if (apiToken != null) request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", apiToken);
}
HttpResponseMessage response = await _client.SendAsync(request, cancellationToken: cancellationToken);
responseData = await DecodeResponse<TResponse>(response, cancellationToken);
@ -131,9 +134,9 @@ namespace Neighbourhood.omg.lol
}
// GET request
private async Task<TResponse?> Get<TResponse>(string uri, CancellationToken cancellationToken = default)
private async Task<TResponse?> Get<TResponse>(string uri, bool useAuthToken = true, CancellationToken cancellationToken = default)
where TResponse : IOmgLolResponseData
=> await Request<TResponse, object>(uri, HttpMethod.Get, cancellationToken: cancellationToken);
=> await Request<TResponse, object>(uri, HttpMethod.Get, useAuthToken: useAuthToken, cancellationToken: cancellationToken);
// POST request
private async Task<TResponse?> Post<TResponse, TData>(string uri, TData data, CancellationToken cancellationToken = default)
@ -242,7 +245,9 @@ namespace Neighbourhood.omg.lol
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>();
(await Get<PastesResponseData>($"/address/{address}/pastebin", useAuthToken: false))?.Pastebin ?? new List<Paste>();
public async Task<List<Paste>> GetMyPastes(string address) =>
(await Get<PastesResponseData>($"/address/{address}/pastebin", useAuthToken: true))?.Pastebin ?? new List<Paste>();
public async Task<BasicResponseData?> DeletePaste(string address, string title) =>
await Delete<BasicResponseData>($"/address/{address}/pastebin/{title}");
@ -259,7 +264,7 @@ namespace Neighbourhood.omg.lol
/// <param name="token">The api token</param>
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);
if (token != null) apiToken = token;
}
/// <summary>

View file

@ -24,7 +24,7 @@ namespace Neighbourhood.omg.lol {
public List<MarkupString>? EphemeralMessages { get; set; }
public List<string>? AddressDirectory { get; set; }
public List<StatusOrPic>? Feed { get; set; }
public List<FeedItem>? Feed { get; set; }
public Dictionary<string, Theme>? Themes { get; set; }
@ -289,7 +289,12 @@ namespace Neighbourhood.omg.lol {
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>();
if (AddressNames?.Contains(address) ?? false) {
CachedAddressPastes = (await api.GetMyPastes(address)) ?? new List<Paste>();
}
else {
CachedAddressPastes = (await api.GetPastes(address)) ?? new List<Paste>();
}
}
return CachedAddressPastes;
}
@ -311,12 +316,13 @@ namespace Neighbourhood.omg.lol {
await GetPastes(SelectedAddressName, forceRefresh: true);
}
public async Task<IOrderedEnumerable<StatusOrPic>> GetFeed(bool forceRefresh = false) {
public async Task<IOrderedEnumerable<FeedItem>> GetFeed(bool forceRefresh = false) {
if(forceRefresh || Feed == null || Feed.Count == 0) {
Feed = new List<StatusOrPic>();
Feed = new List<FeedItem>();
foreach(string address in Following ?? new List<string>()) {
Feed.AddRange((await GetStatuses(address, forceRefresh))?.Select(s => new StatusOrPic { Status = s }) ?? new List<StatusOrPic>());
Feed.AddRange((await GetPics(address, forceRefresh))?.Select(p => new StatusOrPic { Pic = p }) ?? new List<StatusOrPic>());
Feed.AddRange((await GetStatuses(address, forceRefresh))?.Select(s => new FeedItem { Status = s }) ?? new List<FeedItem>());
Feed.AddRange((await GetPics(address, forceRefresh))?.Select(p => new FeedItem { Pic = p }) ?? new List<FeedItem>());
Feed.AddRange((await GetPastes(address, forceRefresh))?.Select(p => new FeedItem { Paste = p }) ?? new List<FeedItem>());
}
}
return Feed.OrderByDescending(s => s.CreatedTime);

View file

@ -124,6 +124,7 @@
Content = string.Empty;
Listed = false;
confirmDelete = false;
loading = false;
await InvokeAsync(StateHasChanged);
}

View file

@ -57,11 +57,8 @@
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)
{

View file

@ -34,6 +34,10 @@
<i class="fa-duotone fa-seedling"></i>
<span>/Now</span>
</a>
<a class="indent row" href="/person/@State.SelectedAddressName#pastebin">
<i class="fa-solid fa-clipboard"></i>
<span>Pastebin</span>
</a>
}
}

View file

@ -35,7 +35,7 @@
catch (Exception) { }
}
<li>
<a class="chip medium no-border no-margin" href="/person/@address">
<a class="chip medium no-border tiny-margin transparent" href="/person/@address">
<img class="circle avatar responsive" src="https://profiles.cache.lol/@linkAddress/picture">
<span>@displayAddress</span>
</a>

View file

@ -35,13 +35,16 @@ else {
<div class="responsive page-container">
<div id="feed" class="page no-padding active">
@if (feed != null){
foreach (StatusOrPic item in feed) {
foreach (FeedItem item in feed) {
if (item.IsStatus) {
<StatusCard Status="@item.Status"></StatusCard>
}
else if (item.IsPic) {
<PicCard Pic="@item.Pic"></PicCard>
}
else if (item.IsPaste) {
<PasteCard Paste="@item.Paste"></PasteCard>
}
}
}
<LoadingCard id="feedLoading" icon="fa-solid fa-list-timeline"></LoadingCard>
@ -75,7 +78,7 @@ else {
}
@code {
private IOrderedEnumerable<StatusOrPic>? feed;
private IOrderedEnumerable<FeedItem>? feed;
protected override async Task OnInitializedAsync() {
await base.OnInitializedAsync();

View file

@ -123,7 +123,7 @@
<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>
<i class="fa-solid fa-clipboard-medical"></i>
</button>
<EditPasteDialog id="paste-modal"></EditPasteDialog>
}

View file

@ -1,7 +1,13 @@
@inject IJSRuntime JS
@using CommunityToolkit.Maui.Alerts
@inject IJSRuntime JS
<article class="paste">
@* TODO: link to paste view *@
@* TODO link to paste view
* <!--
* order:-178.75
* -->
*@
<nav>
<h5 class="mono"><a href="/pastes/tbc">@Paste.Title</a></h5>
<div class="max"></div>
@ -13,12 +19,12 @@
{
<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" title="Copy to Clipboard" @onclick="() => CopyPaste()"><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>
<small class="nowrap chip no-border"><i class="fa-solid fa-clock tiny"></i> @Paste.RelativeTime</small>
@if(MarkupView){
<div class="padding">
@Utilities.MdToHtmlMarkup(Paste.Content)
@ -59,4 +65,12 @@
Subject = Paste!.Title
});
}
public async Task CopyPaste() {
if(Paste != null && !string.IsNullOrEmpty(Paste?.Content)) {
await Clipboard.Default.SetTextAsync(Paste?.Content);
var toast = Toast.Make("Copied to clipboard");
await toast.Show();
}
}
}

View file

@ -22,7 +22,10 @@
private List<Pic>? pics;
// TODO: There is a noticable rendering delay between the pics loading and the page rendering
//TODO There is a noticable rendering delay between the pics loading and the page rendering
// <!--
// order:-145
// -->
protected override async Task OnInitializedAsync() {
await base.OnInitializedAsync();
if (PicsFunc == null) return;

View file

@ -73,7 +73,6 @@
}
public async Task UseTheme() {
// todo: update theme
onthemechanged?.Invoke(activeTheme);
activeTheme = null;
await InvokeAsync(StateHasChanged);

View file

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Components.Authorization;
using CommunityToolkit.Maui;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
@ -7,7 +8,10 @@ namespace Neighbourhood.omg.lol {
public static MauiApp CreateMauiApp() {
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>();
.UseMauiApp<App>()
.UseMauiCommunityToolkit(options => {
options.SetShouldEnableSnackbarOnWindows(true);
});
builder.Services.AddMauiBlazorWebView();
builder.Services.AddTransient<LoginWebViewPage>();

View file

@ -4,6 +4,5 @@
public string Email { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public TimeData Created { get; set; } = TimeData.Empty;
//TODO: api_key and settings
}
}

View file

@ -1,11 +1,13 @@
namespace Neighbourhood.omg.lol.Models {
public class StatusOrPic {
public class FeedItem {
public Status? Status { get; set; }
public Pic? Pic { get; set; }
public Paste? Paste { get; set; }
public bool IsStatus { get => Status != null; }
public bool IsPic { get => Pic != null; }
public bool IsPaste { get => Paste != null; }
public DateTimeOffset? CreatedTime { get => Status?.CreatedTime ?? Pic?.CreatedTime; }
public DateTimeOffset? CreatedTime { get => Status?.CreatedTime ?? Pic?.CreatedTime ?? Paste?.ModifiedTime; }
}
}

View file

@ -43,22 +43,22 @@
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0-ios|AnyCPU'">
<ApplicationId>au.death.lol.omg.neighbourhood</ApplicationId>
<ApplicationDisplayVersion>0.9.7</ApplicationDisplayVersion>
<ApplicationVersion>7</ApplicationVersion>
<ApplicationDisplayVersion>0.9.9</ApplicationDisplayVersion>
<ApplicationVersion>9</ApplicationVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0-maccatalyst|AnyCPU'">
<ApplicationId>au.death.lol.omg.neighbourhood</ApplicationId>
<ApplicationDisplayVersion>0.9.7</ApplicationDisplayVersion>
<ApplicationVersion>7</ApplicationVersion>
<ApplicationDisplayVersion>0.9.9</ApplicationDisplayVersion>
<ApplicationVersion>9</ApplicationVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0-android34.0|AnyCPU'">
<ApplicationId>au.death.lol.omg.neighbourhood</ApplicationId>
<ApplicationDisplayVersion>0.9.7</ApplicationDisplayVersion>
<ApplicationDisplayVersion>0.9.9</ApplicationDisplayVersion>
<AndroidKeyStore>True</AndroidKeyStore>
<AndroidSigningKeyStore>D:\_assets\neighbourhood.omg.lol\neighbourhood.omg.lol.keystore</AndroidSigningKeyStore>
<ApplicationVersion>7</ApplicationVersion>
<ApplicationVersion>9</ApplicationVersion>
<AndroidSigningStorePass>a!zobzizl</AndroidSigningStorePass>
<AndroidSigningKeyAlias>neighbourhood.omg.lol</AndroidSigningKeyAlias>
<AndroidSigningKeyPass>a!zobzizl</AndroidSigningKeyPass>
@ -66,32 +66,32 @@
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0-windows10.0.19041.0|AnyCPU'">
<ApplicationId>au.death.lol.omg.neighbourhood</ApplicationId>
<ApplicationDisplayVersion>0.9.7</ApplicationDisplayVersion>
<ApplicationVersion>7</ApplicationVersion>
<ApplicationDisplayVersion>0.9.9</ApplicationDisplayVersion>
<ApplicationVersion>9</ApplicationVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0-ios|AnyCPU'">
<ApplicationId>au.death.lol.omg.neighbourhood</ApplicationId>
<ApplicationDisplayVersion>0.9.7</ApplicationDisplayVersion>
<ApplicationVersion>7</ApplicationVersion>
<ApplicationDisplayVersion>0.9.9</ApplicationDisplayVersion>
<ApplicationVersion>9</ApplicationVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0-maccatalyst|AnyCPU'">
<ApplicationId>au.death.lol.omg.neighbourhood</ApplicationId>
<ApplicationDisplayVersion>0.9.7</ApplicationDisplayVersion>
<ApplicationVersion>7</ApplicationVersion>
<ApplicationDisplayVersion>0.9.9</ApplicationDisplayVersion>
<ApplicationVersion>9</ApplicationVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0-android34.0|AnyCPU'">
<ApplicationId>au.death.lol.omg.neighbourhood</ApplicationId>
<ApplicationDisplayVersion>0.9.7</ApplicationDisplayVersion>
<ApplicationVersion>7</ApplicationVersion>
<ApplicationDisplayVersion>0.9.9</ApplicationDisplayVersion>
<ApplicationVersion>9</ApplicationVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0-windows10.0.19041.0|AnyCPU'">
<ApplicationId>au.death.lol.omg.neighbourhood</ApplicationId>
<ApplicationDisplayVersion>0.9.7</ApplicationDisplayVersion>
<ApplicationVersion>7</ApplicationVersion>
<ApplicationDisplayVersion>0.9.9</ApplicationDisplayVersion>
<ApplicationVersion>9</ApplicationVersion>
</PropertyGroup>
<ItemGroup>
@ -125,6 +125,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Maui" Version="9.0.2" />
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
<PackageReference Include="Markdig" Version="0.37.0" />
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="8.0.7" />
@ -137,7 +138,7 @@
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.0" />
<PackageReference Include="Microsoft.Maui.Essentials" Version="8.0.70" />
<PackageReference Include="PSC.Blazor.Components.MarkdownEditor" Version="8.0.4" />
<PackageReference Include="System.Text.Json" Version="8.0.4" />
<PackageReference Include="System.Text.Json" Version="8.0.5" />
</ItemGroup>
<ItemGroup>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="au.death.lol.omg.neighbourhood" android:versionCode="7" android:versionName="0.9.7">
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="au.death.lol.omg.neighbourhood" android:versionCode="9" android:versionName="0.9.9">
<application android:allowBackup="true" android:icon="@mipmap/icon_background" android:supportsRtl="true" android:label="omg.lol"></application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />

View file

@ -56,11 +56,15 @@ namespace Neighbourhood.omg.lol {
}
else if (intent.Type.Equals(Intent.ActionSendMultiple)) //Multiple files
{
// TODO: we don't really support this at the moment.
//System.Collections.IList? uriList;
//if (OperatingSystem.IsAndroidVersionAtLeast(33))
//NOTE we don't really support recieving multiple files from a share request at the moment.
// <!--
// order:0
// -->
// System.Collections.IList? uriList;
// if (OperatingSystem.IsAndroidVersionAtLeast(33))
// uriList = intent.GetParcelableArrayListExtra(Intent.ExtraStream, Java.Lang.Class.FromType(typeof(Android.Net.Uri)));
//else uriList = intent.GetParcelableArrayListExtra(Intent.ExtraStream);
// else uriList = intent.GetParcelableArrayListExtra(Intent.ExtraStream);
}
}
}

View file

@ -4,7 +4,9 @@
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
IgnorableNamespaces="uap rescap">
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
IgnorableNamespaces="uap rescap com desktop">
<Identity Name="maui-package-name-placeholder" Publisher="CN=User Name" Version="0.0.0.0" />
@ -36,6 +38,26 @@
<uap:DefaultTile Square71x71Logo="$placeholder$.png" Wide310x150Logo="$placeholder$.png" Square310x310Logo="$placeholder$.png" />
<uap:SplashScreen Image="$placeholder$.png" />
</uap:VisualElements>
<Extensions>
<!-- Specify which CLSID to activate when notification is clicked -->
<desktop:Extension Category="windows.toastNotificationActivation">
<desktop:ToastNotificationActivation ToastActivatorCLSID="6e919706-2634-4d97-a93c-2213b2acc334" />
</desktop:Extension>
<!-- Register COM CLSID -->
<com:Extension Category="windows.comServer">
<com:ComServer>
<com:ExeServer Executable="Neighbourhood.omg.lol\Neighbourhood.omg.lol.exe" DisplayName="$targetnametoken$" Arguments="----AppNotificationActivated:">
<!-- Example path to executable: CommunityToolkit.Maui.Sample\CommunityToolkit.Maui.Sample.exe -->
<com:Class Id="6e919706-2634-4d97-a93c-2213b2acc334" />
</com:ExeServer>
</com:ComServer>
</com:Extension>
</Extensions>
</Application>
</Applications>

View file

@ -29,7 +29,7 @@
<key>XSAppIconAssets</key>
<string>Assets.xcassets/icon.appiconset</string>
<key>CFBundleShortVersionString</key>
<string>0.9.7</string>
<string>0.9.9</string>
<key>MinimumOSVersion</key>
<string>14.2</string>
</dict>

63
backlog.md Normal file
View file

@ -0,0 +1,63 @@
- [Pull to refresh #WantToHave](#TODO:)
<!--
order:-177.5
-->
- [Be a share target for pastes? #WantToHave](#TODO:)
<!--
order:-171.25
-->
- [Update / manage [PURLs](https://api.omg.lol/#purls) #WantToHave](#TODO:)
<!--
order:-172.5
-->
- [Combined status / pics posting (upload pic in new status dialog and paste in link) #WantToHave](#TODO:)
<!--
order:-171.875
-->
- [Update / manage [Weblog](https://api.omg.lol/#weblog) (should probably wait for [Neato](https://neato.pub/)) #WantToHave](#WAITING:)
<!--
order:0
-->
- [Account settings #Someday](#TODO:)
<!--
order:-80
-->
- [Address preferences #Someday](#TODO:)
<!--
order:-90
-->
- [DNS Records #Someday](#TODO:)
<!--
order:-100
-->
- [Switchboard #Someday](#TODO:)
<!--
order:-110
-->
- [Email forwarding #Someday](#TODO:)
<!--
order:-120
-->
- [Keys #Someday](#TODO:)
<!--
order:-130
-->
- [Proofs #Someday](#TODO:)
<!--
order:-140
-->
- [Slow rendering, especially pics, due to large amount of data #BUG](#TODO:)
<!--
order:-150
-->
- [Sharing to app while the sharing page is already open does literally nothing #BUG](#TODO:)
- Not an issue? Only an issue when you have the app and the sharing app open side-by-side, which is rare?
<!--
order:-160
-->
- [Can't select svgs as pics. #BUG](#TODO:)
<!--
order:-170
-->

View file

@ -38,7 +38,6 @@
position: sticky;
top: 0;
height: env(safe-area-inset-top);
background-color: #f7f7f7;
width: 100%;
z-index: 1;
}

View file

@ -9,33 +9,99 @@ body.dark {
--on-background: var(--gray-1);
--surface: var(--gray-9);
--on-surface: var(--gray-4);
--on-surface-variant: var(--gray-6);
}
.author { color: inherit }
@media (prefers-color-scheme: light) {
body.dark {
--primary: #6750a4;
--on-primary: #ffffff;
--primary-container: #e9ddff;
--on-primary-container: #22005d;
--secondary: #625b71;
--on-secondary: #ffffff;
--secondary-container: var(--gray-4);
--on-secondary-container: #1e192b;
--tertiary: #7e5260;
--on-tertiary: #ffffff;
--tertiary-container: #ffd9e3;
--on-tertiary-container: #31101d;
--error: #ba1a1a;
--on-error: #ffffff;
--error-container: #ffdad6;
--on-error-container: #410002;
--background: var(--gray-0);
--on-background: var(--gray-8);
--surface: var(--gray-2);
--on-surface: var(--gray-9);
--surface-variant: var(--gray-1);
--on-surface-variant: var(--gray-7);
--outline: #7a757f;
--outline-variant: #cac4cf;
--shadow: #000000;
--scrim: #000000;
--inverse-surface: var(--gray-1);
--inverse-on-surface: var(--gray-9);
--inverse-primary: #cfbcff;
--surface-dim: #ddd8dd;
--surface-bright: #fdf8fd;
--surface-container-lowest: #ffffff;
--surface-container-low: var(--gray-3);
--surface-container: var(--gray-3);
--surface-container-high: #ece7eb;
--surface-container-highest: #e6e1e6;
--overlay: rgb(0 0 0 / .5);
--active: rgb(0 0 0 / .1);
--elevate1: 0 .125rem .125rem 0 rgb(0 0 0 / .32);
--elevate2: 0 .25rem .5rem 0 rgb(0 0 0 / .4);
--elevate3: 0 .375rem .75rem 0 rgb(0 0 0 / .48);
--secondary-container: var(--gray-4);
}
}
.status nav, .status nav .chip { color: var(--gray-7) }
body, nav:is(.left,.right) {
background-color: var(--background);
}
article {
background-color: var(--surface);
}
.author {
color: inherit
}
.status nav, .status nav .chip {
color: var(--gray-7)
}
.avatar::after {
background-color: var(--surface-container-low);
border: 1px dashed var(--gray-4)
background-color: var(--surface);
border: 1px dashed var(--on-surface)
}
article.ephemeral { border: 2px dashed var(--gray-7) }
article.ephemeral {
border: 2px dashed var(--outline)
}
article.now {
background-color: var(--green-2);
color: var(--black)
}
article.now {
background-color: var(--green-2);
color: var(--black)
}
a.row.indent { border-left: 1px solid var(--outline) }
a.row.indent {
border-left: 1px solid var(--outline)
}
.markdown-editor .editor-preview { background: var(--surface) }
.markdown-editor .editor-preview {
background: var(--surface)
}
menu > details > a:is(:hover,:focus,.active), menu > details > summary:is(:hover,:focus,.active) {
background-color: var(--active)
}
menu > details > a:is(:hover,:focus,.active), menu > details > summary:is(:hover,:focus,.active) {
background-color: var(--active)
}
#advanced :is(.field.textarea, textarea), .EasyMDEContainer, article.paste code {
background-color: #212121;
color: #eff
}
#advanced :is(.field.textarea, textarea), .EasyMDEContainer, article.paste code {
background-color: #212121;
color: #eff
}

View file

@ -4,10 +4,11 @@
.animated[data-emoji|=🫥] { content: url(/vendor/fluent-emoji/1fae5/animated.png) }
.fa-seedling { color: var(--green-9) !important }
.fa-message-smile { color: var(--blue-4) !important }
.fa-images { color: var(--yellow-6) !important }
.fa-id-card { color: var(--pink-4) !important }
.fa-message-smile { color: var(--blue-5) !important }
.fa-images { color: var(--orange-5) !important }
.fa-id-card { color: var(--red-5) !important }
.fa-comment-dots { color: var(--gray-6) !important }
.fa-clipboard { color: var(--violet-5) !important }
i.tiny { ---size: 1em }

View file

@ -12,6 +12,14 @@
--max-article-size: 75rem;
}
#app {
position: relative
}
nav:is(.left, .right){
margin-block-start: env(safe-area-inset-top);
}
main {
position: relative;
overflow-x: hidden;
@ -49,6 +57,10 @@ img {
max-inline-size: 10rem
}
:is(h1,h2,h3,h4,h5,h6):is(:focus-visible) {
outline:none;
}
#bio :is(h1,h2,h3,h4,h5,h6) {
text-align: center;
display: block
@ -239,4 +251,4 @@ a.row.indent { margin-left: 1rem }
.markdown-editor > .EasyMDEContainer > .CodeMirror {
flex: auto
}
}

View file

@ -4,7 +4,9 @@ nav header { z-index: 101 }
nav.bottom.s:not(.drawer) :is(button,.button) > menu { z-index: 100 }
.fab { z-index: 1 }
.fab {
z-index: 1
}
.avatar::after { z-index: 1 }