Compare commits
34 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b77aac8c57 | |||
| 518a42b20a | |||
| 023c077b2b | |||
| 4912ede42c | |||
| cf3931f529 | |||
| d142010786 | |||
| 8d3a454a6b | |||
| b089cb39e6 | |||
| 72c6b590f1 | |||
| b074b9d453 | |||
| 1fe82c18af | |||
| 04a5641650 | |||
| a25df4368e | |||
| d14ee65af4 | |||
| a02b14782b | |||
| 278811c2c2 | |||
| 85bc105ddd | |||
| 6e5b206c20 | |||
| 909038ff79 | |||
| cc779051bf | |||
| cee1c3dfd2 | |||
| 905257789b | |||
| e017269685 | |||
| 859f8348f0 | |||
| 1bf7e74a5a | |||
| 640cd73271 | |||
| bdea7870ea | |||
| 7b462e3cfc | |||
| 2dc66abcd7 | |||
| 3d8047c01c | |||
| f24ef392f7 | |||
| 0fcda98b9f | |||
| 25f362bfc5 | |||
| b42bf2023d |
134 changed files with 2278 additions and 12140 deletions
11
.imdone/DONE/Components/update-theme.md
Normal file
11
.imdone/DONE/Components/update-theme.md
Normal 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
|
||||
-->
|
||||
|
||||
|
||||
11
.imdone/DONE/Components/upload-the-profile-pic.md
Normal file
11
.imdone/DONE/Components/upload-the-profile-pic.md
Normal 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
61
.imdone/DONE/backlog.md
Normal 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
12
.imdone/DONE/test.md
Normal 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
16
.imdone/actions/board.js
Normal 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
4
.imdone/actions/card.js
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
module.exports = function (task) {
|
||||
const project = this.project
|
||||
return []
|
||||
}
|
||||
101
.imdone/config.yml
Normal file
101
.imdone/config.yml
Normal 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
124
.imdone/properties/card.js
Normal 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
0
.imdone/style.css
Normal file
4
.imdone/tags.yml
Normal file
4
.imdone/tags.yml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
tags:
|
||||
- BUG
|
||||
- Someday
|
||||
- WantToHave
|
||||
8
.imdoneignore
Normal file
8
.imdoneignore
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
.vs
|
||||
bin
|
||||
obj
|
||||
*.user
|
||||
.imdone
|
||||
Resources
|
||||
.git
|
||||
.vscode
|
||||
310
Classes/ApiService.cs
Normal file
310
Classes/ApiService.cs
Normal file
|
|
@ -0,0 +1,310 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
using Neighbourhood.omg.lol.Models;
|
||||
using System.Diagnostics;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Neighbourhood.omg.lol
|
||||
{
|
||||
public class ApiService {
|
||||
HttpClient _client;
|
||||
JsonSerializerOptions _serializerOptions;
|
||||
public const string BaseUrl = "https://api.omg.lol";
|
||||
private string? apiToken = null;
|
||||
|
||||
public ApiService(string? token = null) {
|
||||
_client = new HttpClient();
|
||||
_client.BaseAddress = new Uri(BaseUrl);
|
||||
_client.DefaultRequestHeaders.UserAgent.Add(new System.Net.Http.Headers.ProductInfoHeaderValue(App.Name, App.Version));
|
||||
_serializerOptions = new JsonSerializerOptions {
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||
#if DEBUG
|
||||
WriteIndented = true
|
||||
#else
|
||||
WriteIndented = false
|
||||
#endif
|
||||
};
|
||||
AddToken(token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserialize json convenience function with default serializer options
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type to deserialize</typeparam>
|
||||
/// <param name="str">The string to deserialize</param>
|
||||
/// <returns>The deserialized object if successful, otherwise default</returns>
|
||||
public T? Deserialize<T>(string str) {
|
||||
T? responseObj = default;
|
||||
try {
|
||||
responseObj = JsonSerializer.Deserialize<T>(str, _serializerOptions);
|
||||
}
|
||||
catch (JsonException ex) {
|
||||
Debug.WriteLine(@"\tERROR {0}", ex.Message);
|
||||
Debug.WriteLine(str);
|
||||
}
|
||||
return responseObj;
|
||||
}
|
||||
|
||||
#region Base Requests
|
||||
|
||||
/// <summary>
|
||||
/// Decode the response from an API call
|
||||
/// </summary>
|
||||
/// <typeparam name="TResponse">The type of response object we are trying to get</typeparam>
|
||||
/// <param name="response">The raw Http Response Message</param>
|
||||
/// <param name="cancellationToken">A cancellation token to cancel the operation</param>
|
||||
/// <returns>The decoded object if successfull, otherwise default</returns>
|
||||
private async Task<TResponse?> DecodeResponse<TResponse>(HttpResponseMessage response, CancellationToken cancellationToken = default)
|
||||
where TResponse : IOmgLolResponseData
|
||||
{
|
||||
TResponse? responseData = default;
|
||||
try {
|
||||
string str = await response.Content.ReadAsStringAsync();
|
||||
if (response.IsSuccessStatusCode) {
|
||||
OmgLolResponse<TResponse>? responseObj = Deserialize<OmgLolResponse<TResponse>>(str);
|
||||
if (responseObj?.Request == null || (responseObj?.Request?.Success ?? false)) {
|
||||
responseData = responseObj!.Response;
|
||||
}
|
||||
}
|
||||
else {
|
||||
OmgLolResponse<TResponse>? responseObj = Deserialize<OmgLolResponse<TResponse>>(str);
|
||||
throw responseObj == null ? new OmgLolApiException<TResponse>(str) : new OmgLolApiException<TResponse>(responseObj);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Debug.WriteLine(@"\tERROR {0}", ex.Message);
|
||||
}
|
||||
|
||||
return responseData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a request for the supplied uri, with the supplied Http Method,
|
||||
/// with the supplied data in the body (if present)
|
||||
/// </summary>
|
||||
/// <typeparam name="TResponse">The type of response we are expecting</typeparam>
|
||||
/// <typeparam name="TData">The type of data we are sending</typeparam>
|
||||
/// <param name="uri">The uri to request</param>
|
||||
/// <param name="method">The Http Method to use for the request</param>
|
||||
/// <param name="data">The data to send in the body of the request</param>
|
||||
/// <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, bool useAuthToken = true, CancellationToken cancellationToken = default)
|
||||
where TResponse : IOmgLolResponseData
|
||||
{
|
||||
TResponse? responseData = default;
|
||||
try {
|
||||
HttpRequestMessage request = new HttpRequestMessage(method, uri);
|
||||
Stream? fileStream = null;
|
||||
if (file != null) {
|
||||
// append "binary" query parameter (if not already present)
|
||||
Uri url = new Uri(_client.BaseAddress?.AbsoluteUri + uri);
|
||||
if (string.IsNullOrEmpty(url.Query)) uri += "?binary";
|
||||
else if (!url.Query.Contains("binary")) uri += "&binary";
|
||||
request = new HttpRequestMessage(method, uri);
|
||||
|
||||
fileStream = await file.OpenReadAsync();
|
||||
HttpContent fileStreamContent = new StreamContent(fileStream);
|
||||
fileStreamContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(file.ContentType ?? "application/octet-stream");
|
||||
fileStreamContent.Headers.ContentLength = fileStream.Length;
|
||||
request.Content = fileStreamContent;
|
||||
}
|
||||
else if (data != null) {
|
||||
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);
|
||||
|
||||
fileStream?.Dispose();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Debug.WriteLine(@"\tERROR {0}", ex.Message);
|
||||
}
|
||||
|
||||
return responseData;
|
||||
}
|
||||
|
||||
// GET request
|
||||
private async Task<TResponse?> Get<TResponse>(string uri, bool useAuthToken = true, CancellationToken cancellationToken = default)
|
||||
where TResponse : IOmgLolResponseData
|
||||
=> 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)
|
||||
where TResponse : IOmgLolResponseData
|
||||
=> await Request<TResponse, TData>(uri, HttpMethod.Post, data: data, cancellationToken: cancellationToken);
|
||||
|
||||
// POST request, but with a file as binary data
|
||||
private async Task<TResponse?> PostBinary<TResponse>(string uri, FileResult? fileResult = null, CancellationToken cancellationToken = default)
|
||||
where TResponse : IOmgLolResponseData
|
||||
=> await Request<TResponse, object>(uri, HttpMethod.Post, file: fileResult, cancellationToken: cancellationToken);
|
||||
|
||||
// PUT request
|
||||
private async Task<TResponse?> Put<TResponse, TData>(string uri, TData data, CancellationToken cancellationToken = default)
|
||||
where TResponse : IOmgLolResponseData
|
||||
=> await Request<TResponse, TData>(uri, HttpMethod.Put, data: data, cancellationToken: cancellationToken);
|
||||
|
||||
// PATCH request
|
||||
private async Task<TResponse?> Patch<TResponse, TData>(string uri, TData data, CancellationToken cancellationToken = default)
|
||||
where TResponse : IOmgLolResponseData
|
||||
=> await Request<TResponse, TData>(uri, HttpMethod.Patch, data: data, cancellationToken: cancellationToken);
|
||||
|
||||
// Delete request
|
||||
private async Task<TResponse?> Delete<TResponse>(string uri, CancellationToken cancellationToken = default)
|
||||
where TResponse : IOmgLolResponseData
|
||||
=> await Request<TResponse, object>(uri, HttpMethod.Delete, cancellationToken: cancellationToken);
|
||||
#endregion
|
||||
|
||||
#region Specific Requests
|
||||
public async Task<List<Status>> StatuslogLatest() =>
|
||||
(await Get<StatusResponseData>("/statuslog/latest"))?.Statuses ?? new List<Status>();
|
||||
|
||||
public async Task<List<Status>> Statuslog(string address) =>
|
||||
(await Get<StatusResponseData>($"/address/{address}/statuses"))?.Statuses ?? new List<Status>();
|
||||
|
||||
public async Task<string> StatuslogBio(string address) =>
|
||||
(await Get<StatusBioResponseData>($"/address/{address}/statuses/bio"))?.Bio ?? string.Empty;
|
||||
|
||||
public async Task<string> PostStatuslogBio(string address, string bio) =>
|
||||
(await Post<StatusBioResponseData, PostStatusBio>($"/address/{address}/statuses/bio", new PostStatusBio() { Content = bio }))?.Bio ?? string.Empty;
|
||||
|
||||
public async Task<AccountResponseData?> AccountInfo() =>
|
||||
await Get<AccountResponseData>("/account/application/info");
|
||||
|
||||
public async Task<AddressResponseList?> Addresses() =>
|
||||
await Get<AddressResponseList>("/account/application/addresses");
|
||||
|
||||
public async Task<StatusPostResponseData?> StatusPost(string address, StatusPost statusPost) =>
|
||||
await Post<StatusPostResponseData, StatusPost>($"/address/{address}/statuses", statusPost);
|
||||
|
||||
public async Task<List<Pic>> SomePics() =>
|
||||
(await Get<SomePicsResponseData>("/pics"))?.Pics ?? new List<Pic>();
|
||||
|
||||
public async Task<List<Pic>> SomePics(string address) =>
|
||||
(await Get<SomePicsResponseData>($"/address/{address}/pics"))?.Pics ?? new List<Pic>();
|
||||
|
||||
public async Task<PutPicResponseData?> PutPic(string address, string base64Image) =>
|
||||
(await Put<PutPicResponseData, PutPic>($"/address/{address}/pics/upload", new PutPic { Pic = base64Image }));
|
||||
|
||||
public async Task<PutPicResponseData?> PutPic(string address, byte[] bytes) =>
|
||||
await PutPic(address, Convert.ToBase64String(bytes));
|
||||
|
||||
public async Task<PutPicResponseData?> PostPicDescription(string address, string id, string? description) =>
|
||||
(await Post<PutPicResponseData, PostPic>($"/address/{address}/pics/{id}", new PostPic { Description = description }));
|
||||
public async Task<BasicResponseData?> DeletePic(string address, string id) =>
|
||||
(await Delete<BasicResponseData>($"/address/{address}/pics/{id}"));
|
||||
|
||||
public async Task<PatchStatusResponseData?> PatchStatus(string address, string id, string content, string? emoji) =>
|
||||
(await Patch<PatchStatusResponseData, PatchStatus>($"/address/{address}/statuses/", new PatchStatus { Id = id, Content = content, Emoji = emoji }));
|
||||
|
||||
public async Task<BasicResponseData?> DeleteStatus(string address, string id) =>
|
||||
(await Delete<BasicResponseData>($"/address/{address}/statuses/{id}"));
|
||||
|
||||
public async Task<List<NowData>?> NowGarden() =>
|
||||
(await Get<NowResponseData>($"/now/garden"))?.Garden ?? new List<NowData>();
|
||||
|
||||
public async Task<List<string>?> Directory() =>
|
||||
(await Get<DirectoryResponseData>($"/directory"))?.Directory ?? new List<string>();
|
||||
|
||||
public async Task<NowContentData?> GetNowPage(string address) =>
|
||||
(await Get<NowPageResponseData>($"/address/{address}/now"))?.Now;
|
||||
|
||||
public async Task<BasicResponseData?> PostNowPage(string address, string content, bool listed) =>
|
||||
await Post<BasicResponseData, NowContentData>($"/address/{address}/now", new NowContentData { Content = content, Listed = listed ? 1 : 0 });
|
||||
|
||||
public async Task<List<MarkupString>> Ephemeral() =>
|
||||
(await Get<EphemeralResponseData>($"/ephemeral"))?.Content?.Select(s => (MarkupString)s)?.ToList() ?? new List<MarkupString>();
|
||||
|
||||
public async Task<BasicResponseData?> PostEphemeral(string content) =>
|
||||
await Post<BasicResponseData, EphemeralData>("/ephemeral", new EphemeralData { Content = content });
|
||||
|
||||
public async Task<ProfileResponseData?> GetProfile(string address) =>
|
||||
await Get<ProfileResponseData>($"/address/{address}/web");
|
||||
|
||||
public async Task<BasicResponseData?> PostProfile(string address, string content, bool publish = true) =>
|
||||
await Post<BasicResponseData, PostProfile>($"/address/{address}/web", new PostProfile { Content = content, Publish = publish });
|
||||
|
||||
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<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>($"/address/{address}/pfp", fileResult: image);
|
||||
|
||||
public async Task<List<Paste>> GetPastes(string address) =>
|
||||
(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}");
|
||||
|
||||
public async Task<PostPasteResponseData?> PostPaste(string address, string title, string content, bool listed) =>
|
||||
await Post<PostPasteResponseData, Paste>($"/address/{address}/pastebin/", new Paste() { Title = title, Content = content, IsListed = listed });
|
||||
|
||||
#endregion
|
||||
|
||||
#region Auth
|
||||
/// <summary>
|
||||
/// Add the api token into the default headers
|
||||
/// </summary>
|
||||
/// <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) apiToken = token;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove the api token from the default headers
|
||||
/// </summary>
|
||||
public void RemoveToken() {
|
||||
_client.DefaultRequestHeaders.Remove("Authorization");
|
||||
}
|
||||
|
||||
public async Task<string?> OAuth(string code, string client_id, string client_secret, string redirect_uri) {
|
||||
string? token = null;
|
||||
string uri = $"/oauth/?code={code}&client_id={client_id}&client_secret={client_secret}&redirect_uri={redirect_uri}&scope=everything";
|
||||
try {
|
||||
HttpResponseMessage response = await _client.GetAsync(uri);
|
||||
if (response.IsSuccessStatusCode) {
|
||||
TokenResponseData? responseObj = await response.Content.ReadFromJsonAsync<TokenResponseData>(_serializerOptions);
|
||||
if (responseObj != null && !string.IsNullOrEmpty(responseObj.AccessToken)) {
|
||||
token = responseObj.AccessToken;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Debug.WriteLine(@"\tERROR {0}", ex.Message);
|
||||
}
|
||||
return token;
|
||||
}
|
||||
#endregion
|
||||
|
||||
public async Task<MarkupString?> GetHtml(string url) {
|
||||
string? raw = null;
|
||||
try {
|
||||
HttpResponseMessage response = await _client.GetAsync(url);
|
||||
if (response.IsSuccessStatusCode) {
|
||||
raw = await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Debug.WriteLine(@"\tERROR {0}", ex.Message);
|
||||
}
|
||||
return string.IsNullOrEmpty(raw) ? null : (MarkupString)raw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,5 +3,6 @@
|
|||
namespace Neighbourhood.omg.lol {
|
||||
public class NavigatorService {
|
||||
internal NavigationManager? NavigationManager { get; set; }
|
||||
internal Page? Page { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,276 +0,0 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
using Neighbourhood.omg.lol.Models;
|
||||
using System.Diagnostics;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Neighbourhood.omg.lol
|
||||
{
|
||||
public class RestService {
|
||||
HttpClient _client;
|
||||
JsonSerializerOptions _serializerOptions;
|
||||
public const string BaseUrl = "https://api.omg.lol";
|
||||
|
||||
public RestService(string? token = null) {
|
||||
_client = new HttpClient();
|
||||
_client.BaseAddress = new Uri(BaseUrl);
|
||||
_client.DefaultRequestHeaders.UserAgent.Add(new System.Net.Http.Headers.ProductInfoHeaderValue(App.Name, App.Version));
|
||||
_serializerOptions = new JsonSerializerOptions {
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||
WriteIndented = true
|
||||
};
|
||||
AddToken(token);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public void RemoveToken() {
|
||||
_client.DefaultRequestHeaders.Remove("Authorization");
|
||||
}
|
||||
|
||||
private async Task<T?> Get<T>(string uri, CancellationToken cancellationToken = default) where T : IOmgLolResponseData {
|
||||
T? responseData = default(T);
|
||||
try {
|
||||
HttpResponseMessage response = await _client.GetAsync(uri, cancellationToken: cancellationToken);
|
||||
if (response.IsSuccessStatusCode) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
catch (JsonException ex) {
|
||||
Debug.WriteLine(@"\tERROR {0}", ex.Message);
|
||||
Debug.WriteLine(str);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Debug.WriteLine(@"\tERROR {0}", ex.Message);
|
||||
}
|
||||
|
||||
return responseData;
|
||||
}
|
||||
|
||||
private async Task<TResponse?> Post<TResponse, TData>(string uri, TData data, CancellationToken cancellationToken = default) where TResponse : IOmgLolResponseData {
|
||||
TResponse? responseData = default(TResponse);
|
||||
try {
|
||||
HttpResponseMessage response = await _client.PostAsJsonAsync(uri, data, _serializerOptions, 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 {
|
||||
string json = JsonSerializer.Serialize(data, _serializerOptions);
|
||||
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Put, uri);
|
||||
request.Content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
HttpResponseMessage response = await _client.SendAsync(request, cancellationToken: cancellationToken);
|
||||
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?> Patch<TResponse, TData>(string uri, TData data, CancellationToken cancellationToken = default) where TResponse : IOmgLolResponseData {
|
||||
TResponse? responseData = default(TResponse);
|
||||
try {
|
||||
HttpResponseMessage response = await _client.PatchAsJsonAsync(uri, data, _serializerOptions, 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<T?> Delete<T>(string uri, CancellationToken cancellationToken = default) where T : IOmgLolResponseData {
|
||||
T? responseData = default(T);
|
||||
try {
|
||||
HttpResponseMessage response = await _client.DeleteAsync(uri, cancellationToken: cancellationToken);
|
||||
if (response.IsSuccessStatusCode) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
catch (JsonException ex) {
|
||||
Debug.WriteLine(@"\tERROR {0}", ex.Message);
|
||||
Debug.WriteLine(str);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Debug.WriteLine(@"\tERROR {0}", ex.Message);
|
||||
}
|
||||
|
||||
return responseData;
|
||||
}
|
||||
|
||||
public async Task<List<Status>> StatuslogLatest() =>
|
||||
(await Get<StatusResponseData>("/statuslog/latest"))?.Statuses ?? new List<Status>();
|
||||
|
||||
public async Task<List<Status>> Statuslog(string address) =>
|
||||
(await Get<StatusResponseData>($"/address/{address}/statuses"))?.Statuses ?? new List<Status>();
|
||||
|
||||
public async Task<MarkupString> StatuslogBio(string address) {
|
||||
StatusBioResponseData? responseData = await Get<StatusBioResponseData>($"/address/{address}/statuses/bio");
|
||||
return Utilities.MdToHtmlMarkup(responseData?.Bio ?? "");
|
||||
}
|
||||
|
||||
public async Task<AccountResponseData?> AccountInfo() =>
|
||||
await Get<AccountResponseData>("/account/application/info");
|
||||
|
||||
public async Task<AddressResponseList?> Addresses() =>
|
||||
await Get<AddressResponseList>("/account/application/addresses");
|
||||
|
||||
public async Task<StatusPostResponseData?> StatusPost(string address, StatusPost statusPost) =>
|
||||
await Post<StatusPostResponseData, StatusPost>($"/address/{address}/statuses", statusPost);
|
||||
|
||||
public async Task<List<Pic>> SomePics() =>
|
||||
(await Get<SomePicsResponseData>("/pics"))?.Pics ?? new List<Pic>();
|
||||
|
||||
public async Task<List<Pic>> SomePics(string address) =>
|
||||
(await Get<SomePicsResponseData>($"/address/{address}/pics"))?.Pics ?? new List<Pic>();
|
||||
|
||||
public async Task<PutPicResponseData?> PutPic(string address, string base64Image) =>
|
||||
(await Put<PutPicResponseData, PutPic>($"/address/{address}/pics/upload", new PutPic { Pic = base64Image }));
|
||||
public async Task<PutPicResponseData?> PutPic(string address, IBrowserFile file) {
|
||||
byte[] bytes;
|
||||
using (var memoryStream = new MemoryStream()) {
|
||||
await file.OpenReadStream().CopyToAsync(memoryStream);
|
||||
bytes = memoryStream.ToArray();
|
||||
}
|
||||
|
||||
return await PutPic(address, bytes);
|
||||
}
|
||||
|
||||
public async Task<PutPicResponseData?> PutPic(string address, FileResult file) {
|
||||
byte[] bytes;
|
||||
using var memoryStream = new MemoryStream();
|
||||
using var fileStream = await file.OpenReadAsync();
|
||||
await fileStream.CopyToAsync(memoryStream);
|
||||
bytes = memoryStream.ToArray();
|
||||
|
||||
return await PutPic(address, bytes);
|
||||
}
|
||||
|
||||
public async Task<PutPicResponseData?> PutPic(string address, byte[] bytes) =>
|
||||
await PutPic(address, Convert.ToBase64String(bytes));
|
||||
|
||||
public async Task<PutPicResponseData?> PostPicDescription(string address, string id, string? description) =>
|
||||
(await Post<PutPicResponseData, PostPic>($"/address/{address}/pics/{id}", new PostPic { Description = description }));
|
||||
public async Task<BasicResponseData?> DeletePic(string address, string id) =>
|
||||
(await Delete<BasicResponseData>($"/address/{address}/pics/{id}"));
|
||||
|
||||
public async Task<PatchStatusResponseData?> PatchStatus(string address, string id, string content, string? emoji) =>
|
||||
(await Patch<PatchStatusResponseData, PatchStatus>($"/address/{address}/statuses/", new PatchStatus { Id = id, Content = content, Emoji = emoji }));
|
||||
|
||||
public async Task<BasicResponseData?> DeleteStatus(string address, string id) =>
|
||||
(await Delete<BasicResponseData>($"/address/{address}/statuses/{id}"));
|
||||
|
||||
public async Task<List<NowData>?> NowGarden() =>
|
||||
(await Get<NowResponseData>($"/now/garden"))?.Garden ?? new List<NowData>();
|
||||
|
||||
public async Task<List<string>?> Directory() =>
|
||||
(await Get<DirectoryResponseData>($"/directory"))?.Directory ?? new List<string>();
|
||||
|
||||
public async Task<NowContentData?> GetNowPage(string address) =>
|
||||
(await Get<NowPageResponseData>($"/address/{address}/now"))?.Now;
|
||||
|
||||
public async Task<BasicResponseData?> PostNowPage(string address, string content, bool listed) =>
|
||||
await Post<BasicResponseData, NowContentData>($"/address/{address}/now", new NowContentData { Content = content, Listed = listed ? 1 : 0 });
|
||||
|
||||
public async Task<List<MarkupString>> Ephemeral() =>
|
||||
(await Get<EphemeralResponseData>($"/ephemeral"))?.Content?.Select(s => (MarkupString)s)?.ToList() ?? new List<MarkupString>();
|
||||
|
||||
public async Task<BasicResponseData?> PostEphemeral(string content) =>
|
||||
await Post<BasicResponseData, EphemeralData>("/ephemeral", new EphemeralData { Content = content });
|
||||
|
||||
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<string?> OAuth(string code, string client_id, string client_secret, string redirect_uri) {
|
||||
string? token = null;
|
||||
string uri = $"/oauth/?code={code}&client_id={client_id}&client_secret={client_secret}&redirect_uri={redirect_uri}&scope=everything";
|
||||
try {
|
||||
HttpResponseMessage response = await _client.GetAsync(uri);
|
||||
if (response.IsSuccessStatusCode) {
|
||||
TokenResponseData? responseObj = await response.Content.ReadFromJsonAsync<TokenResponseData>(_serializerOptions);
|
||||
if (responseObj != null && !string.IsNullOrEmpty(responseObj.AccessToken)) {
|
||||
token = responseObj.AccessToken;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Debug.WriteLine(@"\tERROR {0}", ex.Message);
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
public async Task<MarkupString?> GetHtml(string url) {
|
||||
string? raw = null;
|
||||
try {
|
||||
HttpResponseMessage response = await _client.GetAsync(url);
|
||||
if (response.IsSuccessStatusCode) {
|
||||
raw = await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Debug.WriteLine(@"\tERROR {0}", ex.Message);
|
||||
}
|
||||
return string.IsNullOrEmpty(raw) ? null : (MarkupString)raw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -24,10 +24,19 @@ 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; }
|
||||
|
||||
// Account data
|
||||
public AccountResponseData? AccountInfo { get; set; }
|
||||
private AccountResponseData? _accountInfo;
|
||||
public AccountResponseData? AccountInfo {
|
||||
get => _accountInfo;
|
||||
set {
|
||||
_accountInfo = value;
|
||||
OnPropertyChanged(nameof(AccountInfo));
|
||||
}
|
||||
}
|
||||
public AddressResponseList? AddressList { get; set; }
|
||||
|
||||
public bool IsAuthorized { get => AccountInfo != null; }
|
||||
|
|
@ -64,6 +73,7 @@ namespace Neighbourhood.omg.lol {
|
|||
// data for selected address
|
||||
public List<Status>? CachedAddressStatuses { get; set; }
|
||||
public List<Pic>? CachedAddressPics { get; set; }
|
||||
public List<Paste>? CachedAddressPastes { get; set; }
|
||||
public MarkupString? CachedAddressBio { get; set; }
|
||||
private string? _cachedAddress;
|
||||
public string? CachedAddress {
|
||||
|
|
@ -73,6 +83,7 @@ namespace Neighbourhood.omg.lol {
|
|||
_cachedAddress = value;
|
||||
CachedAddressStatuses = new List<Status>();
|
||||
CachedAddressPics = new List<Pic>();
|
||||
CachedAddressPastes = new List<Paste>();
|
||||
CachedAddressBio = null;
|
||||
}
|
||||
}
|
||||
|
|
@ -146,9 +157,9 @@ namespace Neighbourhood.omg.lol {
|
|||
}
|
||||
|
||||
// api service
|
||||
private RestService api { get; set; }
|
||||
private ApiService api { get; set; }
|
||||
|
||||
public State(RestService restService) {
|
||||
public State(ApiService restService) {
|
||||
api = restService;
|
||||
}
|
||||
|
||||
|
|
@ -215,7 +226,7 @@ namespace Neighbourhood.omg.lol {
|
|||
public async Task<MarkupString?> GetBio(string address, bool forceRefresh = false) {
|
||||
CachedAddress = address;
|
||||
if (forceRefresh || CachedAddressBio == null) {
|
||||
CachedAddressBio = await api.StatuslogBio(address);
|
||||
CachedAddressBio = Utilities.MdToHtmlMarkup(await api.StatuslogBio(address));
|
||||
}
|
||||
return CachedAddressBio;
|
||||
}
|
||||
|
|
@ -275,6 +286,19 @@ namespace Neighbourhood.omg.lol {
|
|||
return CachedAddressPics;
|
||||
}
|
||||
|
||||
public async Task<List<Paste>?> GetPastes(string address, bool forceRefresh = false) {
|
||||
CachedAddress = address;
|
||||
if (forceRefresh || this.CachedAddressPastes == null || this.CachedAddressPastes.Count == 0) {
|
||||
if (AddressNames?.Contains(address) ?? false) {
|
||||
CachedAddressPastes = (await api.GetMyPastes(address)) ?? new List<Paste>();
|
||||
}
|
||||
else {
|
||||
CachedAddressPastes = (await api.GetPastes(address)) ?? new List<Paste>();
|
||||
}
|
||||
}
|
||||
return CachedAddressPastes;
|
||||
}
|
||||
|
||||
public async Task RefreshStatuses() {
|
||||
await GetStatuses(forceRefresh: true);
|
||||
if(SelectedAddressName != null)
|
||||
|
|
@ -287,16 +311,29 @@ namespace Neighbourhood.omg.lol {
|
|||
}
|
||||
public async Task RefreshNow() => await GetNowGarden(forceRefresh: true);
|
||||
|
||||
public async Task<IOrderedEnumerable<StatusOrPic>> GetFeed(bool forceRefresh = false) {
|
||||
public async Task RefreshPastes() {
|
||||
if (SelectedAddressName != null)
|
||||
await GetPastes(SelectedAddressName, forceRefresh: true);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
81
Components/EditBioDialog.razor
Normal file
81
Components/EditBioDialog.razor
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
@inject IJSRuntime JS
|
||||
@inject State State
|
||||
@inject ApiService api
|
||||
|
||||
<div class="overlay" data-ui="#@id"></div>
|
||||
<dialog id="@id">
|
||||
<h5>Edit your statuslog bio</h5>
|
||||
<div class="row">
|
||||
<div class="max markdown-editor">
|
||||
@if (Bio != null) {
|
||||
<MarkdownEditor @ref="Editor"
|
||||
@bind-Value="@Bio"
|
||||
Theme="material-darker"
|
||||
MaxHeight="100%"
|
||||
AutoDownloadFontAwesome="false"
|
||||
>
|
||||
<Toolbar>
|
||||
<MarkdownToolbarButton Action="MarkdownAction.Bold" Icon="fa-solid fa-bold" Title="Bold" />
|
||||
<MarkdownToolbarButton Action="MarkdownAction.Italic" Icon="fa-solid fa-italic" Title="Italic" />
|
||||
<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" />
|
||||
</Toolbar>
|
||||
</MarkdownEditor>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<nav class="no-space">
|
||||
<div class="max"></div>
|
||||
<button class="transparent link" data-ui="#@id" disabled="@loading">Cancel</button>
|
||||
<button @onclick="PostBio" disabled="@loading">
|
||||
@if (loading) {
|
||||
<span>Saving...</span>
|
||||
}
|
||||
else {
|
||||
<i class="fa-solid fa-floppy-disk"></i> <span>Save</span>
|
||||
}
|
||||
</button>
|
||||
</nav>
|
||||
</dialog>
|
||||
|
||||
@code {
|
||||
private MarkdownEditor? Editor;
|
||||
public string? Bio { get; set; }
|
||||
[Parameter]
|
||||
public string? Address { get; set; }
|
||||
private bool loading = true;
|
||||
[Parameter]
|
||||
public string? id { get; set; }
|
||||
|
||||
protected override async Task OnInitializedAsync() {
|
||||
await base.OnInitializedAsync();
|
||||
Bio = await api.StatuslogBio(Address ?? State.SelectedAddressName!);
|
||||
await InvokeAsync(StateHasChanged);
|
||||
await Editor!.SetValueAsync(Bio);
|
||||
loading = false;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
public async Task PostBio() {
|
||||
loading = true;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
// Post the bio
|
||||
await api.PostStatuslogBio(Address!, Bio ?? string.Empty);
|
||||
State.CachedAddressBio = Utilities.MdToHtmlMarkup(Bio ?? string.Empty);
|
||||
|
||||
await JS.InvokeVoidAsync("ui", "#" + id);
|
||||
// reset input
|
||||
await OnInitializedAsync();
|
||||
loading = false;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
State.SendRefresh();
|
||||
}
|
||||
}
|
||||
131
Components/EditPasteDialog.razor
Normal file
131
Components/EditPasteDialog.razor
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
@inject IJSRuntime JS
|
||||
@inject State State
|
||||
@inject ApiService api
|
||||
|
||||
<div class="overlay" data-ui="#@id"></div>
|
||||
<dialog id="@id">
|
||||
<div class="row">
|
||||
<div class="field text label border max">
|
||||
<InputText @bind-Value="Title"></InputText>
|
||||
<label>Content</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="field textarea label border max">
|
||||
<InputTextArea @bind-Value="Content"></InputTextArea>
|
||||
<label>Content</label>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="no-space">
|
||||
@if (Paste != null)
|
||||
{
|
||||
if (confirmDelete)
|
||||
{
|
||||
<button @onclick="ConfirmDeletePaste" disabled="@loading" class="red-7-bg white-fg">
|
||||
<i class="fa-solid fa-exclamation-triangle"></i> <span>Are you sure?</span>
|
||||
</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button @onclick="DeletePaste" disabled="@loading" class="red-7-bg white-fg">
|
||||
<i class="fa-solid fa-trash"></i> <span>Delete</span>
|
||||
</button>
|
||||
}
|
||||
}
|
||||
<div class="max"></div>
|
||||
<label class="checkbox">
|
||||
<InputCheckbox @bind-Value="Listed"></InputCheckbox>
|
||||
<span>Listed?</span>
|
||||
</label>
|
||||
<button class="transparent link" data-ui="#@id" disabled="@loading">Cancel</button>
|
||||
<button @onclick="PostPaste" disabled="@loading">
|
||||
@if (loading) {
|
||||
<span>Saving...</span>
|
||||
}
|
||||
else {
|
||||
<i class="fa-solid fa-floppy-disk"></i> <span>Save</span>
|
||||
}
|
||||
</button>
|
||||
</nav>
|
||||
</dialog>
|
||||
|
||||
@code {
|
||||
private Paste? _paste;
|
||||
|
||||
public Paste? Paste {
|
||||
get => _paste;
|
||||
set {
|
||||
_paste = value;
|
||||
Title = _paste?.Title;
|
||||
Content = _paste?.Content;
|
||||
Listed = _paste?.IsListed ?? false;
|
||||
InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
|
||||
public string? Title { get; set; }
|
||||
public string? Content { get; set; }
|
||||
public bool Listed { get; set; }
|
||||
private bool loading = false;
|
||||
[Parameter]
|
||||
public string? id { get; set; }
|
||||
private bool confirmDelete { get; set; }
|
||||
|
||||
protected override async Task OnInitializedAsync() {
|
||||
await base.OnInitializedAsync();
|
||||
Title = Paste?.Title;
|
||||
Content = Paste?.Content;
|
||||
Listed = Paste?.IsListed ?? false;
|
||||
}
|
||||
|
||||
public async Task DeletePaste() {
|
||||
if (!confirmDelete) confirmDelete = true;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
public async Task ConfirmDeletePaste() {
|
||||
if (confirmDelete) {
|
||||
loading = true;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
if (!string.IsNullOrEmpty(Paste?.Title)) {
|
||||
await api.DeletePaste(State.SelectedAddressName!, Paste.Title);
|
||||
await State.RefreshPastes();
|
||||
State.SendRefresh();
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
await JS.InvokeVoidAsync("ui", "#" + id);
|
||||
// clear input
|
||||
Title = string.Empty;
|
||||
Content = string.Empty;
|
||||
Listed = false;
|
||||
loading = false;
|
||||
confirmDelete = false;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task PostPaste() {
|
||||
loading = true;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
if (!string.IsNullOrEmpty(Paste?.Title)) {
|
||||
await api.PostPaste(State.SelectedAddressName!, Title, Content, Listed);
|
||||
await State.RefreshPastes();
|
||||
State.SendRefresh();
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
await JS.InvokeVoidAsync("ui", "#" + id);
|
||||
// clear input
|
||||
Paste = null;
|
||||
Title = string.Empty;
|
||||
Content = string.Empty;
|
||||
Listed = false;
|
||||
confirmDelete = false;
|
||||
loading = false;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
@inject IJSRuntime JS
|
||||
@inject State State
|
||||
@inject RestService api
|
||||
@inject ApiService api
|
||||
|
||||
<div class="overlay" data-ui="#@id"></div>
|
||||
<dialog id="@id">
|
||||
|
|
|
|||
114
Components/EditProfilePicDialog.razor
Normal file
114
Components/EditProfilePicDialog.razor
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
@inject IJSRuntime JS
|
||||
@inject State State
|
||||
@inject ApiService 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);
|
||||
|
||||
if (Base64File != null && File != null)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
@inject IJSRuntime JS
|
||||
@inject State State
|
||||
@inject RestService api
|
||||
@inject ApiService api
|
||||
|
||||
<div class="overlay" data-ui="#@id"></div>
|
||||
<dialog id="@id">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
@inject IJSRuntime JS
|
||||
@inject State State
|
||||
@inject RestService api
|
||||
@inject ApiService api
|
||||
@if(Html != null) {
|
||||
<iframe id="@id" frameborder="0" scrolling="no" srcdoc="@Html" onload="() => iframeResize({ license: 'GPLv3' })"></iframe>
|
||||
}
|
||||
|
|
@ -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@5.1.5'></script></body>");
|
||||
Html = (MarkupString)(SrcString ?? string.Empty);
|
||||
}
|
||||
await InvokeAsync(StateHasChanged);
|
||||
await IframeResize();
|
||||
}
|
||||
|
||||
public async Task IframeResize() {
|
||||
await JS.InvokeVoidAsync("iframeResize", new { license = "GPLv3" });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
<AvatarMenuLinks></AvatarMenuLinks>
|
||||
</menu>
|
||||
</button>
|
||||
<small class="s m address">Omg.lol</small>
|
||||
<small class="s m honey">Omg.lol</small>
|
||||
</NavLink>
|
||||
<div class="l">
|
||||
Hey there. <br />
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,10 +27,22 @@ else {
|
|||
<div class="label">Now.garden</div>
|
||||
</NavLink>
|
||||
}
|
||||
|
||||
|
||||
|
||||
<NavLink class="l m nav-link" href="/directory">
|
||||
<i class="square fa-duotone fa-address-book"></i>
|
||||
<div class="label">Directory</div>
|
||||
</NavLink>
|
||||
</NavLink>
|
||||
|
||||
@code {
|
||||
protected override async Task OnInitializedAsync() {
|
||||
await base.OnInitializedAsync();
|
||||
State.PropertyChanged += StateChanged;
|
||||
}
|
||||
|
||||
private async void StateChanged(object? sender, PropertyChangedEventArgs e) {
|
||||
if (e.PropertyName == nameof(State.AccountInfo)) await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
State.PropertyChanged -= StateChanged;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
@inject IJSRuntime JS
|
||||
@inject State State
|
||||
@inject RestService api
|
||||
@inject ApiService api
|
||||
@inject NavigationManager navigationManager
|
||||
|
||||
<div class="overlay @(Active ? "active" : string.Empty)" data-ui="#@id"></div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
@inject IJSRuntime JS
|
||||
@inject State State
|
||||
@inject RestService api
|
||||
@inject ApiService api
|
||||
@inject NavigationManager navigationManager
|
||||
|
||||
<div class="overlay @(Active ? "active" : string.Empty)" data-ui="#@id"></div>
|
||||
|
|
@ -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){
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
@inject IJSRuntime JS
|
||||
@inject State State
|
||||
@inject RestService api
|
||||
@inject ApiService api
|
||||
@inject NavigationManager navigationManager
|
||||
@inject NavigatorService navigatorService
|
||||
|
||||
<div class="overlay @(Active ? "active" : string.Empty)" data-ui="#@id"></div>
|
||||
<dialog id="@id" class="@(Active ? "active" : string.Empty)" open="@Active">
|
||||
|
|
@ -27,7 +28,8 @@
|
|||
</button>
|
||||
</div>
|
||||
<div class="field textarea border max">
|
||||
<InputTextArea @bind-Value="Content"></InputTextArea>
|
||||
<textarea @bind="@Content" @bind:event="oninput" />
|
||||
<div class="right-align"><small class="@( Content.Length >= 500 ? "red" : Content.Length >= 260 ? "yellow-text" : "")">@Content.Length / 500</small></div>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="right-align no-space">
|
||||
|
|
@ -66,16 +68,26 @@
|
|||
|
||||
public async Task PostStatus() {
|
||||
|
||||
StatusPost post = new StatusPost
|
||||
{
|
||||
StatusPost post = new StatusPost {
|
||||
Emoji = Emoji,
|
||||
Content = Content
|
||||
};
|
||||
|
||||
if (State?.SelectedAddress?.Preferences?.Statuslog?.MastodonPosting ?? false){
|
||||
if (State?.SelectedAddress?.Preferences?.Statuslog?.MastodonPosting ?? false) {
|
||||
post.SkipMastodonPost = !postToMastodon;
|
||||
}
|
||||
|
||||
if(Content.Length >= 500) {
|
||||
bool answer = await navigatorService.Page!.DisplayAlert(
|
||||
"Character limit reached",
|
||||
"Your message is over 500 characters, which is a lot for a status.\n"
|
||||
+ ((postToMastodon && !(post.SkipMastodonPost ?? true))? "If you continue, your post will not make it over to Mastodon.\n" : "")
|
||||
+ "Do you wish to post it anyway?",
|
||||
"Yes", "No"
|
||||
);
|
||||
if (!answer) return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
var result = await api.StatusPost(State!.SelectedAddressName!, post);
|
||||
|
|
|
|||
|
|
@ -16,13 +16,13 @@
|
|||
<article id="directoryIndex" class="responsive">
|
||||
<nav class="wrap">
|
||||
@foreach (var group in groupedAddresses) {
|
||||
<a @onclick='()=>{JS.InvokeVoidAsync("scrollToId", $"index-{group.Key}");}' class="button circle transparent address">@group.Key</a>
|
||||
<a @onclick='()=>{JS.InvokeVoidAsync("scrollToId", $"index-{group.Key}");}' class="button circle transparent honey">@group.Key</a>
|
||||
}
|
||||
</nav>
|
||||
</article>
|
||||
<article id="directory" class="responsive">
|
||||
@foreach(var group in groupedAddresses) {
|
||||
<h3 class="address" id="index-@group.Key">— @group.Key —</h3>
|
||||
<h3 class="honey" id="index-@group.Key">— @group.Key —</h3>
|
||||
<ul>
|
||||
@foreach(string address in group) {
|
||||
string displayAddress = address;
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -1,15 +1,35 @@
|
|||
@page "/editNow"
|
||||
@inject NavigationManager Nav
|
||||
@inject RestService api
|
||||
@inject ApiService 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"
|
||||
AutoDownloadFontAwesome="false"
|
||||
>
|
||||
<Toolbar>
|
||||
<MarkdownToolbarButton Action="MarkdownAction.Bold" Icon="fa-solid fa-bold" Title="Bold" />
|
||||
<MarkdownToolbarButton Action="MarkdownAction.Italic" Icon="fa-solid fa-italic" Title="Italic" />
|
||||
<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 +54,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 +63,13 @@
|
|||
{
|
||||
listed = data.Listed == 1;
|
||||
markdownValue = data.Content;
|
||||
|
||||
loading = false;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
await Editor!.SetValueAsync(markdownValue);
|
||||
}
|
||||
loading = false;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
|
|
@ -64,4 +89,10 @@
|
|||
|
||||
loading = false;
|
||||
}
|
||||
|
||||
public async Task OnCustomButtonClicked(MarkdownButtonEventArgs eventArgs) {
|
||||
if (eventArgs.Name == "Help") {
|
||||
await JS.InvokeVoidAsync("open", "https://home.omg.lol/info/editor", "_blank");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
142
Components/Pages/EditProfile.razor
Normal file
142
Components/Pages/EditProfile.razor
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
@page "/editProfile"
|
||||
@inject NavigationManager Nav
|
||||
@inject ApiService api
|
||||
@inject State State
|
||||
@inject IJSRuntime JS
|
||||
|
||||
<div class="max markdown-editor">
|
||||
@if (markdownValue != null)
|
||||
{
|
||||
<MarkdownEditor @ref="Editor"
|
||||
@bind-Value="@markdownValue"
|
||||
Theme="material-darker"
|
||||
MaxHeight="100%"
|
||||
CustomButtonClicked="@OnCustomButtonClicked"
|
||||
AutoDownloadFontAwesome="false"
|
||||
>
|
||||
<Toolbar>
|
||||
<MarkdownToolbarButton Action="MarkdownAction.Bold" Icon="fa-solid fa-bold" Title="Bold" />
|
||||
<MarkdownToolbarButton Action="MarkdownAction.Italic" Icon="fa-solid fa-italic" Title="Italic" />
|
||||
<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>
|
||||
@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>
|
||||
<button @onclick="Save" disabled="@loading">
|
||||
@if (loading) {
|
||||
<span>Saving...</span>
|
||||
}
|
||||
else {
|
||||
<i class="fa-solid fa-floppy-disk"></i> <span>Save & Publish</span>
|
||||
}
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
@code {
|
||||
private MarkdownEditor? Editor;
|
||||
private string? markdownValue;
|
||||
private string? css;
|
||||
private string? head;
|
||||
private string? theme;
|
||||
private Theme? selectedTheme;
|
||||
private Dictionary<string, Theme>? themes;
|
||||
|
||||
private bool loading = true;
|
||||
|
||||
protected override async Task OnInitializedAsync() {
|
||||
await base.OnInitializedAsync();
|
||||
ProfileResponseData? data = await api.GetProfile(State.SelectedAddressName!);
|
||||
if (data != null) {
|
||||
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);
|
||||
}
|
||||
|
||||
Task OnMarkdownValueChanged(string value) {
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task Save() {
|
||||
loading = true;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
var result = await api.PostProfile(State.SelectedAddressName!,
|
||||
new PostProfile() {
|
||||
Content = markdownValue ?? string.Empty,
|
||||
Css = string.IsNullOrEmpty(css) ? null : css,
|
||||
Head = string.IsNullOrEmpty(head) ? null : head,
|
||||
Theme = string.IsNullOrEmpty(theme) ? null : theme
|
||||
});
|
||||
if (result != null) {
|
||||
await State.RefreshNow();
|
||||
await InvokeAsync(StateHasChanged);
|
||||
Nav.NavigateTo($"/person/{State.SelectedAddressName}#profile");
|
||||
}
|
||||
|
||||
loading = false;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
public async Task OnCustomButtonClicked(MarkdownButtonEventArgs eventArgs) {
|
||||
if (eventArgs.Name == "Help") {
|
||||
await JS.InvokeVoidAsync("open", "https://home.omg.lol/info/editor", "_blank");
|
||||
}
|
||||
}
|
||||
|
||||
public void ThemeChanged(Theme? _theme) {
|
||||
theme = _theme?.Id;
|
||||
selectedTheme = _theme;
|
||||
InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
<article class="now">
|
||||
<nav>
|
||||
<a class="author" href="/person/@now.Address#now">
|
||||
<h6><i class="fa-duotone fa-seedling"></i> @now.Address</h6>
|
||||
<h6><i class="fa-duotone fa-seedling"></i><span>@now.Address</span></h6>
|
||||
</a>
|
||||
</nav>
|
||||
<nav>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
@ -45,6 +51,10 @@
|
|||
<span>/Now</span>
|
||||
</a>
|
||||
}
|
||||
<a data-ui="#pastebin">
|
||||
<i class="fa-solid fa-clipboard"></i>
|
||||
<span>Paste.lol</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -52,6 +62,12 @@
|
|||
<div id="profile" class="page no-padding">
|
||||
<a href="@ProfileUrl" target="_blank" class="hover absolute top right chip fill large-elevate">Open in browser <i class="fa-solid fa-arrow-up-right-from-square tiny"></i></a>
|
||||
<ExternalPageComponent id="profile_page" @ref="ProfilePage" Url="@ProfileUrl"></ExternalPageComponent>
|
||||
@if (IsMe) {
|
||||
<a href="/editProfile" class="button fab circle extra large-elevate center-align middle-align">
|
||||
<i class="square fa-solid fa-file-pen" style="line-height:56px;"></i>
|
||||
<span>Edit</span>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div id="statuses" class="page padding active">
|
||||
|
|
@ -67,10 +83,16 @@
|
|||
</article>
|
||||
</div>
|
||||
}
|
||||
@if (IsMe) {
|
||||
<EditBioDialog id="edit-bio" Address="@Address"></EditBioDialog>
|
||||
<div class="row center-align">
|
||||
<button data-ui="#edit-bio"><i class="fa-solid fa-pencil"></i> Edit Bio</button>
|
||||
</div>
|
||||
}
|
||||
<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>
|
||||
}
|
||||
|
|
@ -97,6 +119,15 @@
|
|||
}
|
||||
</div>
|
||||
}
|
||||
<div id="pastebin" class="page padding">
|
||||
<PasteList @ref="PasteList" PastesFunc="@(async(refresh) => await State.GetPastes(Address, refresh))" Editable="@IsMe"></PasteList>
|
||||
@if (IsMe) {
|
||||
<button class="fab circle extra large-elevate" data-ui="#paste-modal">
|
||||
<i class="fa-solid fa-clipboard-medical"></i>
|
||||
</button>
|
||||
<EditPasteDialog id="paste-modal"></EditPasteDialog>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
|
|
@ -108,6 +139,7 @@
|
|||
_address = value;
|
||||
if (StatusList != null) StatusList.StatusFunc = async (refresh) => await State.GetStatuses(_address, refresh);
|
||||
if (PicList != null) PicList.PicsFunc = async (refresh) => await State.GetPics(_address, refresh);
|
||||
if (PasteList != null) PasteList.PastesFunc = async (refresh) => await State.GetPastes(_address, refresh);
|
||||
}
|
||||
}
|
||||
public string ProfileUrl {
|
||||
|
|
@ -123,9 +155,10 @@
|
|||
|
||||
private StatusList? StatusList { get; set; }
|
||||
private PicList? PicList { get; set; }
|
||||
private PasteList? PasteList { 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>
|
||||
|
|
|
|||
76
Components/PasteCard.razor
Normal file
76
Components/PasteCard.razor
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
@using CommunityToolkit.Maui.Alerts
|
||||
@inject IJSRuntime JS
|
||||
|
||||
<article class="paste">
|
||||
@* TODO link to paste view
|
||||
* <!--
|
||||
* order:-178.75
|
||||
* -->
|
||||
*@
|
||||
|
||||
<nav>
|
||||
<h5 class="mono"><a href="/pastes/tbc">@Paste.Title</a></h5>
|
||||
<div class="max"></div>
|
||||
@if (MarkupView)
|
||||
{
|
||||
<button class="transparent circle" title="View Original" @onclick="() => { MarkupView = false; InvokeAsync(StateHasChanged); }"><i class="fa-solid fa-code"></i></button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button class="transparent circle" title="View Markup" @onclick="() => { MarkupView = true; InvokeAsync(StateHasChanged); }"><i class="fa-solid fa-browser"></i></button>
|
||||
}
|
||||
<button class="transparent circle" title="Copy to Clipboard" @onclick="() => 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 chip no-border"><i class="fa-solid fa-clock tiny"></i> @Paste.RelativeTime</small>
|
||||
@if(MarkupView){
|
||||
<div class="padding">
|
||||
@Utilities.MdToHtmlMarkup(Paste.Content)
|
||||
</div>
|
||||
}
|
||||
else {
|
||||
<pre><code class="padding margin">@((MarkupString)Paste.Content)</code></pre>
|
||||
}
|
||||
<nav>
|
||||
<div class="max"></div>
|
||||
@if (Editable) {
|
||||
<button @onclick="EditPaste"><i class="fa-solid fa-pencil"></i> Edit</button>
|
||||
}
|
||||
</nav>
|
||||
</article>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public Paste? Paste { get; set; }
|
||||
[Parameter]
|
||||
public bool Editable { get; set; } = false;
|
||||
[Parameter]
|
||||
public EditPasteDialog? Dialog { get; set; }
|
||||
|
||||
private bool MarkupView = false;
|
||||
|
||||
private async Task EditPaste(EventArgs e) {
|
||||
Dialog!.Paste = Paste;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
await JS.InvokeVoidAsync("ui", "#" + Dialog?.id);
|
||||
}
|
||||
|
||||
public async Task ShareClick(EventArgs e) {
|
||||
await Share.Default.RequestAsync(new ShareTextRequest {
|
||||
Uri = Paste!.Url,
|
||||
Text = Paste!.Content,
|
||||
Title = Paste!.Title,
|
||||
Subject = Paste!.Title
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
51
Components/PasteList.razor
Normal file
51
Components/PasteList.razor
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
@implements IDisposable
|
||||
@inject IJSRuntime JS
|
||||
@inject State State
|
||||
|
||||
@if (Editable) {
|
||||
<EditPasteDialog @ref="Dialog" id="EditPasteModal"></EditPasteDialog>
|
||||
}
|
||||
|
||||
@if (pastes != null) foreach (Paste paste in pastes) {
|
||||
<PasteCard Paste="paste" Editable="Editable" Dialog="Dialog"></PasteCard>
|
||||
}
|
||||
|
||||
<LoadingCard id="pastes-loading" icon="fa-solid fa-clipboard"></LoadingCard>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public Func<bool, Task<List<Paste>?>>? PastesFunc { get; set; }
|
||||
[Parameter]
|
||||
public bool Editable { get; set; } = false;
|
||||
|
||||
public EditPasteDialog? Dialog { get; set; }
|
||||
|
||||
private List<Paste>? pastes;
|
||||
|
||||
protected override async Task OnInitializedAsync() {
|
||||
await base.OnInitializedAsync();
|
||||
if (PastesFunc == null) return;
|
||||
|
||||
if (pastes == null || pastes.Count == 0) pastes = await PastesFunc(false);
|
||||
State.PropertyChanged += StateChanged;
|
||||
State.CanRefresh = true;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
await JS.InvokeVoidAsync("removeElementById", "pastes-loading");
|
||||
}
|
||||
|
||||
private async void StateChanged(object? sender, PropertyChangedEventArgs e) {
|
||||
if (PastesFunc == null) return;
|
||||
|
||||
if (e.PropertyName == nameof(State.IsRefreshing) && State.IsRefreshing) {
|
||||
using (State.GetRefreshToken()) {
|
||||
pastes = await PastesFunc(true);
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
State.PropertyChanged -= StateChanged;
|
||||
State.CanRefresh = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
<article class="status">
|
||||
<div class="row">
|
||||
<div class="large emoji skeleton round" />
|
||||
<div class="max">
|
||||
<span class="author skeleton"> </span>
|
||||
<p class="skeleton"> </p>
|
||||
</div>
|
||||
</div>
|
||||
<nav>
|
||||
<span class="chip transparent-border skeleton"> </span>
|
||||
</nav>
|
||||
</article>
|
||||
|
||||
@code {
|
||||
|
||||
}
|
||||
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="honey">@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; }
|
||||
}
|
||||
81
Components/ThemeDialog.razor
Normal file
81
Components/ThemeDialog.razor
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
@inject IJSRuntime JS
|
||||
@inject State State
|
||||
@inject ApiService 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="honey">@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() {
|
||||
onthemechanged?.Invoke(activeTheme);
|
||||
activeTheme = null;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
await JS.InvokeVoidAsync("ui", "#" + previewId);
|
||||
}
|
||||
}
|
||||
|
|
@ -14,3 +14,4 @@
|
|||
@using Markdig
|
||||
@using PSC.Blazor.Components.MarkdownEditor
|
||||
@using PSC.Blazor.Components.MarkdownEditor.EventsArgs
|
||||
@using PSC.Blazor.Components.MarkdownEditor.Enums
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
@ -8,14 +9,14 @@ namespace Neighbourhood.omg.lol {
|
|||
var builder = MauiApp.CreateBuilder();
|
||||
builder
|
||||
.UseMauiApp<App>()
|
||||
.ConfigureFonts(fonts => {
|
||||
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
|
||||
.UseMauiCommunityToolkit(options => {
|
||||
options.SetShouldEnableSnackbarOnWindows(true);
|
||||
});
|
||||
|
||||
builder.Services.AddMauiBlazorWebView();
|
||||
builder.Services.AddTransient<LoginWebViewPage>();
|
||||
|
||||
builder.Services.AddSingleton<RestService>();
|
||||
builder.Services.AddSingleton<ApiService>();
|
||||
builder.Services.AddSingleton<State>();
|
||||
builder.Services.AddSingleton<NavigatorService>();
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,5 @@
|
|||
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> {
|
||||
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 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,4 +1,5 @@
|
|||
namespace Neighbourhood.omg.lol.Models {
|
||||
public interface IOmgLolResponseData {
|
||||
public string Message { 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 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; }
|
||||
|
|
|
|||
11
Models/API/OmgLolApiException.cs
Normal file
11
Models/API/OmgLolApiException.cs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
namespace Neighbourhood.omg.lol.Models {
|
||||
public class OmgLolApiException<T> : Exception where T : IOmgLolResponseData {
|
||||
public OmgLolResponse<T>? Response { get; set; }
|
||||
|
||||
public OmgLolApiException(OmgLolResponse<T>? response) : base(response?.Response?.Message) {
|
||||
Response = response;
|
||||
}
|
||||
|
||||
public OmgLolApiException(string? response) : base(response) { }
|
||||
}
|
||||
}
|
||||
6
Models/API/PastesResponseData.cs
Normal file
6
Models/API/PastesResponseData.cs
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
namespace Neighbourhood.omg.lol.Models {
|
||||
public class PastesResponseData : IOmgLolResponseData {
|
||||
public string Message { get; set; } = string.Empty;
|
||||
public List<Paste> Pastebin { get; set; } = new List<Paste>();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
6
Models/API/PostPasteResponseData.cs
Normal file
6
Models/API/PostPasteResponseData.cs
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
namespace Neighbourhood.omg.lol.Models {
|
||||
public class PostPasteResponseData : IOmgLolResponseData {
|
||||
public string Message { get; set; } = string.Empty;
|
||||
public string Title { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
|
|
|
|||
9
Models/API/PostProfile.cs
Normal file
9
Models/API/PostProfile.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
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; }
|
||||
}
|
||||
}
|
||||
5
Models/API/PostStatusBio.cs
Normal file
5
Models/API/PostStatusBio.cs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
namespace Neighbourhood.omg.lol.Models {
|
||||
public class PostStatusBio {
|
||||
public string Content { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
16
Models/API/ProfileResponseData.cs
Normal file
16
Models/API/ProfileResponseData.cs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
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; }
|
||||
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,12 +1,6 @@
|
|||
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 Message { get; set; } = string.Empty;
|
||||
public string? Id { get; set; }
|
||||
public string? Url { get; set; }
|
||||
public string? ExternalUrl { 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,17 +1,13 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
public class StatusOrPic {
|
||||
namespace Neighbourhood.omg.lol.Models {
|
||||
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; }
|
||||
}
|
||||
}
|
||||
29
Models/Paste.cs
Normal file
29
Models/Paste.cs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
namespace Neighbourhood.omg.lol.Models {
|
||||
public class Paste {
|
||||
public string? Url;
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string Content { get; set; } = string.Empty;
|
||||
public long? ModifiedOn { get; set; }
|
||||
public int Listed { get; set; }
|
||||
|
||||
public bool IsListed {
|
||||
get => Listed != 0;
|
||||
set => Listed = value ? 1 : 0;
|
||||
}
|
||||
|
||||
public DateTimeOffset ModifiedTime { get => DateTimeOffset.UnixEpoch.AddSeconds(ModifiedOn ?? 0); }
|
||||
public string RelativeTime {
|
||||
get {
|
||||
TimeSpan offset = DateTimeOffset.UtcNow - ModifiedTime;
|
||||
|
||||
var offsetString = string.Empty;
|
||||
if (offset.TotalDays >= 1) offsetString = $"{Math.Floor(offset.TotalDays)} days ago";
|
||||
else if (offset.TotalHours >= 1) offsetString = $"{Math.Floor(offset.TotalHours)} hours, {offset.Minutes} minutes ago";
|
||||
else if (offset.TotalMinutes >= 1) offsetString = $"{Math.Floor(offset.TotalMinutes)} minutes ago";
|
||||
else offsetString = $"{Math.Floor(offset.TotalSeconds)} seconds ago";
|
||||
|
||||
return offsetString;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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 ApiService().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; }
|
||||
|
|
|
|||
|
|
@ -43,22 +43,22 @@
|
|||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0-ios|AnyCPU'">
|
||||
<ApplicationId>au.death.lol.omg.neighbourhood</ApplicationId>
|
||||
<ApplicationDisplayVersion>0.9.3</ApplicationDisplayVersion>
|
||||
<ApplicationVersion>5</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.3</ApplicationDisplayVersion>
|
||||
<ApplicationVersion>5</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.3</ApplicationDisplayVersion>
|
||||
<ApplicationDisplayVersion>0.9.9</ApplicationDisplayVersion>
|
||||
<AndroidKeyStore>True</AndroidKeyStore>
|
||||
<AndroidSigningKeyStore>D:\Neighbourhood.omg.lol\neighbourhood.omg.lol.keystore</AndroidSigningKeyStore>
|
||||
<ApplicationVersion>5</ApplicationVersion>
|
||||
<AndroidSigningKeyStore>D:\_assets\neighbourhood.omg.lol\neighbourhood.omg.lol.keystore</AndroidSigningKeyStore>
|
||||
<ApplicationVersion>9</ApplicationVersion>
|
||||
<AndroidSigningStorePass>a!zobzizl</AndroidSigningStorePass>
|
||||
<AndroidSigningKeyAlias>neighbourhood.omg.lol</AndroidSigningKeyAlias>
|
||||
<AndroidSigningKeyPass>a!zobzizl</AndroidSigningKeyPass>
|
||||
|
|
@ -66,36 +66,37 @@
|
|||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0-windows10.0.19041.0|AnyCPU'">
|
||||
<ApplicationId>au.death.lol.omg.neighbourhood</ApplicationId>
|
||||
<ApplicationDisplayVersion>0.9.3</ApplicationDisplayVersion>
|
||||
<ApplicationVersion>5</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.3</ApplicationDisplayVersion>
|
||||
<ApplicationVersion>5</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.3</ApplicationDisplayVersion>
|
||||
<ApplicationVersion>5</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.3</ApplicationDisplayVersion>
|
||||
<ApplicationVersion>5</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.3</ApplicationDisplayVersion>
|
||||
<ApplicationVersion>5</ApplicationVersion>
|
||||
<ApplicationDisplayVersion>0.9.9</ApplicationDisplayVersion>
|
||||
<ApplicationVersion>9</ApplicationVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- App Icon -->
|
||||
<MauiIcon Include="Resources\AppIcon\icon.svg" />
|
||||
<MauiIcon Include="Resources\AppIcon\icon_background.svg" ForegroundFile="Resources\AppIcon\icon_foreground.svg" Color="#f3eb76" BaseSize="1024,1024" />
|
||||
|
||||
<!-- Splash Screen -->
|
||||
|
|
@ -103,7 +104,6 @@
|
|||
|
||||
<!-- Images -->
|
||||
<MauiImage Include="Resources\Images\*" />
|
||||
<MauiImage Update="Resources\Images\dotnet_bot.svg" BaseSize="168,208" />
|
||||
|
||||
<!-- Custom Fonts -->
|
||||
<MauiFont Include="Resources\Fonts\*" />
|
||||
|
|
@ -117,108 +117,7 @@
|
|||
</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" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Resources\Fonts\fa-brands-400.ttf" />
|
||||
<None Remove="Resources\Fonts\fa-brands-400.woff2" />
|
||||
<None Remove="Resources\Fonts\fa-duotone-900.ttf" />
|
||||
<None Remove="Resources\Fonts\fa-duotone-900.woff2" />
|
||||
<None Remove="Resources\Fonts\fa-light-300.ttf" />
|
||||
<None Remove="Resources\Fonts\fa-light-300.woff2" />
|
||||
<None Remove="Resources\Fonts\fa-regular-400.ttf" />
|
||||
<None Remove="Resources\Fonts\fa-regular-400.woff2" />
|
||||
<None Remove="Resources\Fonts\fa-solid-900.ttf" />
|
||||
<None Remove="Resources\Fonts\fa-solid-900.woff2" />
|
||||
<None Remove="Resources\Fonts\fa-thin-100.ttf" />
|
||||
<None Remove="Resources\Fonts\fa-thin-100.woff2" />
|
||||
<None Remove="Resources\Fonts\fa-v4compatibility.ttf" />
|
||||
<None Remove="Resources\Fonts\fa-v4compatibility.woff2" />
|
||||
<None Remove="Resources\Fonts\omg.lol-icons.woff2" />
|
||||
<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>
|
||||
<None Remove="Resources\AppIcon\icon.svg" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
@ -226,18 +125,20 @@
|
|||
</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.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
|
||||
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="$(MauiVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="$(MauiVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Maui.Controls" Version="8.0.70" />
|
||||
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="8.0.70" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="8.0.70" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Maui.Essentials" Version="8.0.40" />
|
||||
<PackageReference Include="PSC.Blazor.Components.MarkdownEditor" 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.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
@ -249,4 +150,10 @@
|
|||
</MauiXaml>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Resources\Fonts\" />
|
||||
<Folder Include="Resources\Images\" />
|
||||
<Folder Include="Resources\Raw\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -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="5" android:versionName="0.9.3">
|
||||
<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" />
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -2,31 +2,35 @@
|
|||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UIDeviceFamily</key>
|
||||
<array>
|
||||
<integer>1</integer>
|
||||
<integer>2</integer>
|
||||
</array>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>XSAppIconAssets</key>
|
||||
<string>Assets.xcassets/appicon.appiconset</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UIDeviceFamily</key>
|
||||
<array>
|
||||
<integer>1</integer>
|
||||
<integer>2</integer>
|
||||
</array>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>XSAppIconAssets</key>
|
||||
<string>Assets.xcassets/icon.appiconset</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.9.9</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>14.2</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="0" y="0" width="456" height="456" fill="#512BD4" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 228 B |
|
|
@ -1,8 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<path d="m 105.50037,281.60863 c -2.70293,0 -5.00091,-0.90042 -6.893127,-2.70209 -1.892214,-1.84778 -2.837901,-4.04181 -2.837901,-6.58209 0,-2.58722 0.945687,-4.80389 2.837901,-6.65167 1.892217,-1.84778 4.190197,-2.77167 6.893127,-2.77167 2.74819,0 5.06798,0.92389 6.96019,2.77167 1.93749,1.84778 2.90581,4.06445 2.90581,6.65167 0,2.54028 -0.96832,4.73431 -2.90581,6.58209 -1.89221,1.80167 -4.212,2.70209 -6.96019,2.70209 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||
<path d="M 213.56111,280.08446 H 195.99044 L 149.69953,207.0544 c -1.17121,-1.84778 -2.14037,-3.76515 -2.90581,-5.75126 h -0.40578 c 0.36051,2.12528 0.54076,6.67515 0.54076,13.6496 v 65.13172 h -15.54349 v -99.36009 h 18.71925 l 44.7374,71.29798 c 1.89222,2.95695 3.1087,4.98917 3.64945,6.09751 h 0.26996 c -0.45021,-2.6325 -0.67573,-7.09015 -0.67573,-13.37293 v -64.02256 h 15.47557 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||
<path d="m 289.25134,280.08446 h -54.40052 v -99.36009 h 52.23835 v 13.99669 h -36.15411 v 28.13085 h 33.31621 v 13.9271 h -33.31621 v 29.37835 h 38.31628 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||
<path d="M 366.56466,194.72106 H 338.7222 v 85.3634 h -16.08423 v -85.3634 h -27.77455 v -13.99669 h 71.70124 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.8 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 120 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue